From 1b6295b2bf38c0218ebe3c1168db18db63d90f70 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 23 Oct 2023 12:49:16 +0300 Subject: [PATCH 001/111] [FL-3632] FastFAP: human readable error log (#3156) --- lib/flipper_application/elf/elf_file.c | 55 +++++++++++++++++++++----- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 9b8b4c8f5f..b2c9445ffe 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -613,10 +613,31 @@ static Elf32_Addr elf_address_of_by_hash(ELFFile* elf, uint32_t hash) { return ELF_INVALID_ADDRESS; } +static bool elf_file_find_string_by_hash(ELFFile* elf, uint32_t hash, FuriString* out) { + bool result = false; + + FuriString* symbol_name = furi_string_alloc(); + Elf32_Sym sym; + for(size_t i = 0; i < elf->symbol_count; i++) { + furi_string_reset(symbol_name); + if(elf_read_symbol(elf, i, &sym, symbol_name)) { + if(elf_symbolname_hash(furi_string_get_cstr(symbol_name)) == hash) { + furi_string_set(out, symbol_name); + result = true; + break; + } + } + } + furi_string_free(symbol_name); + + return result; +} + static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { UNUSED(elf); const uint8_t* start = s->fast_rel->data; const uint8_t version = *start; + bool no_errors = true; if(version != FAST_RELOCATION_VERSION) { FURI_LOG_E(TAG, "Unsupported fast relocation version %d", version); @@ -664,16 +685,30 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } if(address == ELF_INVALID_ADDRESS) { - FURI_LOG_E(TAG, "Failed to resolve address for hash %lX", hash_or_section_index); - return false; - } + FuriString* symbol_name = furi_string_alloc(); + if(elf_file_find_string_by_hash(elf, hash_or_section_index, symbol_name)) { + FURI_LOG_E( + TAG, + "Failed to resolve address for symbol %s (hash %lX)", + furi_string_get_cstr(symbol_name), + hash_or_section_index); + } else { + FURI_LOG_E( + TAG, + "Failed to resolve address for hash %lX (string not found)", + hash_or_section_index); + } + furi_string_free(symbol_name); - for(uint32_t j = 0; j < offsets_count; j++) { - uint32_t offset = *((uint32_t*)start) & 0x00FFFFFF; - start += 3; - // FURI_LOG_I(TAG, " Fast relocation offset %ld: %ld", j, offset); - Elf32_Addr relAddr = ((Elf32_Addr)s->data) + offset; - elf_relocate_symbol(elf, relAddr, type, address); + no_errors = false; + start += 3 * offsets_count; + } else { + for(uint32_t j = 0; j < offsets_count; j++) { + uint32_t offset = *((uint32_t*)start) & 0x00FFFFFF; + start += 3; + Elf32_Addr relAddr = ((Elf32_Addr)s->data) + offset; + elf_relocate_symbol(elf, relAddr, type, address); + } } } @@ -681,7 +716,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { free(s->fast_rel); s->fast_rel = NULL; - return true; + return no_errors; } static bool elf_relocate_section(ELFFile* elf, ELFSection* section) { From 35c903494c511f41a518dd045be5ab8991fa3991 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 23 Oct 2023 13:55:36 +0400 Subject: [PATCH 002/111] [FL-3627, FL-3628, FL-3631] fbt: glob & git improvements (#3151) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: optional shallow submodule checkout * fbt: more git threads by default * fbt: git condition fix * fbt: renamed FBT_SHALLOW to FBT_GIT_SUBMODULE_SHALLOW * github: enabled FBT_GIT_SUBMODULE_SHALLOW in flows * fbt: always compile icons' .c, even if user does not specify a proper source glob; changed glob to require files at user-specified paths to exist * fbt: fail build for missing imports in .faps * fbt: moved STRICT_FAP_IMPORT_CHECK to commandline options; enabled by default * ufbt: enabled STRICT_FAP_IMPORT_CHECK Co-authored-by: あく --- .github/workflows/build.yml | 1 + .github/workflows/build_compact.yml | 1 + .github/workflows/pvs_studio.yml | 1 + .github/workflows/unit_tests.yml | 1 + .github/workflows/updater_test.yml | 1 + fbt | 11 +++++++++-- fbt.cmd | 12 ++++++++++-- scripts/fbt/appmanifest.py | 4 ++++ scripts/fbt_tools/fbt_extapps.py | 15 +++++++++++++-- scripts/fbt_tools/sconsrecursiveglob.py | 7 +++---- scripts/ufbt/commandline.scons | 5 +++++ site_scons/commandline.scons | 5 +++++ 12 files changed, 54 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a71888379..4c55350665 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,7 @@ on: env: DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: main: diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index 7556c15acd..c63be24a47 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -5,6 +5,7 @@ on: env: FBT_TOOLCHAIN_PATH: /runner/_work + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: compact: diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 24964a3094..4f650f1b7c 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -10,6 +10,7 @@ env: TARGETS: f7 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: analyse_c_cpp: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 794dce37f0..35085ba57a 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -7,6 +7,7 @@ env: TARGETS: f7 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /opt + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: run_units_on_bench: diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index f4064e1cca..9987fdd324 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -7,6 +7,7 @@ env: TARGETS: f7 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /opt + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: test_updater_on_bench: diff --git a/fbt b/fbt index 26f325d455..e6133d07b1 100755 --- a/fbt +++ b/fbt @@ -5,7 +5,8 @@ set -eu; # private variables -N_GIT_THREADS="$(getconf _NPROCESSORS_ONLN)"; +N_CORES="$(getconf _NPROCESSORS_ONLN)"; +N_GIT_THREADS="$(($N_CORES * 2))"; SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)"; SCONS_DEFAULT_FLAGS="--warn=target-not-built"; SCONS_EP="python3 -m SCons"; @@ -15,6 +16,7 @@ FBT_NOENV="${FBT_NOENV:-""}"; FBT_NO_SYNC="${FBT_NO_SYNC:-""}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; +FBT_GIT_SUBMODULE_SHALLOW="${FBT_GIT_SUBMODULE_SHALLOW:-""}"; if [ -z "$FBT_NOENV" ]; then FBT_VERBOSE="$FBT_VERBOSE" . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; @@ -29,7 +31,12 @@ if [ -z "$FBT_NO_SYNC" ]; then echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi - git submodule update --init --jobs "$N_GIT_THREADS"; + _FBT_CLONE_FLAGS="--jobs $N_GIT_THREADS"; + if [ ! -z "$FBT_GIT_SUBMODULE_SHALLOW" ]; then + _FBT_CLONE_FLAGS="$_FBT_CLONE_FLAGS --depth 1"; + fi + + git submodule update --init --recursive $_FBT_CLONE_FLAGS; fi $SCONS_EP $SCONS_DEFAULT_FLAGS "$@" diff --git a/fbt.cmd b/fbt.cmd index 03e4ec3d09..20432be1c5 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -4,10 +4,18 @@ call "%~dp0scripts\toolchain\fbtenv.cmd" env set SCONS_EP=python -m SCons if [%FBT_NO_SYNC%] == [] ( + set _FBT_CLONE_FLAGS=--jobs %NUMBER_OF_PROCESSORS% + if not [%FBT_GIT_SUBMODULE_SHALLOW%] == [] ( + set _FBT_CLONE_FLAGS=%_FBT_CLONE_FLAGS% --depth 1 + ) if exist ".git" ( - git submodule update --init --depth 1 --jobs %NUMBER_OF_PROCESSORS% + git submodule update --init --recursive %_FBT_CLONE_FLAGS% + if %ERRORLEVEL% neq 0 ( + echo Failed to update submodules, set FBT_NO_SYNC to skip + exit /b 1 + ) ) else ( - echo Not in a git repo, please clone with "git clone" + echo .git not found, please clone repo with "git clone" exit /b 1 ) ) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index ff49707b1d..1a6cae9b17 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -100,6 +100,10 @@ def supports_hardware_target(self, target: str): def is_default_deployable(self): return self.apptype != FlipperAppType.DEBUG and self.fap_category != "Examples" + @property + def do_strict_import_checks(self): + return self.apptype != FlipperAppType.PLUGIN + def __post_init__(self): if self.apptype == FlipperAppType.PLUGIN: self.stack_size = 0 diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index bc8f9d5df7..963429f245 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -42,6 +42,7 @@ def __init__(self, env, app): self.ext_apps_work_dir = env["EXT_APPS_WORK_DIR"] self.app_work_dir = self.get_app_work_dir(env, app) self.app_alias = f"fap_{self.app.appid}" + self.icons_src = None self.externally_built_files = [] self.private_libs = [] @@ -93,6 +94,7 @@ def _compile_assets(self): ) self.app_env.Alias("_fap_icons", fap_icons) self.fw_env.Append(_APP_ICONS=[fap_icons]) + self.icons_src = next(filter(lambda n: n.path.endswith(".c"), fap_icons)) def _build_private_libs(self): for lib_def in self.app.fap_private_libs: @@ -160,6 +162,10 @@ def _build_app(self): if not app_sources: raise UserError(f"No source files found for {self.app.appid}") + # Ensure that icons are included in the build, regardless of user-configured sources + if self.icons_src and not self.icons_src in app_sources: + app_sources.append(self.icons_src) + ## Uncomment for debug # print(f"App sources for {self.app.appid}: {list(f.path for f in app_sources)}") @@ -180,7 +186,9 @@ def _build_app(self): self.app._assets_dirs.append(self.app_work_dir.Dir("assets")) app_artifacts.validator = self.app_env.ValidateAppImports( - app_artifacts.compact + app_artifacts.compact, + _CHECK_APP=self.app.do_strict_import_checks + and self.app_env.get("STRICT_FAP_IMPORT_CHECK"), )[0] if self.app.apptype == FlipperAppType.PLUGIN: @@ -300,7 +308,10 @@ def validate_app_imports(target, source, env): + fg.brightmagenta(f"{disabled_api_syms}") + fg.brightyellow(")") ) - SCons.Warnings.warn(SCons.Warnings.LinkWarning, warning_msg), + if env.get("_CHECK_APP"): + raise UserError(warning_msg) + else: + SCons.Warnings.warn(SCons.Warnings.LinkWarning, warning_msg), def GetExtAppByIdOrPath(env, app_dir): diff --git a/scripts/fbt_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py index 38aa9a839f..e7eb8fb729 100644 --- a/scripts/fbt_tools/sconsrecursiveglob.py +++ b/scripts/fbt_tools/sconsrecursiveglob.py @@ -20,10 +20,9 @@ def GlobRecursive(env, pattern, node=".", exclude=[]): source=True, exclude=exclude, ) - # Otherwise, just check if that's an existing file path - # NB: still creates "virtual" nodes as part of existence check - elif (file_node := node.File(pattern)).exists() or file_node.rexists(): - results.append(file_node) + # Otherwise, just assume that file at path exists + else: + results.append(node.File(pattern)) # print(f"Glob result for {pattern} from {node}: {results}") return results diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons index 99c34c35da..a3ba7e08da 100644 --- a/scripts/ufbt/commandline.scons +++ b/scripts/ufbt/commandline.scons @@ -88,6 +88,11 @@ vars.AddVariables( "CDC Port of Flipper to use, if multiple are connected", "auto", ), + BoolVariable( + "STRICT_FAP_IMPORT_CHECK", + help="Enable strict import check for .faps", + default=True, + ), ) Return("vars") diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index f75d5e0e4d..2d2abd5c67 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -269,6 +269,11 @@ vars.AddVariables( "clangd", ], ), + BoolVariable( + "STRICT_FAP_IMPORT_CHECK", + help="Enable strict import check for .faps", + default=True, + ), ) Return("vars") From ac5abdbb1db0ded15b2610a3ffd70199c45c6859 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 24 Oct 2023 00:56:15 +0300 Subject: [PATCH 003/111] add new env var --- .drone.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.drone.yml b/.drone.yml index 39a79b2cf0..341f49e4e1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,6 +22,7 @@ steps: - export DIST_SUFFIX=${DRONE_TAG}c - export WORKFLOW_BRANCH_OR_TAG=release-cfw - export FORCE_NO_DIRTY=yes + - export FBT_GIT_SUBMODULE_SHALLOW=1 - rm -rf assets/resources/apps/ - rm -rf build/ - rm -rf dist/ @@ -42,6 +43,7 @@ steps: - export DIST_SUFFIX=${DRONE_TAG} - export WORKFLOW_BRANCH_OR_TAG=release-cfw - 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 - cp -R base_pack_build/artifacts-base/* assets/resources/apps/ @@ -69,6 +71,7 @@ steps: - export DIST_SUFFIX=${DRONE_TAG}e - export WORKFLOW_BRANCH_OR_TAG=release-cfw - export FORCE_NO_DIRTY=yes + - export FBT_GIT_SUBMODULE_SHALLOW=1 - rm -f build/f7-firmware-C/toolbox/version.* - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-extra-apps @@ -87,6 +90,7 @@ steps: - export DIST_SUFFIX=${DRONE_TAG}r - export WORKFLOW_BRANCH_OR_TAG=release-cfw-rgb - export FORCE_NO_DIRTY=yes + - export FBT_GIT_SUBMODULE_SHALLOW=1 - rm -f build/f7-firmware-C/toolbox/version.* - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-rgb-patch @@ -109,6 +113,7 @@ steps: - export DIST_SUFFIX=${DRONE_TAG}n - export WORKFLOW_BRANCH_OR_TAG=no-custom-anims - export FORCE_NO_DIRTY=yes + - export FBT_GIT_SUBMODULE_SHALLOW=1 - 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 @@ -391,6 +396,7 @@ steps: - export DIST_SUFFIX=${DRONE_BUILD_NUMBER}c - export WORKFLOW_BRANCH_OR_TAG=dev-cfw - export FORCE_NO_DIRTY=yes + - export FBT_GIT_SUBMODULE_SHALLOW=1 - rm -rf assets/resources/apps/ - rm -rf build/ - rm -rf dist/ @@ -412,6 +418,7 @@ steps: - export DIST_SUFFIX=${DRONE_BUILD_NUMBER} - export WORKFLOW_BRANCH_OR_TAG=dev-cfw - 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 - cp -R base_pack_build/artifacts-base/* assets/resources/apps/ @@ -439,6 +446,7 @@ steps: - export DIST_SUFFIX=${DRONE_BUILD_NUMBER}e - export WORKFLOW_BRANCH_OR_TAG=dev-cfw - export FORCE_NO_DIRTY=yes + - export FBT_GIT_SUBMODULE_SHALLOW=1 - rm -f build/f7-firmware-C/toolbox/version.* - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-extra-apps @@ -457,6 +465,7 @@ steps: - export DIST_SUFFIX=${DRONE_BUILD_NUMBER}r - export WORKFLOW_BRANCH_OR_TAG=dev-cfw-rgb - export FORCE_NO_DIRTY=yes + - export FBT_GIT_SUBMODULE_SHALLOW=1 - rm -f build/f7-firmware-C/toolbox/version.* - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-rgb-patch From d92b0a82cc4c79acebbcee6b8e650fbc0e1aa035 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 24 Oct 2023 07:08:09 +0400 Subject: [PATCH 004/111] NFC refactoring (#3050) "A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring. Starring: - @gornekich - NFC refactoring project lead, architect, senior developer - @gsurkov - architect, senior developer - @RebornedBrain - senior developer Supporting roles: - @skotopes, @DrZlo13, @hedger - general architecture advisors, code review - @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance Special thanks: @bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code. --- .github/CODEOWNERS | 1 - .github/workflows/unit_tests.yml | 4 +- .pvsoptions | 2 +- applications/debug/unit_tests/nfc/nfc_test.c | 910 +- .../debug/unit_tests/nfc/nfc_transport.c | 458 + applications/main/nfc/application.fam | 50 + .../main/nfc/helpers/mf_classic_key_cache.c | 212 + .../main/nfc/helpers/mf_classic_key_cache.h | 31 + .../main/nfc/helpers/mf_ultralight_auth.c | 58 + .../main/nfc/helpers/mf_ultralight_auth.h | 35 + applications/main/nfc/helpers/mf_user_dict.c | 83 + applications/main/nfc/helpers/mf_user_dict.h | 23 + .../main/nfc/helpers/mfkey32_logger.c | 173 + .../main/nfc/helpers/mfkey32_logger.h | 25 + .../main/nfc/helpers/nfc_custom_event.h | 26 +- .../main/nfc/helpers/nfc_supported_cards.c | 147 + .../main/nfc/helpers/nfc_supported_cards.h | 50 + .../helpers/protocol_support/felica/felica.c | 108 + .../helpers/protocol_support/felica/felica.h | 5 + .../protocol_support/felica/felica_render.c | 19 + .../protocol_support/felica/felica_render.h | 10 + .../iso14443_3a/iso14443_3a.c | 145 + .../iso14443_3a/iso14443_3a.h | 5 + .../iso14443_3a/iso14443_3a_render.c | 34 + .../iso14443_3a/iso14443_3a_render.h | 16 + .../iso14443_3b/iso14443_3b.c | 113 + .../iso14443_3b/iso14443_3b.h | 5 + .../iso14443_3b/iso14443_3b_i.h | 7 + .../iso14443_3b/iso14443_3b_render.c | 78 + .../iso14443_3b/iso14443_3b_render.h | 10 + .../iso14443_4a/iso14443_4a.c | 149 + .../iso14443_4a/iso14443_4a.h | 5 + .../iso14443_4a/iso14443_4a_i.h | 7 + .../iso14443_4a/iso14443_4a_render.c | 84 + .../iso14443_4a/iso14443_4a_render.h | 14 + .../iso14443_4b/iso14443_4b.c | 118 + .../iso14443_4b/iso14443_4b.h | 5 + .../iso14443_4b/iso14443_4b_render.c | 10 + .../iso14443_4b/iso14443_4b_render.h | 10 + .../protocol_support/iso15693_3/iso15693_3.c | 144 + .../protocol_support/iso15693_3/iso15693_3.h | 5 + .../iso15693_3/iso15693_3_render.c | 92 + .../iso15693_3/iso15693_3_render.h | 14 + .../protocol_support/mf_classic/mf_classic.c | 252 + .../protocol_support/mf_classic/mf_classic.h | 5 + .../mf_classic/mf_classic_render.c | 30 + .../mf_classic/mf_classic_render.h | 12 + .../protocol_support/mf_desfire/mf_desfire.c | 120 + .../protocol_support/mf_desfire/mf_desfire.h | 5 + .../mf_desfire/mf_desfire_render.c | 249 + .../mf_desfire/mf_desfire_render.h | 34 + .../mf_ultralight/mf_ultralight.c | 196 + .../mf_ultralight/mf_ultralight.h | 5 + .../mf_ultralight/mf_ultralight_render.c | 45 + .../mf_ultralight/mf_ultralight_render.h | 14 + .../protocol_support/nfc_protocol_support.c | 786 ++ .../protocol_support/nfc_protocol_support.h | 113 + .../nfc_protocol_support_base.h | 116 + .../nfc_protocol_support_common.h | 36 + .../nfc_protocol_support_defs.c | 45 + .../nfc_protocol_support_defs.h | 12 + .../nfc_protocol_support_gui_common.c | 42 + .../nfc_protocol_support_gui_common.h | 85 + .../nfc_protocol_support_render_common.h | 13 + .../nfc/helpers/protocol_support/slix/slix.c | 141 + .../nfc/helpers/protocol_support/slix/slix.h | 5 + .../protocol_support/slix/slix_render.c | 74 + .../protocol_support/slix/slix_render.h | 7 + .../helpers/protocol_support/st25tb/st25tb.c | 103 + .../helpers/protocol_support/st25tb/st25tb.h | 5 + .../protocol_support/st25tb/st25tb_render.c | 22 + .../protocol_support/st25tb/st25tb_render.h | 10 + applications/main/nfc/nfc.c | 323 - applications/main/nfc/nfc.h | 3 - applications/main/nfc/nfc_app.c | 499 + applications/main/nfc/nfc_app.h | 19 + applications/main/nfc/nfc_app_i.h | 192 + applications/main/nfc/nfc_cli.c | 159 +- applications/main/nfc/nfc_i.h | 118 - .../nfc/plugins/supported_cards/all_in_one.c | 107 + .../main/nfc/plugins/supported_cards/myki.c | 116 + .../nfc_supported_card_plugin.h | 94 + .../main/nfc/plugins/supported_cards/opal.c | 233 + .../nfc/plugins/supported_cards/plantain.c | 219 + .../main/nfc/plugins/supported_cards/troika.c | 214 + .../nfc/plugins/supported_cards/two_cities.c | 189 + .../main/nfc/scenes/nfc_scene_config.h | 107 +- .../main/nfc/scenes/nfc_scene_debug.c | 18 +- .../main/nfc/scenes/nfc_scene_delete.c | 41 +- .../nfc/scenes/nfc_scene_delete_success.c | 10 +- .../main/nfc/scenes/nfc_scene_detect.c | 59 + .../main/nfc/scenes/nfc_scene_detect_reader.c | 103 - .../main/nfc/scenes/nfc_scene_device_info.c | 87 - .../nfc/scenes/nfc_scene_dict_not_found.c | 52 - .../main/nfc/scenes/nfc_scene_emulate.c | 13 + .../scenes/nfc_scene_emulate_apdu_sequence.c | 34 - .../main/nfc/scenes/nfc_scene_emulate_uid.c | 144 - .../main/nfc/scenes/nfc_scene_emv_menu.c | 46 - .../nfc/scenes/nfc_scene_emv_read_success.c | 113 - .../main/nfc/scenes/nfc_scene_exit_confirm.c | 14 +- .../main/nfc/scenes/nfc_scene_extra_actions.c | 60 +- .../main/nfc/scenes/nfc_scene_field.c | 12 +- .../main/nfc/scenes/nfc_scene_file_select.c | 22 +- .../main/nfc/scenes/nfc_scene_generate_info.c | 65 +- applications/main/nfc/scenes/nfc_scene_info.c | 13 + .../nfc/scenes/nfc_scene_mf_classic_data.c | 106 - .../nfc_scene_mf_classic_detect_reader.c | 154 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 348 +- .../nfc/scenes/nfc_scene_mf_classic_emulate.c | 69 - .../nfc/scenes/nfc_scene_mf_classic_keys.c | 76 +- .../scenes/nfc_scene_mf_classic_keys_add.c | 56 +- .../scenes/nfc_scene_mf_classic_keys_delete.c | 62 +- .../scenes/nfc_scene_mf_classic_keys_list.c | 103 +- ...nfc_scene_mf_classic_keys_warn_duplicate.c | 26 +- .../nfc/scenes/nfc_scene_mf_classic_menu.c | 82 - .../nfc_scene_mf_classic_mfkey_complete.c | 58 + .../nfc_scene_mf_classic_mfkey_nonces_info.c | 72 + .../nfc_scene_mf_classic_read_success.c | 82 - .../nfc/scenes/nfc_scene_mf_classic_update.c | 98 - .../nfc_scene_mf_classic_update_initial.c | 144 + ..._scene_mf_classic_update_initial_success.c | 43 + .../nfc_scene_mf_classic_update_success.c | 44 - .../nfc/scenes/nfc_scene_mf_classic_write.c | 92 - .../scenes/nfc_scene_mf_classic_write_fail.c | 58 - .../nfc_scene_mf_classic_write_initial.c | 146 + .../nfc_scene_mf_classic_write_initial_fail.c | 62 + ...c_scene_mf_classic_write_initial_success.c | 43 + .../nfc_scene_mf_classic_write_success.c | 44 - .../scenes/nfc_scene_mf_classic_wrong_card.c | 28 +- .../nfc/scenes/nfc_scene_mf_desfire_app.c | 117 +- .../nfc/scenes/nfc_scene_mf_desfire_data.c | 104 - .../nfc/scenes/nfc_scene_mf_desfire_menu.c | 72 - .../scenes/nfc_scene_mf_desfire_more_info.c | 112 + .../nfc_scene_mf_desfire_read_success.c | 101 - .../nfc_scene_mf_ultralight_capture_pass.c | 67 + .../nfc/scenes/nfc_scene_mf_ultralight_data.c | 32 - .../scenes/nfc_scene_mf_ultralight_emulate.c | 74 - .../nfc_scene_mf_ultralight_key_input.c | 12 +- .../nfc/scenes/nfc_scene_mf_ultralight_menu.c | 89 - .../nfc_scene_mf_ultralight_read_auth.c | 116 - ...nfc_scene_mf_ultralight_read_auth_result.c | 116 - .../nfc_scene_mf_ultralight_read_success.c | 84 - .../nfc_scene_mf_ultralight_unlock_auto.c | 64 - .../nfc_scene_mf_ultralight_unlock_menu.c | 27 +- .../nfc_scene_mf_ultralight_unlock_warn.c | 45 +- .../nfc/scenes/nfc_scene_mfkey_complete.c | 49 - .../nfc/scenes/nfc_scene_mfkey_nonces_info.c | 55 - .../main/nfc/scenes/nfc_scene_more_info.c | 13 + .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 309 - .../main/nfc/scenes/nfc_scene_nfca_menu.c | 68 - .../nfc/scenes/nfc_scene_nfca_read_success.c | 73 - .../main/nfc/scenes/nfc_scene_nfcv_emulate.c | 169 - .../nfc/scenes/nfc_scene_nfcv_key_input.c | 48 - .../main/nfc/scenes/nfc_scene_nfcv_menu.c | 68 - .../nfc/scenes/nfc_scene_nfcv_read_success.c | 92 - .../main/nfc/scenes/nfc_scene_nfcv_sniff.c | 155 - .../main/nfc/scenes/nfc_scene_nfcv_unlock.c | 154 - .../nfc/scenes/nfc_scene_nfcv_unlock_menu.c | 60 - applications/main/nfc/scenes/nfc_scene_read.c | 118 +- .../nfc/scenes/nfc_scene_read_card_success.c | 61 - .../nfc/scenes/nfc_scene_read_card_type.c | 85 - .../main/nfc/scenes/nfc_scene_read_menu.c | 13 + .../main/nfc/scenes/nfc_scene_read_success.c | 13 + .../nfc/scenes/nfc_scene_restore_original.c | 14 +- .../nfc_scene_restore_original_confirm.c | 16 +- .../main/nfc/scenes/nfc_scene_retry_confirm.c | 19 +- applications/main/nfc/scenes/nfc_scene_rpc.c | 88 +- .../main/nfc/scenes/nfc_scene_save_name.c | 88 +- .../main/nfc/scenes/nfc_scene_save_success.c | 13 +- .../main/nfc/scenes/nfc_scene_saved_menu.c | 181 +- .../nfc/scenes/nfc_scene_select_protocol.c | 67 + .../main/nfc/scenes/nfc_scene_set_atqa.c | 37 +- .../main/nfc/scenes/nfc_scene_set_sak.c | 36 +- .../main/nfc/scenes/nfc_scene_set_type.c | 74 +- .../main/nfc/scenes/nfc_scene_set_uid.c | 53 +- .../main/nfc/scenes/nfc_scene_start.c | 29 +- .../nfc/scenes/nfc_scene_supported_card.c | 50 + applications/main/nfc/views/detect_reader.c | 5 +- applications/main/nfc/views/dict_attack.c | 234 +- applications/main/nfc/views/dict_attack.h | 48 +- .../scenes/desktop_scene_pin_timeout.c | 1 - assets/unit_tests/nfc/Ntag213_locked.nfc | 66 + assets/unit_tests/nfc/Ntag215.nfc | 156 + assets/unit_tests/nfc/Ntag216.nfc | 252 + assets/unit_tests/nfc/Ultralight_11.nfc | 41 + assets/unit_tests/nfc/Ultralight_21.nfc | 62 + documentation/file_formats/NfcFileFormats.md | 120 +- firmware.scons | 9 + firmware/targets/f18/api_symbols.csv | 109 +- firmware/targets/f18/target.json | 15 +- firmware/targets/f7/api_symbols.csv | 836 +- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 1252 +-- firmware/targets/f7/furi_hal/furi_hal_nfc.h | 431 - .../targets/f7/furi_hal/furi_hal_nfc_event.c | 116 + .../targets/f7/furi_hal/furi_hal_nfc_felica.c | 69 + firmware/targets/f7/furi_hal/furi_hal_nfc_i.h | 191 + .../targets/f7/furi_hal/furi_hal_nfc_irq.c | 28 + .../f7/furi_hal/furi_hal_nfc_iso14443a.c | 356 + .../f7/furi_hal/furi_hal_nfc_iso14443b.c | 108 + .../f7/furi_hal/furi_hal_nfc_iso15693.c | 463 + .../targets/f7/furi_hal/furi_hal_nfc_tech_i.h | 167 + .../targets/f7/furi_hal/furi_hal_nfc_timer.c | 229 + firmware/targets/f7/target.json | 5 +- .../targets/furi_hal_include/furi_hal_nfc.h | 457 + lib/ReadMe.md | 1 - lib/SConscript | 4 +- lib/ST25RFAL002/SConscript | 19 - lib/ST25RFAL002/doc/Release_Notes.html | 354 - .../doc/ST25R3916_MisraComplianceReport.html | 8867 ----------------- lib/ST25RFAL002/doc/_htmresc/st_logo.png | Bin 18616 -> 0 bytes lib/ST25RFAL002/doc/rfal.chm | Bin 2338282 -> 0 bytes lib/ST25RFAL002/include/rfal_analogConfig.h | 435 - lib/ST25RFAL002/include/rfal_chip.h | 287 - lib/ST25RFAL002/include/rfal_crc.h | 74 - lib/ST25RFAL002/include/rfal_dpo.h | 207 - lib/ST25RFAL002/include/rfal_iso15693_2.h | 206 - lib/ST25RFAL002/include/rfal_isoDep.h | 1092 -- lib/ST25RFAL002/include/rfal_nfc.h | 425 - lib/ST25RFAL002/include/rfal_nfcDep.h | 830 -- lib/ST25RFAL002/include/rfal_nfca.h | 497 - lib/ST25RFAL002/include/rfal_nfcb.h | 425 - lib/ST25RFAL002/include/rfal_nfcf.h | 403 - lib/ST25RFAL002/include/rfal_nfcv.h | 923 -- lib/ST25RFAL002/include/rfal_rf.h | 1722 ---- lib/ST25RFAL002/include/rfal_st25tb.h | 340 - lib/ST25RFAL002/include/rfal_st25xv.h | 844 -- lib/ST25RFAL002/include/rfal_t1t.h | 178 - lib/ST25RFAL002/include/rfal_t2t.h | 150 - lib/ST25RFAL002/include/rfal_t4t.h | 395 - lib/ST25RFAL002/platform.c | 103 - lib/ST25RFAL002/platform.h | 188 - lib/ST25RFAL002/source/custom_analog_config.c | 777 -- lib/ST25RFAL002/source/rfal_analogConfig.c | 476 - lib/ST25RFAL002/source/rfal_crc.c | 82 - lib/ST25RFAL002/source/rfal_dpo.c | 232 - lib/ST25RFAL002/source/rfal_iso15693_2.c | 514 - lib/ST25RFAL002/source/rfal_isoDep.c | 3032 ------ lib/ST25RFAL002/source/rfal_nfc.c | 2118 ---- lib/ST25RFAL002/source/rfal_nfcDep.c | 2730 ----- lib/ST25RFAL002/source/rfal_nfca.c | 886 -- lib/ST25RFAL002/source/rfal_nfcb.c | 519 - lib/ST25RFAL002/source/rfal_nfcf.c | 585 -- lib/ST25RFAL002/source/rfal_nfcv.c | 1057 -- lib/ST25RFAL002/source/rfal_st25tb.c | 563 -- lib/ST25RFAL002/source/rfal_st25xv.c | 818 -- lib/ST25RFAL002/source/rfal_t1t.c | 233 - lib/ST25RFAL002/source/rfal_t2t.c | 253 - lib/ST25RFAL002/source/rfal_t4t.c | 397 - .../source/st25r3916/rfal_analogConfigTbl.h | 1477 --- .../source/st25r3916/rfal_dpoTbl.h | 68 - .../source/st25r3916/rfal_features.h | 109 - .../source/st25r3916/rfal_rfst25r3916.c | 4815 --------- lib/ST25RFAL002/source/st25r3916/st25r3916.c | 792 -- lib/ST25RFAL002/source/st25r3916/st25r3916.h | 669 -- .../source/st25r3916/st25r3916_aat.c | 366 - .../source/st25r3916/st25r3916_aat.h | 109 - .../source/st25r3916/st25r3916_com.c | 618 -- .../source/st25r3916/st25r3916_irq.c | 231 - .../source/st25r3916/st25r3916_irq.h | 296 - .../source/st25r3916/st25r3916_led.c | 148 - .../source/st25r3916/st25r3916_led.h | 151 - lib/ST25RFAL002/st_errno.h | 158 - lib/ST25RFAL002/timer.c | 105 - lib/ST25RFAL002/timer.h | 125 - lib/ST25RFAL002/utils.h | 100 - lib/digital_signal/SConscript | 1 + lib/digital_signal/digital_sequence.c | 381 + lib/digital_signal/digital_sequence.h | 112 + lib/digital_signal/digital_signal.c | 655 +- lib/digital_signal/digital_signal.h | 157 +- lib/digital_signal/digital_signal_i.h | 23 + .../presets/nfc/iso14443_3a_signal.c | 139 + .../presets/nfc/iso14443_3a_signal.h | 51 + .../presets/nfc/iso15693_signal.c | 204 + .../presets/nfc/iso15693_signal.h | 73 + lib/drivers/st25r3916.c | 81 + lib/drivers/st25r3916.h | 113 + lib/drivers/st25r3916_reg.c | 257 + .../st25r3916_reg.h} | 826 +- lib/misc.scons | 3 + lib/nfc/SConscript | 47 +- lib/nfc/helpers/felica_crc.c | 52 + lib/nfc/helpers/felica_crc.h | 22 + lib/nfc/helpers/iso13239_crc.c | 62 + lib/nfc/helpers/iso13239_crc.h | 27 + lib/nfc/helpers/iso14443_4_layer.c | 64 + lib/nfc/helpers/iso14443_4_layer.h | 29 + lib/nfc/helpers/iso14443_crc.c | 57 + lib/nfc/helpers/iso14443_crc.h | 27 + lib/nfc/helpers/mf_classic_dict.c | 346 - lib/nfc/helpers/mf_classic_dict.h | 107 - lib/nfc/helpers/mfkey32.c | 228 - lib/nfc/helpers/mfkey32.h | 34 - lib/nfc/helpers/nfc_data_generator.c | 566 ++ lib/nfc/helpers/nfc_data_generator.h | 40 + lib/nfc/helpers/nfc_debug_log.c | 71 - lib/nfc/helpers/nfc_debug_log.h | 17 - lib/nfc/helpers/nfc_debug_pcap.c | 130 - lib/nfc/helpers/nfc_debug_pcap.h | 17 - lib/nfc/helpers/nfc_dict.c | 270 + lib/nfc/helpers/nfc_dict.h | 103 + lib/nfc/helpers/nfc_generators.c | 528 - lib/nfc/helpers/nfc_generators.h | 14 - lib/nfc/{protocols => helpers}/nfc_util.c | 0 lib/nfc/{protocols => helpers}/nfc_util.h | 0 lib/nfc/helpers/reader_analyzer.c | 265 - lib/nfc/helpers/reader_analyzer.h | 43 - lib/nfc/nfc.c | 649 ++ lib/nfc/nfc.h | 364 + lib/nfc/nfc_common.h | 22 + lib/nfc/nfc_device.c | 1833 +--- lib/nfc/nfc_device.h | 333 +- lib/nfc/nfc_device_i.c | 37 + lib/nfc/nfc_device_i.h | 46 + lib/nfc/nfc_listener.c | 144 + lib/nfc/nfc_listener.h | 94 + lib/nfc/nfc_poller.c | 211 + lib/nfc/nfc_poller.h | 104 + lib/nfc/nfc_scanner.c | 267 + lib/nfc/nfc_scanner.h | 97 + lib/nfc/nfc_types.c | 69 - lib/nfc/nfc_types.h | 19 - lib/nfc/nfc_worker.c | 1325 --- lib/nfc/nfc_worker.h | 108 - lib/nfc/nfc_worker_i.h | 59 - lib/nfc/parsers/all_in_one.c | 103 - lib/nfc/parsers/all_in_one.h | 9 - lib/nfc/parsers/nfc_supported_card.c | 82 - lib/nfc/parsers/nfc_supported_card.h | 47 - lib/nfc/parsers/opal.c | 204 - lib/nfc/parsers/opal.h | 5 - lib/nfc/parsers/plantain_4k_parser.c | 124 - lib/nfc/parsers/plantain_4k_parser.h | 9 - lib/nfc/parsers/plantain_parser.c | 97 - lib/nfc/parsers/plantain_parser.h | 11 - lib/nfc/parsers/troika_4k_parser.c | 106 - lib/nfc/parsers/troika_4k_parser.h | 9 - lib/nfc/parsers/troika_parser.c | 88 - lib/nfc/parsers/troika_parser.h | 9 - lib/nfc/parsers/two_cities.c | 146 - lib/nfc/parsers/two_cities.h | 9 - lib/nfc/protocols/crypto1.c | 128 - lib/nfc/protocols/emv.c | 444 - lib/nfc/protocols/emv.h | 80 - lib/nfc/protocols/felica/felica.c | 147 + lib/nfc/protocols/felica/felica.h | 77 + lib/nfc/protocols/felica/felica_poller.c | 117 + lib/nfc/protocols/felica/felica_poller.h | 30 + lib/nfc/protocols/felica/felica_poller_defs.h | 13 + lib/nfc/protocols/felica/felica_poller_i.c | 128 + lib/nfc/protocols/felica/felica_poller_i.h | 60 + lib/nfc/protocols/iso14443_3a/iso14443_3a.c | 186 + lib/nfc/protocols/iso14443_3a/iso14443_3a.h | 107 + .../iso14443_3a/iso14443_3a_device_defs.h | 5 + .../iso14443_3a/iso14443_3a_listener.c | 127 + .../iso14443_3a/iso14443_3a_listener.h | 31 + .../iso14443_3a/iso14443_3a_listener_defs.h | 5 + .../iso14443_3a/iso14443_3a_listener_i.c | 73 + .../iso14443_3a/iso14443_3a_listener_i.h | 42 + .../iso14443_3a/iso14443_3a_poller.c | 128 + .../iso14443_3a/iso14443_3a_poller.h | 42 + .../iso14443_3a/iso14443_3a_poller_defs.h | 5 + .../iso14443_3a/iso14443_3a_poller_i.c | 293 + .../iso14443_3a/iso14443_3a_poller_i.h | 81 + .../iso14443_3a/iso14443_3a_poller_sync_api.c | 58 + .../iso14443_3a/iso14443_3a_poller_sync_api.h | 14 + lib/nfc/protocols/iso14443_3b/iso14443_3b.c | 223 + lib/nfc/protocols/iso14443_3b/iso14443_3b.h | 83 + .../iso14443_3b/iso14443_3b_device_defs.h | 5 + lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c | 1 + lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h | 37 + .../iso14443_3b/iso14443_3b_poller.c | 121 + .../iso14443_3b/iso14443_3b_poller.h | 30 + .../iso14443_3b/iso14443_3b_poller_defs.h | 5 + .../iso14443_3b/iso14443_3b_poller_i.c | 194 + .../iso14443_3b/iso14443_3b_poller_i.h | 49 + lib/nfc/protocols/iso14443_4a/iso14443_4a.c | 300 + lib/nfc/protocols/iso14443_4a/iso14443_4a.h | 73 + .../iso14443_4a/iso14443_4a_device_defs.h | 5 + lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c | 71 + lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h | 42 + .../iso14443_4a/iso14443_4a_listener.c | 99 + .../iso14443_4a/iso14443_4a_listener.h | 29 + .../iso14443_4a/iso14443_4a_listener_defs.h | 5 + .../iso14443_4a/iso14443_4a_listener_i.c | 32 + .../iso14443_4a/iso14443_4a_listener_i.h | 36 + .../iso14443_4a/iso14443_4a_poller.c | 154 + .../iso14443_4a/iso14443_4a_poller.h | 29 + .../iso14443_4a/iso14443_4a_poller_defs.h | 5 + .../iso14443_4a/iso14443_4a_poller_i.c | 83 + .../iso14443_4a/iso14443_4a_poller_i.h | 62 + lib/nfc/protocols/iso14443_4b/iso14443_4b.c | 94 + lib/nfc/protocols/iso14443_4b/iso14443_4b.h | 46 + .../iso14443_4b/iso14443_4b_device_defs.h | 5 + lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c | 18 + lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h | 9 + .../iso14443_4b/iso14443_4b_poller.c | 138 + .../iso14443_4b/iso14443_4b_poller.h | 29 + .../iso14443_4b/iso14443_4b_poller_defs.h | 5 + .../iso14443_4b/iso14443_4b_poller_i.c | 45 + .../iso14443_4b/iso14443_4b_poller_i.h | 56 + lib/nfc/protocols/iso15693_3/iso15693_3.c | 358 + lib/nfc/protocols/iso15693_3/iso15693_3.h | 163 + .../iso15693_3/iso15693_3_device_defs.h | 5 + lib/nfc/protocols/iso15693_3/iso15693_3_i.c | 263 + lib/nfc/protocols/iso15693_3/iso15693_3_i.h | 58 + .../iso15693_3/iso15693_3_listener.c | 110 + .../iso15693_3/iso15693_3_listener.h | 30 + .../iso15693_3/iso15693_3_listener_defs.h | 5 + .../iso15693_3/iso15693_3_listener_i.c | 883 ++ .../iso15693_3/iso15693_3_listener_i.h | 70 + .../protocols/iso15693_3/iso15693_3_poller.c | 122 + .../protocols/iso15693_3/iso15693_3_poller.h | 29 + .../iso15693_3/iso15693_3_poller_defs.h | 5 + .../iso15693_3/iso15693_3_poller_i.c | 303 + .../iso15693_3/iso15693_3_poller_i.h | 70 + lib/nfc/protocols/mf_classic/crypto1.c | 172 + lib/nfc/protocols/{ => mf_classic}/crypto1.h | 31 +- lib/nfc/protocols/mf_classic/mf_classic.c | 740 ++ lib/nfc/protocols/mf_classic/mf_classic.h | 235 + .../mf_classic/mf_classic_listener.c | 656 ++ .../mf_classic/mf_classic_listener.h | 27 + .../mf_classic/mf_classic_listener_defs.h | 13 + .../mf_classic/mf_classic_listener_i.h | 62 + .../protocols/mf_classic/mf_classic_poller.c | 951 ++ .../protocols/mf_classic/mf_classic_poller.h | 109 + .../mf_classic/mf_classic_poller_defs.h | 13 + .../mf_classic/mf_classic_poller_i.c | 386 + .../mf_classic/mf_classic_poller_i.h | 205 + .../mf_classic/mf_classic_poller_sync_api.c | 524 + .../mf_classic/mf_classic_poller_sync_api.h | 59 + lib/nfc/protocols/mf_desfire/mf_desfire.c | 290 + lib/nfc/protocols/mf_desfire/mf_desfire.h | 191 + lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 790 ++ lib/nfc/protocols/mf_desfire/mf_desfire_i.h | 140 + .../protocols/mf_desfire/mf_desfire_poller.c | 243 + .../protocols/mf_desfire/mf_desfire_poller.h | 31 + .../mf_desfire/mf_desfire_poller_defs.h | 5 + .../mf_desfire/mf_desfire_poller_i.c | 474 + .../mf_desfire/mf_desfire_poller_i.h | 126 + .../protocols/mf_ultralight/mf_ultralight.c | 627 ++ .../protocols/mf_ultralight/mf_ultralight.h | 229 + .../mf_ultralight/mf_ultralight_listener.c | 772 ++ .../mf_ultralight/mf_ultralight_listener.h | 28 + .../mf_ultralight_listener_defs.h | 13 + .../mf_ultralight/mf_ultralight_listener_i.c | 577 ++ .../mf_ultralight/mf_ultralight_listener_i.h | 123 + .../mf_ultralight/mf_ultralight_poller.c | 591 ++ .../mf_ultralight/mf_ultralight_poller.h | 41 + .../mf_ultralight/mf_ultralight_poller_defs.h | 5 + .../mf_ultralight/mf_ultralight_poller_i.c | 308 + .../mf_ultralight/mf_ultralight_poller_i.h | 148 + .../mf_ultralight_poller_sync_api.c | 287 + .../mf_ultralight_poller_sync_api.h | 30 + lib/nfc/protocols/mifare_classic.c | 1626 --- lib/nfc/protocols/mifare_classic.h | 251 - lib/nfc/protocols/mifare_common.c | 18 - lib/nfc/protocols/mifare_common.h | 12 - lib/nfc/protocols/mifare_desfire.c | 665 -- lib/nfc/protocols/mifare_desfire.h | 179 - lib/nfc/protocols/mifare_ultralight.c | 1946 ---- lib/nfc/protocols/mifare_ultralight.h | 269 - lib/nfc/protocols/nfc_device_base.h | 26 + lib/nfc/protocols/nfc_device_base_i.h | 161 + lib/nfc/protocols/nfc_device_defs.c | 46 + lib/nfc/protocols/nfc_device_defs.h | 13 + lib/nfc/protocols/nfc_generic_event.h | 79 + lib/nfc/protocols/nfc_listener_base.h | 98 + lib/nfc/protocols/nfc_listener_defs.c | 21 + lib/nfc/protocols/nfc_listener_defs.h | 14 + lib/nfc/protocols/nfc_poller_base.h | 132 + lib/nfc/protocols/nfc_poller_defs.c | 28 + lib/nfc/protocols/nfc_poller_defs.h | 14 + lib/nfc/protocols/nfc_protocol.c | 174 + lib/nfc/protocols/nfc_protocol.h | 219 + lib/nfc/protocols/nfca.c | 140 - lib/nfc/protocols/nfca.h | 31 - lib/nfc/protocols/nfcv.c | 1438 --- lib/nfc/protocols/nfcv.h | 334 - lib/nfc/protocols/slix.c | 784 -- lib/nfc/protocols/slix.h | 66 - lib/nfc/protocols/slix/slix.c | 433 + lib/nfc/protocols/slix/slix.h | 146 + lib/nfc/protocols/slix/slix_device_defs.h | 5 + lib/nfc/protocols/slix/slix_i.c | 127 + lib/nfc/protocols/slix/slix_i.h | 86 + lib/nfc/protocols/slix/slix_listener.c | 79 + lib/nfc/protocols/slix/slix_listener.h | 29 + lib/nfc/protocols/slix/slix_listener_defs.h | 5 + lib/nfc/protocols/slix/slix_listener_i.c | 617 ++ lib/nfc/protocols/slix/slix_listener_i.h | 37 + lib/nfc/protocols/slix/slix_poller.c | 159 + lib/nfc/protocols/slix/slix_poller.h | 29 + lib/nfc/protocols/slix/slix_poller_defs.h | 5 + lib/nfc/protocols/slix/slix_poller_i.c | 69 + lib/nfc/protocols/slix/slix_poller_i.h | 48 + lib/nfc/protocols/st25tb/st25tb.c | 234 + lib/nfc/protocols/st25tb/st25tb.h | 80 + lib/nfc/protocols/st25tb/st25tb_poller.c | 123 + lib/nfc/protocols/st25tb/st25tb_poller.h | 30 + lib/nfc/protocols/st25tb/st25tb_poller_defs.h | 13 + lib/nfc/protocols/st25tb/st25tb_poller_i.c | 297 + lib/nfc/protocols/st25tb/st25tb_poller_i.h | 56 + lib/signal_reader/SConscript | 20 + .../parsers/iso15693/iso15693_parser.c | 300 + .../parsers/iso15693/iso15693_parser.h | 42 + lib/signal_reader/signal_reader.c | 317 + lib/signal_reader/signal_reader.h | 67 + lib/subghz/protocols/bin_raw.c | 2 + lib/toolbox/SConscript | 2 + lib/toolbox/bit_buffer.c | 336 + lib/toolbox/bit_buffer.h | 325 + lib/toolbox/simple_array.c | 127 + lib/toolbox/simple_array.h | 148 + 514 files changed, 41333 insertions(+), 67970 deletions(-) create mode 100644 applications/debug/unit_tests/nfc/nfc_transport.c create mode 100644 applications/main/nfc/helpers/mf_classic_key_cache.c create mode 100644 applications/main/nfc/helpers/mf_classic_key_cache.h create mode 100644 applications/main/nfc/helpers/mf_ultralight_auth.c create mode 100644 applications/main/nfc/helpers/mf_ultralight_auth.h create mode 100644 applications/main/nfc/helpers/mf_user_dict.c create mode 100644 applications/main/nfc/helpers/mf_user_dict.h create mode 100644 applications/main/nfc/helpers/mfkey32_logger.c create mode 100644 applications/main/nfc/helpers/mfkey32_logger.h create mode 100644 applications/main/nfc/helpers/nfc_supported_cards.c create mode 100644 applications/main/nfc/helpers/nfc_supported_cards.h create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica.c create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica.h create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix.c create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix.h create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h delete mode 100644 applications/main/nfc/nfc.c delete mode 100644 applications/main/nfc/nfc.h create mode 100644 applications/main/nfc/nfc_app.c create mode 100644 applications/main/nfc/nfc_app.h create mode 100644 applications/main/nfc/nfc_app_i.h delete mode 100644 applications/main/nfc/nfc_i.h create mode 100644 applications/main/nfc/plugins/supported_cards/all_in_one.c create mode 100644 applications/main/nfc/plugins/supported_cards/myki.c create mode 100644 applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h create mode 100644 applications/main/nfc/plugins/supported_cards/opal.c create mode 100644 applications/main/nfc/plugins/supported_cards/plantain.c create mode 100644 applications/main/nfc/plugins/supported_cards/troika.c create mode 100644 applications/main/nfc/plugins/supported_cards/two_cities.c create mode 100644 applications/main/nfc/scenes/nfc_scene_detect.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_detect_reader.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_device_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_dict_not_found.c create mode 100644 applications/main/nfc/scenes/nfc_scene_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emulate_uid.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emv_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emv_read_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_data.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mfkey_complete.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c create mode 100644 applications/main/nfc/scenes/nfc_scene_more_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfc_data_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfca_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfca_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_read_card_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_read_card_type.c create mode 100644 applications/main/nfc/scenes/nfc_scene_read_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_read_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_select_protocol.c create mode 100644 applications/main/nfc/scenes/nfc_scene_supported_card.c create mode 100644 assets/unit_tests/nfc/Ntag213_locked.nfc create mode 100644 assets/unit_tests/nfc/Ntag215.nfc create mode 100644 assets/unit_tests/nfc/Ntag216.nfc create mode 100644 assets/unit_tests/nfc/Ultralight_11.nfc create mode 100644 assets/unit_tests/nfc/Ultralight_21.nfc delete mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_event.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_i.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c create mode 100644 firmware/targets/furi_hal_include/furi_hal_nfc.h delete mode 100644 lib/ST25RFAL002/SConscript delete mode 100755 lib/ST25RFAL002/doc/Release_Notes.html delete mode 100755 lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html delete mode 100755 lib/ST25RFAL002/doc/_htmresc/st_logo.png delete mode 100755 lib/ST25RFAL002/doc/rfal.chm delete mode 100644 lib/ST25RFAL002/include/rfal_analogConfig.h delete mode 100644 lib/ST25RFAL002/include/rfal_chip.h delete mode 100644 lib/ST25RFAL002/include/rfal_crc.h delete mode 100644 lib/ST25RFAL002/include/rfal_dpo.h delete mode 100644 lib/ST25RFAL002/include/rfal_iso15693_2.h delete mode 100644 lib/ST25RFAL002/include/rfal_isoDep.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfc.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcDep.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfca.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcb.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcf.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcv.h delete mode 100644 lib/ST25RFAL002/include/rfal_rf.h delete mode 100644 lib/ST25RFAL002/include/rfal_st25tb.h delete mode 100644 lib/ST25RFAL002/include/rfal_st25xv.h delete mode 100644 lib/ST25RFAL002/include/rfal_t1t.h delete mode 100644 lib/ST25RFAL002/include/rfal_t2t.h delete mode 100644 lib/ST25RFAL002/include/rfal_t4t.h delete mode 100644 lib/ST25RFAL002/platform.c delete mode 100644 lib/ST25RFAL002/platform.h delete mode 100644 lib/ST25RFAL002/source/custom_analog_config.c delete mode 100644 lib/ST25RFAL002/source/rfal_analogConfig.c delete mode 100644 lib/ST25RFAL002/source/rfal_crc.c delete mode 100644 lib/ST25RFAL002/source/rfal_dpo.c delete mode 100644 lib/ST25RFAL002/source/rfal_iso15693_2.c delete mode 100644 lib/ST25RFAL002/source/rfal_isoDep.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfc.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcDep.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfca.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcb.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcf.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcv.c delete mode 100644 lib/ST25RFAL002/source/rfal_st25tb.c delete mode 100644 lib/ST25RFAL002/source/rfal_st25xv.c delete mode 100644 lib/ST25RFAL002/source/rfal_t1t.c delete mode 100644 lib/ST25RFAL002/source/rfal_t2t.c delete mode 100644 lib/ST25RFAL002/source/rfal_t4t.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_features.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_com.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_led.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_led.h delete mode 100644 lib/ST25RFAL002/st_errno.h delete mode 100644 lib/ST25RFAL002/timer.c delete mode 100644 lib/ST25RFAL002/timer.h delete mode 100644 lib/ST25RFAL002/utils.h create mode 100644 lib/digital_signal/digital_sequence.c create mode 100644 lib/digital_signal/digital_sequence.h create mode 100644 lib/digital_signal/digital_signal_i.h create mode 100644 lib/digital_signal/presets/nfc/iso14443_3a_signal.c create mode 100644 lib/digital_signal/presets/nfc/iso14443_3a_signal.h create mode 100644 lib/digital_signal/presets/nfc/iso15693_signal.c create mode 100644 lib/digital_signal/presets/nfc/iso15693_signal.h create mode 100644 lib/drivers/st25r3916.c create mode 100644 lib/drivers/st25r3916.h create mode 100644 lib/drivers/st25r3916_reg.c rename lib/{ST25RFAL002/source/st25r3916/st25r3916_com.h => drivers/st25r3916_reg.h} (62%) create mode 100644 lib/nfc/helpers/felica_crc.c create mode 100644 lib/nfc/helpers/felica_crc.h create mode 100644 lib/nfc/helpers/iso13239_crc.c create mode 100644 lib/nfc/helpers/iso13239_crc.h create mode 100644 lib/nfc/helpers/iso14443_4_layer.c create mode 100644 lib/nfc/helpers/iso14443_4_layer.h create mode 100644 lib/nfc/helpers/iso14443_crc.c create mode 100644 lib/nfc/helpers/iso14443_crc.h delete mode 100644 lib/nfc/helpers/mf_classic_dict.c delete mode 100644 lib/nfc/helpers/mf_classic_dict.h delete mode 100644 lib/nfc/helpers/mfkey32.c delete mode 100644 lib/nfc/helpers/mfkey32.h create mode 100644 lib/nfc/helpers/nfc_data_generator.c create mode 100644 lib/nfc/helpers/nfc_data_generator.h delete mode 100644 lib/nfc/helpers/nfc_debug_log.c delete mode 100644 lib/nfc/helpers/nfc_debug_log.h delete mode 100644 lib/nfc/helpers/nfc_debug_pcap.c delete mode 100644 lib/nfc/helpers/nfc_debug_pcap.h create mode 100644 lib/nfc/helpers/nfc_dict.c create mode 100644 lib/nfc/helpers/nfc_dict.h delete mode 100644 lib/nfc/helpers/nfc_generators.c delete mode 100644 lib/nfc/helpers/nfc_generators.h rename lib/nfc/{protocols => helpers}/nfc_util.c (100%) rename lib/nfc/{protocols => helpers}/nfc_util.h (100%) delete mode 100644 lib/nfc/helpers/reader_analyzer.c delete mode 100644 lib/nfc/helpers/reader_analyzer.h create mode 100644 lib/nfc/nfc.c create mode 100644 lib/nfc/nfc.h create mode 100644 lib/nfc/nfc_common.h create mode 100644 lib/nfc/nfc_device_i.c create mode 100644 lib/nfc/nfc_device_i.h create mode 100644 lib/nfc/nfc_listener.c create mode 100644 lib/nfc/nfc_listener.h create mode 100644 lib/nfc/nfc_poller.c create mode 100644 lib/nfc/nfc_poller.h create mode 100644 lib/nfc/nfc_scanner.c create mode 100644 lib/nfc/nfc_scanner.h delete mode 100644 lib/nfc/nfc_types.c delete mode 100644 lib/nfc/nfc_types.h delete mode 100644 lib/nfc/nfc_worker.c delete mode 100644 lib/nfc/nfc_worker.h delete mode 100644 lib/nfc/nfc_worker_i.h delete mode 100644 lib/nfc/parsers/all_in_one.c delete mode 100644 lib/nfc/parsers/all_in_one.h delete mode 100644 lib/nfc/parsers/nfc_supported_card.c delete mode 100644 lib/nfc/parsers/nfc_supported_card.h delete mode 100644 lib/nfc/parsers/opal.c delete mode 100644 lib/nfc/parsers/opal.h delete mode 100644 lib/nfc/parsers/plantain_4k_parser.c delete mode 100644 lib/nfc/parsers/plantain_4k_parser.h delete mode 100644 lib/nfc/parsers/plantain_parser.c delete mode 100644 lib/nfc/parsers/plantain_parser.h delete mode 100644 lib/nfc/parsers/troika_4k_parser.c delete mode 100644 lib/nfc/parsers/troika_4k_parser.h delete mode 100644 lib/nfc/parsers/troika_parser.c delete mode 100644 lib/nfc/parsers/troika_parser.h delete mode 100644 lib/nfc/parsers/two_cities.c delete mode 100644 lib/nfc/parsers/two_cities.h delete mode 100644 lib/nfc/protocols/crypto1.c delete mode 100644 lib/nfc/protocols/emv.c delete mode 100644 lib/nfc/protocols/emv.h create mode 100644 lib/nfc/protocols/felica/felica.c create mode 100644 lib/nfc/protocols/felica/felica.h create mode 100644 lib/nfc/protocols/felica/felica_poller.c create mode 100644 lib/nfc/protocols/felica/felica_poller.h create mode 100644 lib/nfc/protocols/felica/felica_poller_defs.h create mode 100644 lib/nfc/protocols/felica/felica_poller_i.c create mode 100644 lib/nfc/protocols/felica/felica_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_i.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_i.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h create mode 100644 lib/nfc/protocols/mf_classic/crypto1.c rename lib/nfc/protocols/{ => mf_classic}/crypto1.h (56%) create mode 100644 lib/nfc/protocols/mf_classic/mf_classic.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener_i.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_i.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_i.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_i.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_i.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h delete mode 100644 lib/nfc/protocols/mifare_classic.c delete mode 100644 lib/nfc/protocols/mifare_classic.h delete mode 100644 lib/nfc/protocols/mifare_common.c delete mode 100644 lib/nfc/protocols/mifare_common.h delete mode 100644 lib/nfc/protocols/mifare_desfire.c delete mode 100644 lib/nfc/protocols/mifare_desfire.h delete mode 100644 lib/nfc/protocols/mifare_ultralight.c delete mode 100644 lib/nfc/protocols/mifare_ultralight.h create mode 100644 lib/nfc/protocols/nfc_device_base.h create mode 100644 lib/nfc/protocols/nfc_device_base_i.h create mode 100644 lib/nfc/protocols/nfc_device_defs.c create mode 100644 lib/nfc/protocols/nfc_device_defs.h create mode 100644 lib/nfc/protocols/nfc_generic_event.h create mode 100644 lib/nfc/protocols/nfc_listener_base.h create mode 100644 lib/nfc/protocols/nfc_listener_defs.c create mode 100644 lib/nfc/protocols/nfc_listener_defs.h create mode 100644 lib/nfc/protocols/nfc_poller_base.h create mode 100644 lib/nfc/protocols/nfc_poller_defs.c create mode 100644 lib/nfc/protocols/nfc_poller_defs.h create mode 100644 lib/nfc/protocols/nfc_protocol.c create mode 100644 lib/nfc/protocols/nfc_protocol.h delete mode 100644 lib/nfc/protocols/nfca.c delete mode 100644 lib/nfc/protocols/nfca.h delete mode 100644 lib/nfc/protocols/nfcv.c delete mode 100644 lib/nfc/protocols/nfcv.h delete mode 100644 lib/nfc/protocols/slix.c delete mode 100644 lib/nfc/protocols/slix.h create mode 100644 lib/nfc/protocols/slix/slix.c create mode 100644 lib/nfc/protocols/slix/slix.h create mode 100644 lib/nfc/protocols/slix/slix_device_defs.h create mode 100644 lib/nfc/protocols/slix/slix_i.c create mode 100644 lib/nfc/protocols/slix/slix_i.h create mode 100644 lib/nfc/protocols/slix/slix_listener.c create mode 100644 lib/nfc/protocols/slix/slix_listener.h create mode 100644 lib/nfc/protocols/slix/slix_listener_defs.h create mode 100644 lib/nfc/protocols/slix/slix_listener_i.c create mode 100644 lib/nfc/protocols/slix/slix_listener_i.h create mode 100644 lib/nfc/protocols/slix/slix_poller.c create mode 100644 lib/nfc/protocols/slix/slix_poller.h create mode 100644 lib/nfc/protocols/slix/slix_poller_defs.h create mode 100644 lib/nfc/protocols/slix/slix_poller_i.c create mode 100644 lib/nfc/protocols/slix/slix_poller_i.h create mode 100644 lib/nfc/protocols/st25tb/st25tb.c create mode 100644 lib/nfc/protocols/st25tb/st25tb.h create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller.c create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller.h create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_defs.h create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_i.c create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_i.h create mode 100644 lib/signal_reader/SConscript create mode 100644 lib/signal_reader/parsers/iso15693/iso15693_parser.c create mode 100644 lib/signal_reader/parsers/iso15693/iso15693_parser.h create mode 100644 lib/signal_reader/signal_reader.c create mode 100644 lib/signal_reader/signal_reader.h create mode 100644 lib/toolbox/bit_buffer.c create mode 100644 lib/toolbox/bit_buffer.h create mode 100644 lib/toolbox/simple_array.c create mode 100644 lib/toolbox/simple_array.h diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6626f9ae2a..e02c931af9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -52,7 +52,6 @@ /scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya # Lib -/lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich /lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gornekich /lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 35085ba57a..2680f520c1 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -32,7 +32,7 @@ jobs: if: success() timeout-minutes: 10 run: | - ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' id: format_ext @@ -66,4 +66,4 @@ jobs: - name: 'Check GDB output' if: failure() run: | - ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 diff --git a/.pvsoptions b/.pvsoptions index 2e0b2fb8d2..0312180924 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -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/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/* diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index bc2f7887b1..e7406fab8a 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -1,52 +1,35 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include "../minunit.h" #define TAG "NfcTest" -#define NFC_TEST_RESOURCES_DIR EXT_PATH("unit_tests/nfc/") -#define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc" -#define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc" -#define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") -#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_dev_test.nfc") - -static const char* nfc_test_file_type = "Flipper NFC test"; -static const uint32_t nfc_test_file_version = 1; - -#define NFC_TEST_DATA_MAX_LEN 18 -#define NFC_TETS_TIMINGS_MAX_LEN 1350 - -// Maximum allowed time for buffer preparation to fit 500us nt message timeout -#define NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX (150) -#define NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX (640) -#define NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX (110) -#define NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX (440) +#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc") +#define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc") typedef struct { Storage* storage; - NfcaSignal* signal; - uint32_t test_data_len; - uint8_t test_data[NFC_TEST_DATA_MAX_LEN]; - uint32_t test_timings_len; - uint32_t test_timings[NFC_TETS_TIMINGS_MAX_LEN]; } NfcTest; static NfcTest* nfc_test = NULL; static void nfc_test_alloc() { nfc_test = malloc(sizeof(NfcTest)); - nfc_test->signal = nfca_signal_alloc(); nfc_test->storage = furi_record_open(RECORD_STORAGE); } @@ -54,517 +37,500 @@ static void nfc_test_free() { furi_assert(nfc_test); furi_record_close(RECORD_STORAGE); - nfca_signal_free(nfc_test->signal); free(nfc_test); nfc_test = NULL; } -static bool nfc_test_read_signal_from_file(const char* file_name) { - bool success = false; - - FlipperFormat* file = flipper_format_file_alloc(nfc_test->storage); - FuriString* file_type; - file_type = furi_string_alloc(); - uint32_t file_version = 0; - - do { - if(!flipper_format_file_open_existing(file, file_name)) break; - if(!flipper_format_read_header(file, file_type, &file_version)) break; - if(furi_string_cmp_str(file_type, nfc_test_file_type) || - file_version != nfc_test_file_version) - break; - if(!flipper_format_read_uint32(file, "Data length", &nfc_test->test_data_len, 1)) break; - if(nfc_test->test_data_len > NFC_TEST_DATA_MAX_LEN) break; - if(!flipper_format_read_hex( - file, "Plain data", nfc_test->test_data, nfc_test->test_data_len)) - break; - if(!flipper_format_read_uint32(file, "Timings length", &nfc_test->test_timings_len, 1)) - break; - if(nfc_test->test_timings_len > NFC_TETS_TIMINGS_MAX_LEN) break; - if(!flipper_format_read_uint32( - file, "Timings", nfc_test->test_timings, nfc_test->test_timings_len)) - break; - success = true; - } while(false); - - furi_string_free(file_type); - flipper_format_free(file); - - return success; -} - -static bool nfc_test_digital_signal_test_encode( - const char* file_name, - uint32_t build_signal_max_time_us, - uint32_t build_buffer_max_time_us, - uint32_t timing_tolerance, - uint32_t timings_sum_tolerance) { - furi_assert(nfc_test); +static void nfc_test_save_and_load(NfcDevice* nfc_device_ref) { + NfcDevice* nfc_device_dut = nfc_device_alloc(); - bool success = false; - uint32_t dut_timings_sum = 0; - uint32_t ref_timings_sum = 0; - uint8_t parity[10] = {}; - - do { - // Read test data - if(!nfc_test_read_signal_from_file(file_name)) { - FURI_LOG_E(TAG, "Failed to read signal from file"); - break; - } - - // Encode signal - FURI_CRITICAL_ENTER(); - uint32_t time_start = DWT->CYCCNT; - - nfca_signal_encode( - nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity); - - uint32_t time_signal = - (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); - - time_start = DWT->CYCCNT; - - digital_signal_prepare_arr(nfc_test->signal->tx_signal); - - uint32_t time_buffer = - (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); - FURI_CRITICAL_EXIT(); - - // Check timings - if(time_signal > build_signal_max_time_us) { - FURI_LOG_E( - TAG, - "Build signal time: %ld us while accepted value: %ld us", - time_signal, - build_signal_max_time_us); - break; - } - if(time_buffer > build_buffer_max_time_us) { - FURI_LOG_E( - TAG, - "Build buffer time: %ld us while accepted value: %ld us", - time_buffer, - build_buffer_max_time_us); - break; - } - - // Check data - if(nfc_test->signal->tx_signal->edge_cnt != nfc_test->test_timings_len) { - FURI_LOG_E(TAG, "Not equal timings buffers length"); - break; - } - - uint32_t timings_diff = 0; - uint32_t* ref = nfc_test->test_timings; - uint32_t* dut = nfc_test->signal->tx_signal->reload_reg_buff; - bool timing_check_success = true; - for(size_t i = 0; i < nfc_test->test_timings_len; i++) { - timings_diff = dut[i] > ref[i] ? dut[i] - ref[i] : ref[i] - dut[i]; - dut_timings_sum += dut[i]; - ref_timings_sum += ref[i]; - if(timings_diff > timing_tolerance) { - FURI_LOG_E( - TAG, "Too big difference in %d timings. Ref: %ld, DUT: %ld", i, ref[i], dut[i]); - timing_check_success = false; - break; - } - } - if(!timing_check_success) break; - uint32_t sum_diff = dut_timings_sum > ref_timings_sum ? dut_timings_sum - ref_timings_sum : - ref_timings_sum - dut_timings_sum; - if(sum_diff > timings_sum_tolerance) { - FURI_LOG_E( - TAG, - "Too big difference in timings sum. Ref: %ld, DUT: %ld", - ref_timings_sum, - dut_timings_sum); - break; - } - - FURI_LOG_I( - TAG, - "Build signal time: %ld us. Acceptable time: %ld us", - time_signal, - build_signal_max_time_us); - FURI_LOG_I( - TAG, - "Build buffer time: %ld us. Acceptable time: %ld us", - time_buffer, - build_buffer_max_time_us); - FURI_LOG_I( - TAG, - "Timings sum difference: %ld [1/64MHZ]. Acceptable difference: %ld [1/64MHz]", - sum_diff, - timings_sum_tolerance); - success = true; - } while(false); - - return success; -} - -MU_TEST(nfc_digital_signal_test) { mu_assert( - nfc_test_digital_signal_test_encode( - NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, - NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX, - NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX, - 1, - 37), - "NFC short digital signal test failed\r\n"); + nfc_device_save(nfc_device_ref, NFC_TEST_NFC_DEV_PATH), "nfc_device_save() failed\r\n"); + + mu_assert( + nfc_device_load(nfc_device_dut, NFC_TEST_NFC_DEV_PATH), "nfc_device_load() failed\r\n"); + + mu_assert( + nfc_device_is_equal(nfc_device_ref, nfc_device_dut), + "nfc_device_data_dut != nfc_device_data_ref\r\n"); + mu_assert( - nfc_test_digital_signal_test_encode( - NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, - NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX, - NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX, - 1, - 37), - "NFC long digital signal test failed\r\n"); + storage_simply_remove(nfc_test->storage, NFC_TEST_NFC_DEV_PATH), + "storage_simply_remove() failed\r\n"); + + nfc_device_free(nfc_device_dut); } -MU_TEST(mf_classic_dict_test) { - MfClassicDict* instance = NULL; - uint64_t key = 0; - FuriString* temp_str; - temp_str = furi_string_alloc(); +static void iso14443_3a_file_test(uint8_t uid_len) { + NfcDevice* nfc_device = nfc_device_alloc(); - instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest); - mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n"); + Iso14443_3aData* data = iso14443_3a_alloc(); + data->uid_len = uid_len; + furi_hal_random_fill_buf(data->uid, uid_len); + furi_hal_random_fill_buf(data->atqa, sizeof(data->atqa)); + furi_hal_random_fill_buf(&data->sak, 1); - mu_assert( - mf_classic_dict_get_total_keys(instance) == 0, - "mf_classic_dict_get_total_keys == 0 assert failed\r\n"); + nfc_device_set_data(nfc_device, NfcProtocolIso14443_3a, data); + nfc_test_save_and_load(nfc_device); - furi_string_set(temp_str, "2196FAD8115B"); - mu_assert( - mf_classic_dict_add_key_str(instance, temp_str), - "mf_classic_dict_add_key == true assert failed\r\n"); + iso14443_3a_free(data); + nfc_device_free(nfc_device); +} - mu_assert( - mf_classic_dict_get_total_keys(instance) == 1, - "mf_classic_dict_get_total_keys == 1 assert failed\r\n"); +static void nfc_file_test_with_generator(NfcDataGeneratorType type) { + NfcDevice* nfc_device_ref = nfc_device_alloc(); - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); + nfc_data_generator_fill_data(type, nfc_device_ref); + nfc_test_save_and_load(nfc_device_ref); - mu_assert( - mf_classic_dict_get_key_at_index_str(instance, temp_str, 0), - "mf_classic_dict_get_key_at_index_str == true assert failed\r\n"); - mu_assert( - furi_string_cmp(temp_str, "2196FAD8115B") == 0, - "string_cmp(temp_str, \"2196FAD8115B\") == 0 assert failed\r\n"); + nfc_device_free(nfc_device_ref); +} - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); +MU_TEST(iso14443_3a_4b_file_test) { + iso14443_3a_file_test(4); +} - mu_assert( - mf_classic_dict_get_key_at_index(instance, &key, 0), - "mf_classic_dict_get_key_at_index == true assert failed\r\n"); - mu_assert(key == 0x2196FAD8115B, "key == 0x2196FAD8115B assert failed\r\n"); +MU_TEST(iso14443_3a_7b_file_test) { + iso14443_3a_file_test(7); +} - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); +MU_TEST(mf_ultralight_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralight); +} - mu_assert( - mf_classic_dict_delete_index(instance, 0), - "mf_classic_dict_delete_index == true assert failed\r\n"); +MU_TEST(mf_ultralight_ev1_11_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_11); +} - mf_classic_dict_free(instance); - furi_string_free(temp_str); +MU_TEST(mf_ultralight_ev1_h11_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_H11); } -MU_TEST(mf_classic_dict_load_test) { - Storage* storage = furi_record_open(RECORD_STORAGE); - mu_assert(storage != NULL, "storage != NULL assert failed\r\n"); +MU_TEST(mf_ultralight_ev1_21_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_21); +} - // Delete unit test dict file if exists - if(storage_file_exists(storage, NFC_TEST_DICT_PATH)) { - mu_assert( - storage_simply_remove(storage, NFC_TEST_DICT_PATH), - "remove == true assert failed\r\n"); - } +MU_TEST(mf_ultralight_ev1_h21_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_H21); +} - // Create unit test dict file - Stream* file_stream = file_stream_alloc(storage); - mu_assert(file_stream != NULL, "file_stream != NULL assert failed\r\n"); - mu_assert( - file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS), - "file_stream_open == true assert failed\r\n"); +MU_TEST(mf_ultralight_ntag_203_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG203); +} - // Write unit test dict file - char key_str[] = "a0a1a2a3a4a5"; - mu_assert( - stream_write_cstring(file_stream, key_str) == strlen(key_str), - "write == true assert failed\r\n"); - // Close unit test dict file - mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n"); - - // Load unit test dict file - MfClassicDict* instance = NULL; - instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest); - mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n"); - uint32_t total_keys = mf_classic_dict_get_total_keys(instance); - mu_assert(total_keys == 1, "total_keys == 1 assert failed\r\n"); - - // Read key - uint64_t key_ref = 0xa0a1a2a3a4a5; - uint64_t key_dut = 0; - FuriString* temp_str = furi_string_alloc(); - mu_assert( - mf_classic_dict_get_next_key_str(instance, temp_str), - "get_next_key_str == true assert failed\r\n"); - mu_assert(furi_string_cmp_str(temp_str, key_str) == 0, "invalid key loaded\r\n"); - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); - mu_assert( - mf_classic_dict_get_next_key(instance, &key_dut), - "get_next_key == true assert failed\r\n"); - mu_assert(key_dut == key_ref, "invalid key loaded\r\n"); - furi_string_free(temp_str); - mf_classic_dict_free(instance); +MU_TEST(mf_ultralight_ntag_213_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG213); +} - // Check that MfClassicDict added new line to the end of the file - mu_assert( - file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_READ, FSOM_OPEN_EXISTING), - "file_stream_open == true assert failed\r\n"); - mu_assert(stream_seek(file_stream, -1, StreamOffsetFromEnd), "seek == true assert failed\r\n"); - uint8_t last_char = 0; - mu_assert(stream_read(file_stream, &last_char, 1) == 1, "read == true assert failed\r\n"); - mu_assert(last_char == '\n', "last_char == '\\n' assert failed\r\n"); - mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n"); - - // Delete unit test dict file - mu_assert( - storage_simply_remove(storage, NFC_TEST_DICT_PATH), "remove == true assert failed\r\n"); - stream_free(file_stream); - furi_record_close(RECORD_STORAGE); +MU_TEST(mf_ultralight_ntag_215_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG215); } -MU_TEST(nfca_file_test) { - NfcDevice* nfc = nfc_device_alloc(); - mu_assert(nfc != NULL, "nfc_device_data != NULL assert failed\r\n"); - nfc->format = NfcDeviceSaveFormatUid; +MU_TEST(mf_ultralight_ntag_216_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG216); +} - // Fill the UID, sak, ATQA and type - uint8_t uid[7] = {0x04, 0x01, 0x23, 0x45, 0x67, 0x89, 0x00}; - memcpy(nfc->dev_data.nfc_data.uid, uid, 7); - nfc->dev_data.nfc_data.uid_len = 7; +MU_TEST(mf_ultralight_ntag_i2c_1k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2C1k); +} - nfc->dev_data.nfc_data.sak = 0x08; - nfc->dev_data.nfc_data.atqa[0] = 0x00; - nfc->dev_data.nfc_data.atqa[1] = 0x04; - nfc->dev_data.nfc_data.type = FuriHalNfcTypeA; +MU_TEST(mf_ultralight_ntag_i2c_2k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2C2k); +} - // Save the NFC device data to the file - mu_assert( - nfc_device_save(nfc, NFC_TEST_NFC_DEV_PATH), "nfc_device_save == true assert failed\r\n"); - nfc_device_free(nfc); +MU_TEST(mf_ultralight_ntag_i2c_plus_1k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2CPlus1k); +} - // Load the NFC device data from the file - NfcDevice* nfc_validate = nfc_device_alloc(); - mu_assert( - nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, true), - "nfc_device_load == true assert failed\r\n"); +MU_TEST(mf_ultralight_ntag_i2c_plus_2k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2CPlus2k); +} - // Check the UID, sak, ATQA and type - mu_assert(memcmp(nfc_validate->dev_data.nfc_data.uid, uid, 7) == 0, "uid assert failed\r\n"); - mu_assert(nfc_validate->dev_data.nfc_data.sak == 0x08, "sak == 0x08 assert failed\r\n"); - mu_assert( - nfc_validate->dev_data.nfc_data.atqa[0] == 0x00, "atqa[0] == 0x00 assert failed\r\n"); - mu_assert( - nfc_validate->dev_data.nfc_data.atqa[1] == 0x04, "atqa[1] == 0x04 assert failed\r\n"); +MU_TEST(mf_classic_mini_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassicMini); +} + +MU_TEST(mf_classic_1k_4b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic1k_4b); +} + +MU_TEST(mf_classic_1k_7b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic1k_7b); +} + +MU_TEST(mf_classic_4k_4b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic4k_4b); +} + +MU_TEST(mf_classic_4k_7b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic4k_7b); +} + +MU_TEST(iso14443_3a_reader) { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + Iso14443_3aData iso14443_3a_listener_data = { + .uid_len = 7, + .uid = {0x04, 0x51, 0x5C, 0xFA, 0x6F, 0x73, 0x81}, + .atqa = {0x44, 0x00}, + .sak = 0x00, + }; + NfcListener* iso3_listener = + nfc_listener_alloc(listener, NfcProtocolIso14443_3a, &iso14443_3a_listener_data); + nfc_listener_start(iso3_listener, NULL, NULL); + + Iso14443_3aData iso14443_3a_poller_data = {}; mu_assert( - nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, - "type == FuriHalNfcTypeA assert failed\r\n"); - nfc_device_free(nfc_validate); -} - -static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { - NfcDevice* nfc_dev = nfc_device_alloc(); - mu_assert(nfc_dev != NULL, "nfc_device_data != NULL assert failed\r\n"); - nfc_dev->format = NfcDeviceSaveFormatMifareClassic; - - // Create a test file - nfc_generate_mf_classic(&nfc_dev->dev_data, uid_len, type); - - // Get the uid from generated MFC - uint8_t uid[7] = {0}; - memcpy(uid, nfc_dev->dev_data.nfc_data.uid, uid_len); - uint8_t sak = nfc_dev->dev_data.nfc_data.sak; - uint8_t atqa[2] = {}; - memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2); - - MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data; - // Check the manufacturer block (should be uid[uid_len] + BCC (for 4byte only) + SAK + ATQA0 + ATQA1 + 0xFF[rest]) - uint8_t manufacturer_block[16] = {0}; - memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); + iso14443_3a_poller_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone, + "iso14443_3a_poller_read() failed"); + + nfc_listener_stop(iso3_listener); mu_assert( - memcmp(manufacturer_block, uid, uid_len) == 0, - "manufacturer_block uid doesn't match the file\r\n"); + iso14443_3a_is_equal(&iso14443_3a_poller_data, &iso14443_3a_listener_data), + "Data not matches"); - uint8_t position = 0; - if(uid_len == 4) { - position = uid_len; + nfc_listener_free(iso3_listener); + nfc_free(listener); + nfc_free(poller); +} - uint8_t bcc = 0; +static void mf_ultralight_reader_test(const char* path) { + FURI_LOG_I(TAG, "Testing file: %s", path); + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); - for(int i = 0; i < uid_len; i++) { - bcc ^= uid[i]; - } + NfcDevice* nfc_device = nfc_device_alloc(); + mu_assert(nfc_device_load(nfc_device, path), "nfc_device_load() failed\r\n"); - mu_assert(manufacturer_block[position] == bcc, "manufacturer_block bcc assert failed\r\n"); - } else { - position = uid_len - 1; - } + NfcListener* mfu_listener = nfc_listener_alloc( + listener, + NfcProtocolMfUltralight, + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)); + nfc_listener_start(mfu_listener, NULL, NULL); - mu_assert(manufacturer_block[position + 1] == sak, "manufacturer_block sak assert failed\r\n"); + 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"); - mu_assert( - manufacturer_block[position + 2] == atqa[0], "manufacturer_block atqa0 assert failed\r\n"); + nfc_listener_stop(mfu_listener); + nfc_listener_free(mfu_listener); mu_assert( - manufacturer_block[position + 3] == atqa[1], "manufacturer_block atqa1 assert failed\r\n"); + mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)), + "Data not matches"); - for(uint8_t i = position + 4; i < 16; i++) { - mu_assert( - manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n"); - } + mf_ultralight_free(mfu_data); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} - // Reference sector trailers (should be 0xFF[6] + 0xFF + 0x07 + 0x80 + 0x69 + 0xFF[6]) - uint8_t sector_trailer[16] = { - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0x07, - 0x80, - 0x69, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF}; - // Reference block data - uint8_t block_data[16] = {}; - memset(block_data, 0xff, sizeof(block_data)); - uint16_t total_blocks = mf_classic_get_total_block_num(type); - for(size_t i = 1; i < total_blocks; i++) { - if(mf_classic_is_sector_trailer(i)) { - mu_assert( - memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, - "Failed sector trailer compare"); - } else { - mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); - } - } - // Save the NFC device data to the file - mu_assert( - nfc_device_save(nfc_dev, NFC_TEST_NFC_DEV_PATH), - "nfc_device_save == true assert failed\r\n"); - // Verify that key cache is saved - FuriString* key_cache_name = furi_string_alloc(); - furi_string_set_str(key_cache_name, "/ext/nfc/.cache/"); - for(size_t i = 0; i < uid_len; i++) { - furi_string_cat_printf(key_cache_name, "%02X", uid[i]); - } - furi_string_cat_printf(key_cache_name, ".keys"); - mu_assert( - storage_common_stat(nfc_dev->storage, furi_string_get_cstr(key_cache_name), NULL) == - FSE_OK, - "Key cache file save failed"); - nfc_device_free(nfc_dev); - - // Load the NFC device data from the file - NfcDevice* nfc_validate = nfc_device_alloc(); - mu_assert(nfc_validate, "Nfc device alloc assert"); - mu_assert( - nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, false), - "nfc_device_load == true assert failed\r\n"); +MU_TEST(mf_ultralight_11_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ultralight_11.nfc")); +} - // Check the UID, sak, ATQA and type - mu_assert( - memcmp(nfc_validate->dev_data.nfc_data.uid, uid, uid_len) == 0, - "uid compare assert failed\r\n"); - mu_assert(nfc_validate->dev_data.nfc_data.sak == sak, "sak compare assert failed\r\n"); +MU_TEST(mf_ultralight_21_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ultralight_21.nfc")); +} + +MU_TEST(ntag_215_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ntag215.nfc")); +} + +MU_TEST(ntag_216_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ntag216.nfc")); +} + +MU_TEST(ntag_213_locked_reader) { + FURI_LOG_I(TAG, "Testing Ntag215 locked file"); + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDeviceData* nfc_device = nfc_device_alloc(); mu_assert( - memcmp(nfc_validate->dev_data.nfc_data.atqa, atqa, 2) == 0, - "atqa compare assert failed\r\n"); + nfc_device_load(nfc_device, EXT_PATH("unit_tests/nfc/Ntag213_locked.nfc")), + "nfc_device_load() failed\r\n"); + + NfcListener* mfu_listener = nfc_listener_alloc( + listener, + NfcProtocolMfUltralight, + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)); + 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"); + + nfc_listener_stop(mfu_listener); + nfc_listener_free(mfu_listener); + + MfUltralightConfigPages* config = NULL; + const MfUltralightData* mfu_ref_data = + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight); mu_assert( - nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, - "type == FuriHalNfcTypeA assert failed\r\n"); + mf_ultralight_get_config_page(mfu_ref_data, &config), + "mf_ultralight_get_config_page() failed"); + uint16_t pages_locked = config->auth0; + + mu_assert(mfu_data->pages_read == pages_locked, "Unexpected pages read"); + + mf_ultralight_free(mfu_data); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + +static void mf_ultralight_write() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfUltralightEV1_21, nfc_device); + + NfcListener* mfu_listener = nfc_listener_alloc( + listener, + NfcProtocolMfUltralight, + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)); + nfc_listener_start(mfu_listener, NULL, NULL); + + 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"); - // Check the manufacturer block mu_assert( - memcmp(nfc_validate->dev_data.mf_classic_data.block[0].value, manufacturer_block, 16) == 0, - "manufacturer_block assert failed\r\n"); - // Check other blocks - for(size_t i = 1; i < total_blocks; i++) { - if(mf_classic_is_sector_trailer(i)) { - mu_assert( - memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, - "Failed sector trailer compare"); - } else { - mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); - } - } - nfc_device_free(nfc_validate); - - // Check saved key cache - NfcDevice* nfc_keys = nfc_device_alloc(); - mu_assert(nfc_validate, "Nfc device alloc assert"); - nfc_keys->dev_data.nfc_data.uid_len = uid_len; - memcpy(nfc_keys->dev_data.nfc_data.uid, uid, uid_len); - mu_assert(nfc_device_load_key_cache(nfc_keys), "Failed to load key cache"); - uint8_t total_sec = mf_classic_get_total_sectors_num(type); - uint8_t default_key[6] = {}; - memset(default_key, 0xff, 6); - for(size_t i = 0; i < total_sec; i++) { - MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(&nfc_keys->dev_data.mf_classic_data, i); - mu_assert(memcmp(sec_tr->key_a, default_key, 6) == 0, "Failed key compare"); - mu_assert(memcmp(sec_tr->key_b, default_key, 6) == 0, "Failed key compare"); + mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)), + "Data not matches"); + + // Write random data + for(size_t i = 5; i < 15; i++) { + MfUltralightPage page = {}; + 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"); } - // Delete key cache file - mu_assert( - storage_common_remove(nfc_keys->storage, furi_string_get_cstr(key_cache_name)) == FSE_OK, - "Failed to remove key cache file"); - furi_string_free(key_cache_name); - nfc_device_free(nfc_keys); -} + // Verification read + error = mf_ultralight_poller_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); + + nfc_listener_stop(mfu_listener); + const MfUltralightData* mfu_listener_data = + nfc_listener_get_data(mfu_listener, NfcProtocolMfUltralight); -MU_TEST(mf_mini_file_test) { - mf_classic_generator_test(4, MfClassicTypeMini); + mu_assert(mf_ultralight_is_equal(mfu_data, mfu_listener_data), "Data not matches"); + + nfc_listener_free(mfu_listener); + mf_ultralight_free(mfu_data); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_1k_4b_file_test) { - mf_classic_generator_test(4, MfClassicType1k); +static void mf_classic_reader() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + MfClassicBlock block = {}; + MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + + mf_classic_poller_read_block(poller, 0, &key, MfClassicKeyTypeA, &block); + + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + + const MfClassicData* mfc_data = nfc_device_get_data(nfc_device, NfcProtocolMfClassic); + mu_assert(memcmp(&mfc_data->block[0], &block, sizeof(MfClassicBlock)) == 0, "Data mismatch"); + + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_4k_4b_file_test) { - mf_classic_generator_test(4, MfClassicType4k); +static void mf_classic_write() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + MfClassicBlock block_write = {}; + MfClassicBlock block_read = {}; + 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); + + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + + mu_assert(memcmp(&block_read, &block_write, sizeof(MfClassicBlock)) == 0, "Data mismatch"); + + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_1k_7b_file_test) { - mf_classic_generator_test(7, MfClassicType1k); +static void mf_classic_value_block() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + + int32_t value = 228; + MfClassicBlock block_write = {}; + mf_classic_value_to_block(value, 1, &block_write); + + MfClassicError error = MfClassicErrorNone; + error = mf_classic_poller_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); + 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); + mu_assert(error == MfClassicErrorNone, "Value decrement failed"); + mu_assert(new_value == value, "Value not match"); + + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_4k_7b_file_test) { - mf_classic_generator_test(7, MfClassicType4k); +MU_TEST(mf_classic_dict_test) { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) { + mu_assert( + storage_simply_remove(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH), + "Remove test dict failed"); + } + + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + mu_assert(dict != NULL, "nfc_dict_alloc() failed"); + + size_t dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert(dict_keys_total == 0, "nfc_dict_keys_total() failed"); + + const uint32_t test_key_num = 30; + MfClassicKey* key_arr_ref = malloc(test_key_num * sizeof(MfClassicKey)); + for(size_t i = 0; i < test_key_num; i++) { + furi_hal_random_fill_buf(key_arr_ref[i].data, sizeof(MfClassicKey)); + mu_assert( + nfc_dict_add_key(dict, key_arr_ref[i].data, sizeof(MfClassicKey)), "add key failed"); + + size_t dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert(dict_keys_total == (i + 1), "nfc_dict_keys_total() failed"); + } + + nfc_dict_free(dict); + + dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + mu_assert(dict != NULL, "nfc_dict_alloc() failed"); + + dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert(dict_keys_total == test_key_num, "nfc_dict_keys_total() failed"); + + MfClassicKey key_dut = {}; + size_t key_idx = 0; + while(nfc_dict_get_next_key(dict, key_dut.data, sizeof(MfClassicKey))) { + mu_assert( + memcmp(key_arr_ref[key_idx].data, key_dut.data, sizeof(MfClassicKey)) == 0, + "Loaded key data mismatch"); + key_idx++; + } + + uint32_t delete_keys_idx[] = {1, 3, 9, 11, 19, 27}; + + for(size_t i = 0; i < COUNT_OF(delete_keys_idx); i++) { + MfClassicKey* key = &key_arr_ref[delete_keys_idx[i]]; + mu_assert( + nfc_dict_is_key_present(dict, key->data, sizeof(MfClassicKey)), + "nfc_dict_is_key_present() failed"); + mu_assert( + nfc_dict_delete_key(dict, key->data, sizeof(MfClassicKey)), + "nfc_dict_delete_key() failed"); + } + + dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert( + dict_keys_total == test_key_num - COUNT_OF(delete_keys_idx), + "nfc_dict_keys_total() failed"); + + nfc_dict_free(dict); + free(key_arr_ref); + + mu_assert( + storage_simply_remove(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH), + "Remove test dict failed"); } MU_TEST_SUITE(nfc) { nfc_test_alloc(); - MU_RUN_TEST(nfca_file_test); - MU_RUN_TEST(mf_mini_file_test); + MU_RUN_TEST(iso14443_3a_reader); + MU_RUN_TEST(mf_ultralight_11_reader); + MU_RUN_TEST(mf_ultralight_21_reader); + MU_RUN_TEST(ntag_215_reader); + MU_RUN_TEST(ntag_216_reader); + MU_RUN_TEST(ntag_213_locked_reader); + + MU_RUN_TEST(mf_ultralight_write); + + MU_RUN_TEST(iso14443_3a_4b_file_test); + MU_RUN_TEST(iso14443_3a_7b_file_test); + + MU_RUN_TEST(mf_ultralight_file_test); + MU_RUN_TEST(mf_ultralight_ev1_11_file_test); + MU_RUN_TEST(mf_ultralight_ev1_h11_file_test); + MU_RUN_TEST(mf_ultralight_ev1_21_file_test); + MU_RUN_TEST(mf_ultralight_ev1_h21_file_test); + MU_RUN_TEST(mf_ultralight_ntag_203_file_test); + MU_RUN_TEST(mf_ultralight_ntag_213_file_test); + MU_RUN_TEST(mf_ultralight_ntag_215_file_test); + MU_RUN_TEST(mf_ultralight_ntag_216_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_1k_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_2k_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_plus_1k_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_plus_2k_file_test); + + MU_RUN_TEST(mf_classic_mini_file_test); MU_RUN_TEST(mf_classic_1k_4b_file_test); - MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_1k_7b_file_test); + MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_4k_7b_file_test); - MU_RUN_TEST(nfc_digital_signal_test); + MU_RUN_TEST(mf_classic_reader); + + MU_RUN_TEST(mf_classic_write); + MU_RUN_TEST(mf_classic_value_block); + MU_RUN_TEST(mf_classic_dict_test); - MU_RUN_TEST(mf_classic_dict_load_test); nfc_test_free(); } diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c new file mode 100644 index 0000000000..ee2657bea1 --- /dev/null +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -0,0 +1,458 @@ +#ifdef FW_CFG_unit_tests + +#include +#include +#include + +#include + +#define NFC_MAX_BUFFER_SIZE (256) + +typedef enum { + NfcTransportLogLevelWarning, + NfcTransportLogLevelInfo, +} NfcTransportLogLevel; + +FuriMessageQueue* poller_queue = NULL; +FuriMessageQueue* listener_queue = NULL; + +typedef enum { + NfcMessageTypeTx, + NfcMessageTypeTimeout, + NfcMessageTypeAbort, +} NfcMessageType; + +typedef struct { + uint16_t data_bits; + uint8_t data[NFC_MAX_BUFFER_SIZE]; +} NfcMessageData; + +typedef struct { + NfcMessageType type; + NfcMessageData data; +} NfcMessage; + +typedef enum { + NfcStateIdle, + NfcStateReady, + NfcStateReset, +} NfcState; + +typedef enum { + Iso14443_3aColResStatusIdle, + Iso14443_3aColResStatusInProgress, + Iso14443_3aColResStatusDone, +} Iso14443_3aColResStatus; + +typedef struct { + Iso14443_3aSensResp sens_resp; + Iso14443_3aSddResp sdd_resp[2]; + Iso14443_3aSelResp sel_resp[2]; +} Iso14443_3aColResData; + +struct Nfc { + NfcState state; + + Iso14443_3aColResStatus col_res_status; + Iso14443_3aColResData col_res_data; + + NfcEventCallback callback; + void* context; + + NfcMode mode; + + FuriThread* worker_thread; +}; + +static void nfc_test_print( + NfcTransportLogLevel log_level, + const char* message, + uint8_t* buffer, + uint16_t bits) { + FuriString* str = furi_string_alloc(); + size_t bytes = (bits + 7) / 8; + + for(size_t i = 0; i < bytes; i++) { + furi_string_cat_printf(str, " %02X", buffer[i]); + } + if(log_level == NfcTransportLogLevelWarning) { + FURI_LOG_W(message, "%s", furi_string_get_cstr(str)); + } else { + FURI_LOG_I(message, "%s", furi_string_get_cstr(str)); + } + + furi_string_free(str); +} + +static void nfc_prepare_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + memcpy(instance->col_res_data.sens_resp.sens_resp, atqa, 2); + + if(uid_len == 7) { + instance->col_res_data.sdd_resp[0].nfcid[0] = 0x88; + memcpy(&instance->col_res_data.sdd_resp[0].nfcid[1], uid, 3); + uint8_t bss = 0; + for(size_t i = 0; i < 4; i++) { + bss ^= instance->col_res_data.sdd_resp[0].nfcid[i]; + } + instance->col_res_data.sdd_resp[0].bss = bss; + instance->col_res_data.sel_resp[0].sak = 0x04; + + memcpy(instance->col_res_data.sdd_resp[1].nfcid, &uid[3], 4); + bss = 0; + for(size_t i = 0; i < 4; i++) { + bss ^= instance->col_res_data.sdd_resp[1].nfcid[i]; + } + instance->col_res_data.sdd_resp[1].bss = bss; + instance->col_res_data.sel_resp[1].sak = sak; + + } else { + furi_crash("Not supporting not 7 bytes"); + } +} + +Nfc* nfc_alloc() { + Nfc* instance = malloc(sizeof(Nfc)); + + return instance; +} + +void nfc_free(Nfc* instance) { + furi_assert(instance); + + free(instance); +} + +void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) { + UNUSED(instance); + UNUSED(tech); + + instance->mode = mode; +} + +void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) { + UNUSED(instance); + UNUSED(fdt_poll_fc); +} + +void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) { + UNUSED(instance); + UNUSED(fdt_listen_fc); +} + +void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) { + UNUSED(instance); + UNUSED(mask_rx_time_fc); +} + +void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) { + UNUSED(instance); + UNUSED(fdt_poll_poll_us); +} + +void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) { + UNUSED(instance); + UNUSED(guard_time_us); +} + +NfcError nfc_iso14443a_listener_set_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + furi_assert(instance); + furi_assert(uid); + furi_assert(atqa); + + nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak); + + return NfcErrorNone; +} + +static int32_t nfc_worker_poller(void* context) { + Nfc* instance = context; + furi_assert(instance->callback); + + instance->state = NfcStateReady; + NfcCommand command = NfcCommandContinue; + NfcEvent event = {}; + + while(true) { + event.type = NfcEventTypePollerReady; + command = instance->callback(event, instance->context); + if(command == NfcCommandStop) { + break; + } + } + + instance->state = NfcStateIdle; + + return 0; +} + +static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, uint16_t rx_bits) { + furi_assert(instance->col_res_status != Iso14443_3aColResStatusDone); + BitBuffer* tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + + bool processed = false; + + if((rx_bits == 7) && (rx_data[0] == 0x52)) { + instance->col_res_status = Iso14443_3aColResStatusInProgress; + bit_buffer_copy_bytes( + tx_buffer, + instance->col_res_data.sens_resp.sens_resp, + sizeof(instance->col_res_data.sens_resp.sens_resp)); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } else if(rx_bits == 2 * 8) { + if((rx_data[0] == 0x93) && (rx_data[1] == 0x20)) { + bit_buffer_copy_bytes( + tx_buffer, + (const uint8_t*)&instance->col_res_data.sdd_resp[0], + sizeof(Iso14443_3aSddResp)); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } else if((rx_data[0] == 0x95) && (rx_data[1] == 0x20)) { + bit_buffer_copy_bytes( + tx_buffer, + (const uint8_t*)&instance->col_res_data.sdd_resp[1], + sizeof(Iso14443_3aSddResp)); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } + } else if(rx_bits == 9 * 8) { + if((rx_data[0] == 0x93) && (rx_data[1] == 0x70)) { + bit_buffer_set_size_bytes(tx_buffer, 1); + bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[0].sak); + iso14443_crc_append(Iso14443CrcTypeA, tx_buffer); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } else if((rx_data[0] == 0x95) && (rx_data[1] == 0x70)) { + bit_buffer_set_size_bytes(tx_buffer, 1); + bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[1].sak); + iso14443_crc_append(Iso14443CrcTypeA, tx_buffer); + nfc_listener_tx(instance, tx_buffer); + instance->col_res_status = Iso14443_3aColResStatusDone; + NfcEvent event = {.type = NfcEventTypeListenerActivated}; + instance->callback(event, instance->context); + + processed = true; + } + } + + if(!processed) { + NfcMessage message = {.type = NfcMessageTypeTimeout}; + furi_message_queue_put(poller_queue, &message, FuriWaitForever); + } + + bit_buffer_free(tx_buffer); +} + +static int32_t nfc_worker_listener(void* context) { + Nfc* instance = context; + furi_assert(instance->callback); + + NfcMessage message = {}; + + NfcEventData event_data = {}; + event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + NfcEvent nfc_event = {.data = event_data}; + + while(true) { + furi_message_queue_get(listener_queue, &message, FuriWaitForever); + bit_buffer_copy_bits(event_data.buffer, message.data.data, message.data.data_bits); + if((message.data.data[0] == 0x52) && (message.data.data_bits == 7)) { + instance->col_res_status = Iso14443_3aColResStatusIdle; + } + + if(message.type == NfcMessageTypeAbort) { + break; + } else if(message.type == NfcMessageTypeTx) { + nfc_test_print( + NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits); + if(instance->col_res_status != Iso14443_3aColResStatusDone) { + nfc_worker_listener_pass_col_res( + instance, message.data.data, message.data.data_bits); + } else { + instance->state = NfcStateReady; + nfc_event.type = NfcEventTypeRxEnd; + instance->callback(nfc_event, instance->context); + } + } + } + + instance->state = NfcStateIdle; + instance->col_res_status = Iso14443_3aColResStatusIdle; + memset(&instance->col_res_data, 0, sizeof(instance->col_res_data)); + bit_buffer_free(nfc_event.data.buffer); + + return 0; +} + +void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { + furi_assert(instance); + furi_assert(instance->worker_thread == NULL); + + if(instance->mode == NfcModeListener) { + furi_assert(listener_queue == NULL); + // Check that poller didn't start + furi_assert(poller_queue == NULL); + } else { + furi_assert(poller_queue == NULL); + // Check that poller is started after listener + furi_assert(listener_queue); + } + + instance->callback = callback; + instance->context = context; + + if(instance->mode == NfcModeListener) { + listener_queue = furi_message_queue_alloc(4, sizeof(NfcMessage)); + } else { + poller_queue = furi_message_queue_alloc(4, sizeof(NfcMessage)); + } + + instance->worker_thread = furi_thread_alloc(); + furi_thread_set_context(instance->worker_thread, instance); + furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHigh); + furi_thread_set_stack_size(instance->worker_thread, 8 * 1024); + + if(instance->mode == NfcModeListener) { + furi_thread_set_name(instance->worker_thread, "NfcWorkerListener"); + furi_thread_set_callback(instance->worker_thread, nfc_worker_listener); + } else { + furi_thread_set_name(instance->worker_thread, "NfcWorkerPoller"); + furi_thread_set_callback(instance->worker_thread, nfc_worker_poller); + } + + furi_thread_start(instance->worker_thread); +} + +void nfc_stop(Nfc* instance) { + furi_assert(instance); + furi_assert(instance->worker_thread); + + if(instance->mode == NfcModeListener) { + NfcMessage message = {.type = NfcMessageTypeAbort}; + furi_message_queue_put(listener_queue, &message, FuriWaitForever); + furi_thread_join(instance->worker_thread); + + furi_message_queue_free(listener_queue); + listener_queue = NULL; + + furi_thread_free(instance->worker_thread); + instance->worker_thread = NULL; + } else { + furi_thread_join(instance->worker_thread); + + furi_message_queue_free(poller_queue); + poller_queue = NULL; + + furi_thread_free(instance->worker_thread); + instance->worker_thread = NULL; + } +} + +// Called from worker thread + +NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(poller_queue); + furi_assert(listener_queue); + furi_assert(tx_buffer); + + NfcMessage message = {}; + message.type = NfcMessageTypeTx; + message.data.data_bits = bit_buffer_get_size(tx_buffer); + bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer)); + + furi_message_queue_put(poller_queue, &message, FuriWaitForever); + + return NfcErrorNone; +} + +NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) { + return nfc_listener_tx(instance, tx_buffer); +} + +NfcError + nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + furi_assert(poller_queue); + furi_assert(listener_queue); + UNUSED(fwt); + + NfcError error = NfcErrorNone; + + NfcMessage message = {}; + message.type = NfcMessageTypeTx; + message.data.data_bits = bit_buffer_get_size(tx_buffer); + bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer)); + // Tx + furi_assert(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk); + // Rx + FuriStatus status = furi_message_queue_get(poller_queue, &message, 50); + + if(status == FuriStatusErrorTimeout) { + error = NfcErrorTimeout; + } else if(message.type == NfcMessageTypeTx) { + bit_buffer_copy_bits(rx_buffer, message.data.data, message.data.data_bits); + nfc_test_print( + NfcTransportLogLevelWarning, "TAG", message.data.data, message.data.data_bits); + } else if(message.type == NfcMessageTypeTimeout) { + error = NfcErrorTimeout; + } + + return error; +} + +NfcError nfc_iso14443a_poller_trx_custom_parity( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt); +} + +// Technology specific API + +NfcError nfc_iso14443a_poller_trx_short_frame( + Nfc* instance, + NfcIso14443aShortFrame frame, + BitBuffer* rx_buffer, + uint32_t fwt) { + UNUSED(frame); + + BitBuffer* tx_buffer = bit_buffer_alloc(32); + bit_buffer_set_size(tx_buffer, 7); + bit_buffer_set_byte(tx_buffer, 0, 0x52); + + NfcError error = nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt); + + bit_buffer_free(tx_buffer); + + return error; +} + +NfcError nfc_iso14443a_poller_trx_sdd_frame( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt); +} + +NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { + UNUSED(instance); + + return NfcErrorNone; +} + +#endif diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index bc34d4e658..20ebd4ca08 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -12,6 +12,56 @@ App( fap_category="NFC", ) +# Parser plugins + +App( + appid="all_in_one_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="all_in_one_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="opal_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="opal_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="myki_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="myki_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="troika_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="troika_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="plantain_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="plantain_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="two_cities_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="two_cities_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.c b/applications/main/nfc/helpers/mf_classic_key_cache.c new file mode 100644 index 0000000000..5127e64520 --- /dev/null +++ b/applications/main/nfc/helpers/mf_classic_key_cache.c @@ -0,0 +1,212 @@ +#include "mf_classic_key_cache.h" + +#include +#include + +#define NFC_APP_KEYS_EXTENSION ".keys" +#define NFC_APP_KEY_CACHE_FOLDER "/ext/nfc/.cache" + +static const char* mf_classic_key_cache_file_header = "Flipper NFC keys"; +static const uint32_t mf_classic_key_cache_file_version = 1; + +struct MfClassicKeyCache { + MfClassicDeviceKeys keys; + MfClassicKeyType current_key_type; + uint8_t current_sector; +}; + +static void nfc_get_key_cache_file_path(const uint8_t* uid, size_t uid_len, FuriString* path) { + furi_string_printf(path, "%s/", NFC_APP_KEY_CACHE_FOLDER); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(path, "%02X", uid[i]); + } + furi_string_cat_printf(path, "%s", NFC_APP_KEYS_EXTENSION); +} + +MfClassicKeyCache* mf_classic_key_cache_alloc() { + MfClassicKeyCache* instance = malloc(sizeof(MfClassicKeyCache)); + + return instance; +} + +void mf_classic_key_cache_free(MfClassicKeyCache* instance) { + furi_assert(instance); + + free(instance); +} + +bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data) { + UNUSED(instance); + furi_assert(data); + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + FuriString* file_path = furi_string_alloc(); + nfc_get_key_cache_file_path(uid, uid_len, file_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + FuriString* temp_str = furi_string_alloc(); + bool save_success = false; + do { + if(!storage_simply_mkdir(storage, NFC_APP_KEY_CACHE_FOLDER)) break; + if(!storage_simply_remove(storage, furi_string_get_cstr(file_path))) break; + if(!flipper_format_buffered_file_open_always(ff, furi_string_get_cstr(file_path))) break; + + if(!flipper_format_write_header_cstr( + ff, mf_classic_key_cache_file_header, mf_classic_key_cache_file_version)) + break; + if(!flipper_format_write_string_cstr( + ff, "Mifare Classic type", mf_classic_get_device_name(data, NfcDeviceNameTypeShort))) + break; + if(!flipper_format_write_hex_uint64(ff, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_write_hex_uint64(ff, "Key B map", &data->key_b_mask, 1)) break; + + uint8_t sector_num = mf_classic_get_total_sectors_num(data->type); + bool key_save_success = true; + for(size_t i = 0; (i < sector_num) && (key_save_success); i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); + if(FURI_BIT(data->key_a_mask, i)) { + furi_string_printf(temp_str, "Key A sector %d", i); + key_save_success = flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), sec_tr->key_a.data, sizeof(MfClassicKey)); + } + if(!key_save_success) break; + if(FURI_BIT(data->key_b_mask, i)) { + furi_string_printf(temp_str, "Key B sector %d", i); + key_save_success = flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), sec_tr->key_b.data, sizeof(MfClassicKey)); + } + } + save_success = key_save_success; + } while(false); + + flipper_format_free(ff); + furi_string_free(temp_str); + furi_string_free(file_path); + furi_record_close(RECORD_STORAGE); + + return save_success; +} + +bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len) { + furi_assert(instance); + furi_assert(uid); + + mf_classic_key_cache_reset(instance); + + FuriString* file_path = furi_string_alloc(); + nfc_get_key_cache_file_path(uid, uid_len, file_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + FuriString* temp_str = furi_string_alloc(); + bool load_success = false; + do { + if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(file_path))) break; + + uint32_t version = 0; + if(!flipper_format_read_header(ff, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, mf_classic_key_cache_file_header)) break; + if(version != mf_classic_key_cache_file_version) break; + + if(!flipper_format_read_hex_uint64(ff, "Key A map", &instance->keys.key_a_mask, 1)) break; + if(!flipper_format_read_hex_uint64(ff, "Key B map", &instance->keys.key_b_mask, 1)) break; + + bool key_read_success = true; + for(size_t i = 0; (i < MF_CLASSIC_TOTAL_SECTORS_MAX) && (key_read_success); i++) { + if(FURI_BIT(instance->keys.key_a_mask, i)) { + furi_string_printf(temp_str, "Key A sector %d", i); + key_read_success = flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + instance->keys.key_a[i].data, + sizeof(MfClassicKey)); + } + if(!key_read_success) break; + if(FURI_BIT(instance->keys.key_b_mask, i)) { + furi_string_printf(temp_str, "Key B sector %d", i); + key_read_success = flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + instance->keys.key_b[i].data, + sizeof(MfClassicKey)); + } + } + load_success = key_read_success; + } while(false); + + flipper_format_buffered_file_close(ff); + flipper_format_free(ff); + furi_string_free(temp_str); + furi_string_free(file_path); + furi_record_close(RECORD_STORAGE); + + return load_success; +} + +void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data) { + furi_assert(instance); + furi_assert(data); + + mf_classic_key_cache_reset(instance); + instance->keys.key_a_mask = data->key_a_mask; + instance->keys.key_b_mask = data->key_b_mask; + for(size_t i = 0; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); + + if(FURI_BIT(data->key_a_mask, i)) { + instance->keys.key_a[i] = sec_tr->key_a; + } + if(FURI_BIT(data->key_b_mask, i)) { + instance->keys.key_b[i] = sec_tr->key_b; + } + } +} + +bool mf_classic_key_cahce_get_next_key( + MfClassicKeyCache* instance, + uint8_t* sector_num, + MfClassicKey* key, + MfClassicKeyType* key_type) { + furi_assert(instance); + furi_assert(sector_num); + furi_assert(key); + furi_assert(key_type); + + bool next_key_found = false; + for(uint8_t i = instance->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) { + if(FURI_BIT(instance->keys.key_a_mask, i)) { + FURI_BIT_CLEAR(instance->keys.key_a_mask, i); + *key = instance->keys.key_a[i]; + *key_type = MfClassicKeyTypeA; + *sector_num = i; + + next_key_found = true; + break; + } + if(FURI_BIT(instance->keys.key_b_mask, i)) { + FURI_BIT_CLEAR(instance->keys.key_b_mask, i); + *key = instance->keys.key_b[i]; + *key_type = MfClassicKeyTypeB; + *sector_num = i; + + next_key_found = true; + instance->current_sector = i; + break; + } + } + + return next_key_found; +} + +void mf_classic_key_cache_reset(MfClassicKeyCache* instance) { + furi_assert(instance); + + instance->current_key_type = MfClassicKeyTypeA; + instance->current_sector = 0; + instance->keys.key_a_mask = 0; + instance->keys.key_b_mask = 0; +} diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.h b/applications/main/nfc/helpers/mf_classic_key_cache.h new file mode 100644 index 0000000000..407c6e28bd --- /dev/null +++ b/applications/main/nfc/helpers/mf_classic_key_cache.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfClassicKeyCache MfClassicKeyCache; + +MfClassicKeyCache* mf_classic_key_cache_alloc(); + +void mf_classic_key_cache_free(MfClassicKeyCache* instance); + +bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len); + +void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data); + +bool mf_classic_key_cahce_get_next_key( + MfClassicKeyCache* instance, + uint8_t* sector_num, + MfClassicKey* key, + MfClassicKeyType* key_type); + +bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data); + +void mf_classic_key_cache_reset(MfClassicKeyCache* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/mf_ultralight_auth.c b/applications/main/nfc/helpers/mf_ultralight_auth.c new file mode 100644 index 0000000000..5e0c67887f --- /dev/null +++ b/applications/main/nfc/helpers/mf_ultralight_auth.c @@ -0,0 +1,58 @@ +#include "mf_ultralight_auth.h" + +#include +#include + +MfUltralightAuth* mf_ultralight_auth_alloc() { + MfUltralightAuth* instance = malloc(sizeof(MfUltralightAuth)); + + return instance; +} + +void mf_ultralight_auth_free(MfUltralightAuth* instance) { + furi_assert(instance); + + free(instance); +} + +void mf_ultralight_auth_reset(MfUltralightAuth* instance) { + furi_assert(instance); + + instance->type = MfUltralightAuthTypeNone; + memset(&instance->password, 0, sizeof(MfUltralightAuthPassword)); + memset(&instance->pack, 0, sizeof(MfUltralightAuthPack)); +} + +bool mf_ultralight_generate_amiibo_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len) { + furi_assert(instance); + furi_assert(uid); + + bool generated = false; + if(uid_len == 7) { + instance->password.data[0] = uid[1] ^ uid[3] ^ 0xAA; + instance->password.data[1] = uid[2] ^ uid[4] ^ 0x55; + instance->password.data[2] = uid[3] ^ uid[5] ^ 0xAA; + instance->password.data[3] = uid[4] ^ uid[6] ^ 0x55; + generated = true; + } + + return generated; +} + +bool mf_ultralight_generate_xiaomi_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len) { + furi_assert(instance); + furi_assert(uid); + + uint8_t hash[20]; + bool generated = false; + if(uid_len == 7) { + mbedtls_sha1(uid, uid_len, hash); + instance->password.data[0] = (hash[hash[0] % 20]); + instance->password.data[1] = (hash[(hash[0] + 5) % 20]); + instance->password.data[2] = (hash[(hash[0] + 13) % 20]); + instance->password.data[3] = (hash[(hash[0] + 17) % 20]); + generated = true; + } + + return generated; +} diff --git a/applications/main/nfc/helpers/mf_ultralight_auth.h b/applications/main/nfc/helpers/mf_ultralight_auth.h new file mode 100644 index 0000000000..2ddfcafe12 --- /dev/null +++ b/applications/main/nfc/helpers/mf_ultralight_auth.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfUltralightAuthTypeNone, + MfUltralightAuthTypeReader, + MfUltralightAuthTypeManual, + MfUltralightAuthTypeXiaomi, + MfUltralightAuthTypeAmiibo, +} MfUltralightAuthType; + +typedef struct { + MfUltralightAuthType type; + MfUltralightAuthPassword password; + MfUltralightAuthPack pack; +} MfUltralightAuth; + +MfUltralightAuth* mf_ultralight_auth_alloc(); + +void mf_ultralight_auth_free(MfUltralightAuth* instance); + +void mf_ultralight_auth_reset(MfUltralightAuth* instance); + +bool mf_ultralight_generate_amiibo_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len); + +bool mf_ultralight_generate_xiaomi_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/mf_user_dict.c b/applications/main/nfc/helpers/mf_user_dict.c new file mode 100644 index 0000000000..09f0c1506e --- /dev/null +++ b/applications/main/nfc/helpers/mf_user_dict.c @@ -0,0 +1,83 @@ +#include "mf_user_dict.h" + +#include +#include +#include + +#define NFC_APP_FOLDER ANY_PATH("nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") + +struct MfUserDict { + size_t keys_num; + MfClassicKey* keys_arr; +}; + +MfUserDict* mf_user_dict_alloc(size_t max_keys_to_load) { + MfUserDict* instance = malloc(sizeof(MfUserDict)); + + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + furi_assert(dict); + + size_t dict_keys_num = nfc_dict_get_total_keys(dict); + instance->keys_num = MIN(max_keys_to_load, dict_keys_num); + + if(instance->keys_num > 0) { + instance->keys_arr = malloc(instance->keys_num * sizeof(MfClassicKey)); + for(size_t i = 0; i < instance->keys_num; i++) { + bool key_loaded = + nfc_dict_get_next_key(dict, instance->keys_arr[i].data, sizeof(MfClassicKey)); + furi_assert(key_loaded); + } + } + nfc_dict_free(dict); + + return instance; +} + +void mf_user_dict_free(MfUserDict* instance) { + furi_assert(instance); + + if(instance->keys_num > 0) { + free(instance->keys_arr); + } + free(instance); +} + +size_t mf_user_dict_get_keys_cnt(MfUserDict* instance) { + furi_assert(instance); + + return instance->keys_num; +} + +void mf_user_dict_get_key_str(MfUserDict* instance, uint32_t index, FuriString* str) { + furi_assert(instance); + furi_assert(str); + furi_assert(index < instance->keys_num); + furi_assert(instance->keys_arr); + + furi_string_reset(str); + for(size_t i = 0; i < sizeof(MfClassicKey); i++) { + furi_string_cat_printf(str, "%02X", instance->keys_arr[index].data[i]); + } +} + +bool mf_user_dict_delete_key(MfUserDict* instance, uint32_t index) { + furi_assert(instance); + furi_assert(index < instance->keys_num); + furi_assert(instance->keys_arr); + + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + furi_assert(dict); + + bool key_delete_success = + nfc_dict_delete_key(dict, instance->keys_arr[index].data, sizeof(MfClassicKey)); + nfc_dict_free(dict); + + if(key_delete_success) { + instance->keys_num--; + } + + return key_delete_success; +} diff --git a/applications/main/nfc/helpers/mf_user_dict.h b/applications/main/nfc/helpers/mf_user_dict.h new file mode 100644 index 0000000000..d482d2d5f7 --- /dev/null +++ b/applications/main/nfc/helpers/mf_user_dict.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfUserDict MfUserDict; + +MfUserDict* mf_user_dict_alloc(size_t max_keys_to_load); + +void mf_user_dict_free(MfUserDict* instance); + +size_t mf_user_dict_get_keys_cnt(MfUserDict* instance); + +void mf_user_dict_get_key_str(MfUserDict* instance, uint32_t index, FuriString* str); + +bool mf_user_dict_delete_key(MfUserDict* instance, uint32_t index); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/mfkey32_logger.c b/applications/main/nfc/helpers/mfkey32_logger.c new file mode 100644 index 0000000000..8024179f80 --- /dev/null +++ b/applications/main/nfc/helpers/mfkey32_logger.c @@ -0,0 +1,173 @@ +#include "mfkey32_logger.h" + +#include + +#include +#include +#include + +#define MFKEY32_LOGGER_MAX_NONCES_SAVED (100) + +typedef struct { + bool is_filled; + uint32_t cuid; + uint8_t sector_num; + MfClassicKeyType key_type; + uint32_t nt0; + uint32_t nr0; + uint32_t ar0; + uint32_t nt1; + uint32_t nr1; + uint32_t ar1; +} Mfkey32LoggerParams; + +ARRAY_DEF(Mfkey32LoggerParams, Mfkey32LoggerParams, M_POD_OPLIST); + +struct Mfkey32Logger { + uint32_t cuid; + Mfkey32LoggerParams_t params_arr; + size_t nonces_saves; + size_t params_collected; +}; + +Mfkey32Logger* mfkey32_logger_alloc(uint32_t cuid) { + Mfkey32Logger* instance = malloc(sizeof(Mfkey32Logger)); + instance->cuid = cuid; + Mfkey32LoggerParams_init(instance->params_arr); + + return instance; +} + +void mfkey32_logger_free(Mfkey32Logger* instance) { + furi_assert(instance); + furi_assert(instance->params_arr); + + Mfkey32LoggerParams_clear(instance->params_arr); + free(instance); +} + +static bool mfkey32_logger_add_nonce_to_existing_params( + Mfkey32Logger* instance, + MfClassicAuthContext* auth_context) { + bool nonce_added = false; + do { + if(Mfkey32LoggerParams_size(instance->params_arr) == 0) break; + + Mfkey32LoggerParams_it_t it; + for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it); + Mfkey32LoggerParams_next(it)) { + Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it); + if(params->is_filled) continue; + + uint8_t sector_num = mf_classic_get_sector_by_block(auth_context->block_num); + if(params->sector_num != sector_num) continue; + if(params->key_type != auth_context->key_type) continue; + + params->nt1 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt)); + params->nr1 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr)); + params->ar1 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr)); + params->is_filled = true; + + instance->params_collected++; + nonce_added = true; + break; + } + + } while(false); + + return nonce_added; +} + +void mfkey32_logger_add_nonce(Mfkey32Logger* instance, MfClassicAuthContext* auth_context) { + furi_assert(instance); + furi_assert(auth_context); + + bool nonce_added = mfkey32_logger_add_nonce_to_existing_params(instance, auth_context); + if(!nonce_added && (instance->nonces_saves < MFKEY32_LOGGER_MAX_NONCES_SAVED)) { + uint8_t sector_num = mf_classic_get_sector_by_block(auth_context->block_num); + Mfkey32LoggerParams params = { + .is_filled = false, + .cuid = instance->cuid, + .sector_num = sector_num, + .key_type = auth_context->key_type, + .nt0 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt)), + .nr0 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr)), + .ar0 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr)), + }; + Mfkey32LoggerParams_push_back(instance->params_arr, params); + instance->nonces_saves++; + } +} + +size_t mfkey32_logger_get_params_num(Mfkey32Logger* instance) { + furi_assert(instance); + + return instance->params_collected; +} + +bool mfkey32_logger_save_params(Mfkey32Logger* instance, const char* path) { + furi_assert(instance); + furi_assert(path); + furi_assert(instance->params_collected > 0); + furi_assert(instance->params_arr); + + bool params_saved = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + do { + if(!buffered_file_stream_open(stream, path, FSAM_WRITE, FSOM_OPEN_APPEND)) break; + + bool params_write_success = true; + Mfkey32LoggerParams_it_t it; + for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it); + Mfkey32LoggerParams_next(it)) { + Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it); + if(!params->is_filled) continue; + furi_string_printf( + temp_str, + "Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n", + params->sector_num, + params->key_type == MfClassicKeyTypeA ? 'A' : 'B', + params->cuid, + params->nt0, + params->nr0, + params->ar0, + params->nt1, + params->nr1, + params->ar1); + if(!stream_write_string(stream, temp_str)) { + params_write_success = false; + break; + } + } + if(!params_write_success) break; + + params_saved = true; + } while(false); + + furi_string_free(temp_str); + buffered_file_stream_close(stream); + stream_free(stream); + furi_record_close(RECORD_STORAGE); + + return params_saved; +} + +void mfkey32_logger_get_params_data(Mfkey32Logger* instance, FuriString* str) { + furi_assert(instance); + furi_assert(str); + furi_assert(instance->params_collected > 0); + + furi_string_reset(str); + Mfkey32LoggerParams_it_t it; + for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it); + Mfkey32LoggerParams_next(it)) { + Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it); + if(!params->is_filled) continue; + + char key_char = params->key_type == MfClassicKeyTypeA ? 'A' : 'B'; + furi_string_cat_printf(str, "Sector %d, key %c\n", params->sector_num, key_char); + } +} diff --git a/applications/main/nfc/helpers/mfkey32_logger.h b/applications/main/nfc/helpers/mfkey32_logger.h new file mode 100644 index 0000000000..12b42e1fb5 --- /dev/null +++ b/applications/main/nfc/helpers/mfkey32_logger.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Mfkey32Logger Mfkey32Logger; + +Mfkey32Logger* mfkey32_logger_alloc(uint32_t cuid); + +void mfkey32_logger_free(Mfkey32Logger* instance); + +void mfkey32_logger_add_nonce(Mfkey32Logger* instance, MfClassicAuthContext* auth_context); + +size_t mfkey32_logger_get_params_num(Mfkey32Logger* instance); + +bool mfkey32_logger_save_params(Mfkey32Logger* instance, const char* path); + +void mfkey32_logger_get_params_data(Mfkey32Logger* instance, FuriString* str); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index aa932a3d85..2a3b13e1a4 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -1,17 +1,33 @@ #pragma once -enum NfcCustomEvent { +typedef enum { // Reserve first 100 events for button types and indexes, starting from 0 NfcCustomEventReserved = 100, + // Mf classic dict attack events + NfcCustomEventDictAttackComplete, + NfcCustomEventDictAttackSkip, + NfcCustomEventDictAttackDataUpdate, + + NfcCustomEventCardDetected, + NfcCustomEventCardLost, + NfcCustomEventViewExit, NfcCustomEventWorkerExit, + NfcCustomEventWorkerUpdate, + NfcCustomEventWrongCard, + NfcCustomEventTimerExpired, NfcCustomEventByteInputDone, NfcCustomEventTextInputDone, NfcCustomEventDictAttackDone, - NfcCustomEventDictAttackSkip, + NfcCustomEventRpcLoad, + NfcCustomEventRpcExit, NfcCustomEventRpcSessionClose, - NfcCustomEventUpdateLog, - NfcCustomEventSaveShadow, -}; + + NfcCustomEventPollerSuccess, + NfcCustomEventPollerIncomplete, + NfcCustomEventPollerFailure, + + NfcCustomEventListenerUpdate, +} NfcCustomEvent; diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c new file mode 100644 index 0000000000..a242ba3ae6 --- /dev/null +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -0,0 +1,147 @@ +#include "nfc_supported_cards.h" +#include "../plugins/supported_cards/nfc_supported_card_plugin.h" + +#include +#include +#include + +#include +#include + +#define TAG "NfcSupportedCards" + +#define NFC_SUPPORTED_CARDS_PLUGINS_PATH APP_DATA_PATH("plugins") +#define NFC_SUPPORTED_CARDS_PLUGIN_SUFFIX "_parser.fal" + +typedef struct { + Storage* storage; + File* directory; + FuriString* file_path; + char file_name[256]; + FlipperApplication* app; +} NfcSupportedCards; + +static NfcSupportedCards* nfc_supported_cards_alloc() { + NfcSupportedCards* instance = malloc(sizeof(NfcSupportedCards)); + + instance->storage = furi_record_open(RECORD_STORAGE); + instance->directory = storage_file_alloc(instance->storage); + instance->file_path = furi_string_alloc(); + + if(!storage_dir_open(instance->directory, NFC_SUPPORTED_CARDS_PLUGINS_PATH)) { + FURI_LOG_D(TAG, "Failed to open directory: %s", NFC_SUPPORTED_CARDS_PLUGINS_PATH); + } + + return instance; +} + +static void nfc_supported_cards_free(NfcSupportedCards* instance) { + if(instance->app) { + flipper_application_free(instance->app); + } + + furi_string_free(instance->file_path); + + storage_dir_close(instance->directory); + storage_file_free(instance->directory); + + furi_record_close(RECORD_STORAGE); + free(instance); +} + +static const NfcSupportedCardsPlugin* + nfc_supported_cards_get_next_plugin(NfcSupportedCards* instance) { + const NfcSupportedCardsPlugin* plugin = NULL; + + do { + if(!storage_file_is_open(instance->directory)) break; + if(!storage_dir_read( + instance->directory, NULL, instance->file_name, sizeof(instance->file_name))) + break; + + furi_string_set(instance->file_path, instance->file_name); + if(!furi_string_end_with_str(instance->file_path, NFC_SUPPORTED_CARDS_PLUGIN_SUFFIX)) + continue; + + path_concat(NFC_SUPPORTED_CARDS_PLUGINS_PATH, instance->file_name, instance->file_path); + + if(instance->app) flipper_application_free(instance->app); + instance->app = flipper_application_alloc(instance->storage, firmware_api_interface); + + if(flipper_application_preload(instance->app, furi_string_get_cstr(instance->file_path)) != + FlipperApplicationPreloadStatusSuccess) + continue; + if(!flipper_application_is_plugin(instance->app)) continue; + + if(flipper_application_map_to_memory(instance->app) != FlipperApplicationLoadStatusSuccess) + continue; + + const FlipperAppPluginDescriptor* descriptor = + flipper_application_plugin_get_descriptor(instance->app); + + if(descriptor == NULL) continue; + + if(strcmp(descriptor->appid, NFC_SUPPORTED_CARD_PLUGIN_APP_ID) != 0) continue; + if(descriptor->ep_api_version != NFC_SUPPORTED_CARD_PLUGIN_API_VERSION) continue; + + plugin = descriptor->entry_point; + } while(plugin == NULL); //-V654 + + return plugin; +} + +bool nfc_supported_cards_read(NfcDevice* device, Nfc* nfc) { + furi_assert(device); + furi_assert(nfc); + + bool card_read = false; + + NfcSupportedCards* supported_cards = nfc_supported_cards_alloc(); + + do { + const NfcSupportedCardsPlugin* plugin = + nfc_supported_cards_get_next_plugin(supported_cards); + if(plugin == NULL) break; //-V547 + + const NfcProtocol protocol = nfc_device_get_protocol(device); //-V779 + if(plugin->protocol != protocol) continue; + + if(plugin->verify) { + if(!plugin->verify(nfc)) continue; + } + + if(plugin->read) { + card_read = plugin->read(nfc, device); + } + + } while(!card_read); + + nfc_supported_cards_free(supported_cards); + return card_read; +} + +bool nfc_supported_cards_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = false; + + NfcSupportedCards* supported_cards = nfc_supported_cards_alloc(); + + do { + const NfcSupportedCardsPlugin* plugin = + nfc_supported_cards_get_next_plugin(supported_cards); + if(plugin == NULL) break; //-V547 + + const NfcProtocol protocol = nfc_device_get_protocol(device); //-V779 + if(plugin->protocol != protocol) continue; + + if(plugin->parse) { + parsed = plugin->parse(device, parsed_data); + } + + } while(!parsed); + + nfc_supported_cards_free(supported_cards); + return parsed; +} diff --git a/applications/main/nfc/helpers/nfc_supported_cards.h b/applications/main/nfc/helpers/nfc_supported_cards.h new file mode 100644 index 0000000000..0c25a5b118 --- /dev/null +++ b/applications/main/nfc/helpers/nfc_supported_cards.h @@ -0,0 +1,50 @@ +/** + * @file nfc_supported_cards.h + * @brief Supported card plugin loader interface. + * + * @see nfc_supported_card_plugin.h for instructions on adding a new plugin. + */ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Read the card using a custom procedure. + * + * This function will load all suitable supported card plugins one by one and + * try to execute the custom read procedure specified in each. Upon first success, + * no further attempts will be made and the function will return. + * + * @param[in,out] device pointer to a device instance to hold the read data. + * @param[in,out] nfc pointer to an Nfc instance. + * @returns true if the card was successfully read, false otherwise. + * + * @see NfcSupportedCardPluginRead for detailed description. + */ +bool nfc_supported_cards_read(NfcDevice* device, Nfc* nfc); + +/** + * @brief Parse raw data into human-readable representation. + * + * This function will load all suitable supported card plugins one by one and + * try to parse the data according to each implementation. Upon first success, + * no further attempts will be made and the function will return. + * + * @param[in] device pointer to a device instance holding the data is to be parsed. + * @param[out] parsed_data pointer to the string to contain the formatted result. + * @returns true if the card was successfully parsed, false otherwise. + * + * @see NfcSupportedCardPluginParse for detailed description. + */ +bool nfc_supported_cards_parse(const NfcDevice* device, FuriString* parsed_data); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c new file mode 100644 index 0000000000..b3e629f4a5 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -0,0 +1,108 @@ +#include "felica.h" +#include "felica_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolFelica); + + NfcApp* instance = context; + const FelicaPollerEvent* felica_event = event.event_data; + + if(felica_event->type == FelicaPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_felica(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_felica, instance); +} + +static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_felica = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_felica, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_felica, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_felica, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_felica, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.h b/applications/main/nfc/helpers/protocol_support/felica/felica.h new file mode 100644 index 0000000000..e581b6709e --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_felica; diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c new file mode 100644 index 0000000000..3142b2c6db --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -0,0 +1,19 @@ +#include "felica_render.h" + +void nfc_render_felica_info( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + furi_string_cat_printf(str, "IDm:"); + + for(size_t i = 0; i < FELICA_IDM_SIZE; i++) { + furi_string_cat_printf(str, " %02X", data->idm.data[i]); + } + + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "\nPMm:"); + for(size_t i = 0; i < FELICA_PMM_SIZE; ++i) { + furi_string_cat_printf(str, " %02X", data->pmm.data[i]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h new file mode 100644 index 0000000000..6d9816fc66 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_felica_info( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c new file mode 100644 index 0000000000..c0d502d038 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c @@ -0,0 +1,145 @@ +#include "iso14443_3a.h" +#include "iso14443_3a_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_3a(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + NfcApp* instance = context; + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3a, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_3a(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_3a, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_3a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_emulate_listener_callback_iso14443_3a(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + + NfcApp* nfc = context; + Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_3a_event->data->buffer); + i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(iso14443_3a_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) { + const Iso14443_3aData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_3a); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_3a, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance); +} + +static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEmulate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_read_menu_on_event_iso14443_3a, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h new file mode 100644 index 0000000000..d085f25c77 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c new file mode 100644 index 0000000000..7306f1072d --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c @@ -0,0 +1,34 @@ +#include "iso14443_3a_render.h" + +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size) { + for(size_t i = 0; i < size; i++) { + furi_string_cat_printf(str, " %02X", data[i]); + } +} + +void nfc_render_iso14443_3a_info( + const Iso14443_3aData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3'; + furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type); + } + + nfc_render_iso14443_3a_brief(data, str); + + if(format_type == NfcProtocolFormatTypeFull) { + nfc_render_iso14443_3a_extra(data, str); + } +} + +void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str) { + furi_string_cat_printf(str, "UID:"); + + nfc_render_iso14443_3a_format_bytes(str, data->uid, data->uid_len); +} + +void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) { + furi_string_cat_printf(str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); + furi_string_cat_printf(str, "SAK: %02X", data->sak); +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h new file mode 100644 index 0000000000..14b91d221d --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_3a_info( + const Iso14443_3aData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size); + +void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str); + +void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c new file mode 100644 index 0000000000..fee2318462 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c @@ -0,0 +1,113 @@ +#include "iso14443_3b.h" +#include "iso14443_3b_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_3b(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3b); + + NfcApp* instance = context; + const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data; + + if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3b, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_3b(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_3b, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) { + return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_iso14443_3b, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h new file mode 100644 index 0000000000..bf3caa0c0e --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h new file mode 100644 index 0000000000..53fe6b3927 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "iso14443_3b.h" + +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c new file mode 100644 index 0000000000..2e81d57a48 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c @@ -0,0 +1,78 @@ +#include "iso14443_3b_render.h" + +void nfc_render_iso14443_3b_info( + const Iso14443_3bData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3'; + furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type); + } + + furi_string_cat_printf(str, "UID:"); + + size_t uid_size; + const uint8_t* uid = iso14443_3b_get_uid(data, &uid_size); + + for(size_t i = 0; i < uid_size; i++) { + furi_string_cat_printf(str, " %02X", uid[i]); + } + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat_printf(str, "\n\e#Protocol info\n"); + + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) { + furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); + } else { + furi_string_cat(str, "Bit rate PICC -> PCD:\n"); + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + + furi_string_cat(str, "Bit rate PICC <- PCD:\n"); + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + } + + furi_string_cat(str, "Max frame size: "); + + const uint16_t max_frame_size = iso14443_3b_get_frame_size_max(data); + if(max_frame_size != 0) { + furi_string_cat_printf(str, "%u bytes\n", max_frame_size); + } else { + furi_string_cat(str, "? (RFU)\n"); + } + + const double fwt = iso14443_3b_get_fwt_fc_max(data) / 13.56e6; + furi_string_cat_printf(str, "Max waiting time: %4.2g s\n", fwt); + + const char* nad_support_str = + iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionNad) ? "" : "not "; + furi_string_cat_printf(str, "NAD: %ssupported\n", nad_support_str); + + const char* cid_support_str = + iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not "; + furi_string_cat_printf(str, "CID: %ssupported", cid_support_str); + + furi_string_cat_printf(str, "\n\e#Application data\nRaw:"); + + size_t app_data_size; + const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size); + for(size_t i = 0; i < app_data_size; ++i) { + furi_string_cat_printf(str, " %02X", app_data[i]); + } +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h new file mode 100644 index 0000000000..ee50f6acf5 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_3b_info( + const Iso14443_3bData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c new file mode 100644 index 0000000000..0a3a592e17 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c @@ -0,0 +1,149 @@ +#include "iso14443_4a.h" +#include "iso14443_4a_render.h" + +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + NfcApp* instance = context; + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_4a, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_4a(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_4a, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_4a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_saved_menu_on_enter_iso14443_4a(NfcApp* instance) { + UNUSED(instance); +} + +NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_4a); + furi_assert(event.event_data); + + NfcApp* nfc = context; + Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data; + + if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_4a_event->data->buffer); + i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(iso14443_4a_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) { + const Iso14443_4aData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEmulate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_read_menu_on_event_iso14443_4a, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h new file mode 100644 index 0000000000..1b173d7b09 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h new file mode 100644 index 0000000000..7e13403da1 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "iso14443_4a.h" + +NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c new file mode 100644 index 0000000000..a963e744b9 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c @@ -0,0 +1,84 @@ +#include "iso14443_4a_render.h" + +#include "../iso14443_3a/iso14443_3a_render.h" + +void nfc_render_iso14443_4a_info( + const Iso14443_4aData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(data, str); + + if(format_type != NfcProtocolFormatTypeFull) return; + + nfc_render_iso14443_4a_extra(data, str); +} + +void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) { + nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str); +} + +void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) { + furi_string_cat_printf(str, "\n\e#Protocol info\n"); + + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) { + furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); + } else { + furi_string_cat(str, "Bit rate PICC -> PCD:\n"); + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + + furi_string_cat(str, "Bit rate PICC <- PCD:\n"); + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + } + + furi_string_cat(str, "Max frame size: "); + + const uint16_t max_frame_size = iso14443_4a_get_frame_size_max(data); + if(max_frame_size != 0) { + furi_string_cat_printf(str, "%u bytes\n", max_frame_size); + } else { + furi_string_cat(str, "? (RFU)\n"); + } + + const uint32_t fwt_fc = iso14443_4a_get_fwt_fc_max(data); + if(fwt_fc != 0) { + furi_string_cat_printf(str, "Max waiting time: %4.2g s\n", (double)(fwt_fc / 13.56e6)); + } + + const char* nad_support_str = + iso14443_4a_supports_frame_option(data, Iso14443_4aFrameOptionNad) ? "" : "not "; + furi_string_cat_printf(str, "NAD: %ssupported\n", nad_support_str); + + const char* cid_support_str = + iso14443_4a_supports_frame_option(data, Iso14443_4aFrameOptionCid) ? "" : "not "; + furi_string_cat_printf(str, "CID: %ssupported", cid_support_str); + + uint32_t hist_bytes_count; + const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count); + + if(hist_bytes_count > 0) { + furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:"); + + for(size_t i = 0; i < hist_bytes_count; ++i) { + furi_string_cat_printf(str, " %02X", hist_bytes[i]); + } + } + + furi_string_cat(str, "\n\e#ISO14443-3A data"); + nfc_render_iso14443_3a_extra(iso14443_4a_get_base_data(data), str); +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h new file mode 100644 index 0000000000..fdcd7066ea --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_4a_info( + const Iso14443_4aData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str); + +void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c new file mode 100644 index 0000000000..a0c70a22e9 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c @@ -0,0 +1,118 @@ +#include "iso14443_4b.h" +#include "iso14443_4b_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_3b/iso14443_3b_i.h" + +static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_4b(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4b); + + NfcApp* instance = context; + const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data; + + if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_4b(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_4b, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_4b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) { + UNUSED(instance); +} + +static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEmulate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + return true; + } + + return false; +} + +static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { + return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_read_menu_on_event_iso14443_4b, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4b, + .on_event = nfc_scene_saved_menu_on_event_iso14443_4b, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h new file mode 100644 index 0000000000..84a3456d4d --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c new file mode 100644 index 0000000000..e15b289a08 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c @@ -0,0 +1,10 @@ +#include "iso14443_4b_render.h" + +#include "../iso14443_3b/iso14443_3b_render.h" + +void nfc_render_iso14443_4b_info( + const Iso14443_4bData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_3b_info(iso14443_4b_get_base_data(data), format_type, str); +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h new file mode 100644 index 0000000000..094892f83a --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_4b_info( + const Iso14443_4bData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c new file mode 100644 index 0000000000..7f861a0326 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -0,0 +1,144 @@ +#include "iso15693_3.h" +#include "iso15693_3_render.h" + +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso15693_3); + + NfcApp* instance = context; + const Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso15693_3, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso15693_3(NfcApp* instance) { + UNUSED(instance); + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso15693_3, instance); +} + +static void nfc_scene_read_success_on_enter_iso15693_3(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_emulate_listener_callback_iso15693_3(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso15693_3); + furi_assert(event.event_data); + + NfcApp* nfc = context; + Iso15693_3ListenerEvent* iso15693_3_event = event.event_data; + + if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(iso15693_3_event->data->buffer); i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(iso15693_3_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) { + const Iso15693_3Data* data = nfc_device_get_data(instance->nfc_device, NfcProtocolIso15693_3); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso15693_3, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance); +} + +static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_iso15693_3, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h new file mode 100644 index 0000000000..a26633ee61 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso15693_3; diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c new file mode 100644 index 0000000000..92bdb22dc9 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c @@ -0,0 +1,92 @@ +#include "iso15693_3_render.h" + +#define NFC_RENDER_ISO15693_3_MAX_BYTES (128U) + +void nfc_render_iso15693_3_info( + const Iso15693_3Data* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat(str, "ISO15693-3 (NFC-V)\n"); + } + + nfc_render_iso15693_3_brief(data, str); + + if(format_type == NfcProtocolFormatTypeFull) { + nfc_render_iso15693_3_extra(data, str); + } +} + +void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) { + furi_string_cat_printf(str, "UID:"); + + size_t uid_len; + const uint8_t* uid = iso15693_3_get_uid(data, &uid_len); + + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(str, " %02X", uid[i]); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + const uint16_t block_count = iso15693_3_get_block_count(data); + const uint8_t block_size = iso15693_3_get_block_size(data); + + furi_string_cat_printf(str, "Memory: %u bytes\n", block_count * block_size); + furi_string_cat_printf(str, "(%u blocks x %u bytes)", block_count, block_size); + } +} + +void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { + furi_string_cat(str, "\n\e#General info\n"); + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref); + } + + furi_string_cat(str, "\e#Lock bits\n"); + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf( + str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf( + str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + furi_string_cat(str, "\e#Memory data\n\e*--------------------\n"); + + const uint16_t block_count = iso15693_3_get_block_count(data); + const uint8_t block_size = iso15693_3_get_block_size(data); + const uint16_t display_block_count = + MIN(NFC_RENDER_ISO15693_3_MAX_BYTES / block_size, block_count); + + for(uint32_t i = 0; i < display_block_count; ++i) { + furi_string_cat(str, "\e*"); + + const uint8_t* block_data = iso15693_3_get_block_data(data, i); + for(uint32_t j = 0; j < block_size; ++j) { + furi_string_cat_printf(str, "%02X ", block_data[j]); + } + + const char* lock_str = iso15693_3_is_block_locked(data, i) ? "[LOCK]" : ""; + furi_string_cat_printf(str, "| %s\n", lock_str); + } + + if(block_count != display_block_count) { + furi_string_cat_printf( + str, + "(Data is too big. Showing only the first %u bytes.)", + display_block_count * block_size); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h new file mode 100644 index 0000000000..d531fd2eb0 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso15693_3_info( + const Iso15693_3Data* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str); + +void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c new file mode 100644 index 0000000000..3e0468cd91 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -0,0 +1,252 @@ +#include "mf_classic.h" +#include "mf_classic_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +#define TAG "MfClassicApp" + +enum { + SubmenuIndexDetectReader = SubmenuIndexCommonMax, + SubmenuIndexWrite, + SubmenuIndexUpdate, +}; + +static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_classic_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_mf_classic(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfClassicData* mfc_data = nfc_device_get_data(device, NfcProtocolMfClassic); + + furi_string_reset(instance->text_box_store); + nfc_render_mf_classic_dump(mfc_data, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + +static NfcCommand nfc_scene_read_poller_callback_mf_classic(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcApp* instance = context; + const MfClassicPollerEvent* mfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller)); + size_t uid_len = 0; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + if(mf_classic_key_cache_load(instance->mfc_key_cache, uid, uid_len)) { + FURI_LOG_I(TAG, "Key cache found"); + mfc_event->data->poller_mode.mode = MfClassicPollerModeRead; + } else { + FURI_LOG_I(TAG, "Key cache not found"); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventPollerIncomplete); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) { + uint8_t sector_num = 0; + MfClassicKey key = {}; + MfClassicKeyType key_type = MfClassicKeyTypeA; + if(mf_classic_key_cahce_get_next_key( + instance->mfc_key_cache, §or_num, &key, &key_type)) { + mfc_event->data->read_sector_request_data.sector_num = sector_num; + mfc_event->data->read_sector_request_data.key = key; + mfc_event->data->read_sector_request_data.key_type = key_type; + mfc_event->data->read_sector_request_data.key_provided = true; + } else { + mfc_event->data->read_sector_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller)); + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + NfcCustomEvent custom_event = mf_classic_is_card_read(mfc_data) ? + NfcCustomEventPollerSuccess : + NfcCustomEventPollerIncomplete; + view_dispatcher_send_custom_event(instance->view_dispatcher, custom_event); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) { + mf_classic_key_cache_reset(instance->mfc_key_cache); + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance); +} + +static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) { + if(event == NfcCustomEventPollerIncomplete) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + } + + return true; +} + +static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { + Submenu* submenu = instance->submenu; + const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(!mf_classic_is_card_read(data)) { + submenu_add_item( + submenu, + "Detect Reader", + SubmenuIndexDetectReader, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_classic_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { + Submenu* submenu = instance->submenu; + const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(!mf_classic_is_card_read(data)) { + submenu_add_item( + submenu, + "Detect Reader", + SubmenuIndexDetectReader, + nfc_protocol_support_common_submenu_callback, + instance); + } + submenu_add_item( + submenu, + "Write to Initial Card", + SubmenuIndexWrite, + nfc_protocol_support_common_submenu_callback, + instance); + submenu_add_item( + submenu, + "Update from Initial Card", + SubmenuIndexUpdate, + nfc_protocol_support_common_submenu_callback, + instance); +} + +static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { + const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfClassic, data); + nfc_listener_start(instance->listener, NULL, NULL); +} + +static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); + dolphin_deed(DolphinDeedNfcDetectReader); + return true; + } + + return false; +} + +static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { + bool consumed = false; + + if(event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); + consumed = true; + } else if(event == SubmenuIndexWrite) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); + consumed = true; + } else if(event == SubmenuIndexUpdate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + consumed = true; + } + + return consumed; +} + +static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) { + bool consumed = false; + + if(event == NfcCustomEventTextInputDone) { + mf_classic_key_cache_save( + instance->mfc_key_cache, + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic)); + consumed = true; + } + + return consumed; +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_classic = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_classic, + .on_event = nfc_scene_read_on_event_mf_classic, + }, + .scene_read_menu = + { + .on_enter = nfc_scene_read_menu_on_enter_mf_classic, + .on_event = nfc_scene_read_menu_on_event_mf_classic, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_saved_menu_on_enter_mf_classic, + .on_event = nfc_scene_saved_menu_on_event_mf_classic, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_save_name_on_event_mf_classic, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h new file mode 100644 index 0000000000..d0f09f5d78 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_classic; diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c new file mode 100644 index 0000000000..5bd4a6b6dd --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c @@ -0,0 +1,30 @@ +#include "mf_classic_render.h" + +#include "../iso14443_3a/iso14443_3a_render.h" + +void nfc_render_mf_classic_info( + const MfClassicData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str); + + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + uint8_t keys_total = sectors_total * 2; + uint8_t keys_found = 0; + uint8_t sectors_read = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + + furi_string_cat_printf(str, "\nKeys Found: %u/%u", keys_found, keys_total); + furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total); +} + +void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) { + uint16_t total_blocks = mf_classic_get_total_block_num(data->type); + + for(size_t i = 0; i < total_blocks; i++) { + for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) { + furi_string_cat_printf( + str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h new file mode 100644 index 0000000000..7e1619761f --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_classic_info( + const MfClassicData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c new file mode 100644 index 0000000000..bc05c2a4c3 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -0,0 +1,120 @@ +#include "mf_desfire.h" +#include "mf_desfire_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +static void nfc_scene_info_on_enter_mf_desfire(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) { + // Jump to advanced scene right away + scene_manager_next_scene(instance->scene_manager, NfcSceneMfDesfireMoreInfo); +} + +static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfDesfire); + + NfcApp* instance = context; + const MfDesfirePollerEvent* mf_desfire_event = event.event_data; + + if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_desfire, instance); +} + +static void nfc_scene_read_success_on_enter_mf_desfire(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_mf_desfire(NfcApp* instance) { + const Iso14443_4aData* iso14443_4a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = + nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_desfire = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h new file mode 100644 index 0000000000..504860f16a --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_desfire; diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c new file mode 100644 index 0000000000..883ea720b0 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -0,0 +1,249 @@ +#include "mf_desfire_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_mf_desfire_info( + const MfDesfireData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(mf_desfire_get_base_data(data), str); + + const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); + const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0; + + furi_string_cat_printf(str, "\n%lu", bytes_total); + + if(data->version.sw_storage & 1) { + furi_string_push_back(str, '+'); + } + + furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free); + + const uint32_t app_count = simple_array_get_count(data->applications); + uint32_t file_count = 0; + + for(uint32_t i = 0; i < app_count; ++i) { + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + file_count += simple_array_get_count(app->file_ids); + } + + furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : ""); + furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(mf_desfire_get_base_data(data), str); +} + +void nfc_render_mf_desfire_data(const MfDesfireData* data, FuriString* str) { + nfc_render_mf_desfire_version(&data->version, str); + nfc_render_mf_desfire_free_memory(&data->free_memory, str); + nfc_render_mf_desfire_key_settings(&data->master_key_settings, str); + + for(uint32_t i = 0; i < simple_array_get_count(data->master_key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->master_key_versions, i), i, str); + } +} + +void nfc_render_mf_desfire_version(const MfDesfireVersion* data, FuriString* str) { + furi_string_cat_printf( + str, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + data->uid[0], + data->uid[1], + data->uid[2], + data->uid[3], + data->uid[4], + data->uid[5], + data->uid[6]); + furi_string_cat_printf( + str, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->hw_vendor, + data->hw_type, + data->hw_subtype, + data->hw_major, + data->hw_minor, + data->hw_storage, + data->hw_proto); + furi_string_cat_printf( + str, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->sw_vendor, + data->sw_type, + data->sw_subtype, + data->sw_major, + data->sw_minor, + data->sw_storage, + data->sw_proto); + furi_string_cat_printf( + str, + "batch %02x:%02x:%02x:%02x:%02x\n" + "week %d year %d\n", + data->batch[0], + data->batch[1], + data->batch[2], + data->batch[3], + data->batch[4], + data->prod_week, + data->prod_year); +} + +void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriString* str) { + if(data->is_present) { + furi_string_cat_printf(str, "freeMem %lu\n", data->bytes_free); + } +} + +void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) { + furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); + furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); + furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); + furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); + furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + + if(data->flags) { + furi_string_cat_printf(str, "flags %d\n", data->flags); + } + + furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); +} + +void nfc_render_mf_desfire_key_version( + const MfDesfireKeyVersion* data, + uint32_t index, + FuriString* str) { + furi_string_cat_printf(str, "key %lu version %u\n", index, *data); +} + +void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) { + const uint8_t* app_id = data->data; + furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]); +} + +void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) { + nfc_render_mf_desfire_key_settings(&data->key_settings, str); + + for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + } +} + +void nfc_render_mf_desfire_file_id(const MfDesfireFileId* data, FuriString* str) { + furi_string_cat_printf(str, "File %d\n", *data); +} + +void nfc_render_mf_desfire_file_settings_data( + const MfDesfireFileSettings* settings, + const MfDesfireFileData* data, + FuriString* str) { + const char* type; + switch(settings->type) { + case MfDesfireFileTypeStandard: + type = "standard"; + break; + case MfDesfireFileTypeBackup: + type = "backup"; + break; + case MfDesfireFileTypeValue: + type = "value"; + break; + case MfDesfireFileTypeLinearRecord: + type = "linear"; + break; + case MfDesfireFileTypeCyclicRecord: + type = "cyclic"; + break; + default: + type = "unknown"; + } + + const char* comm; + switch(settings->comm) { + case MfDesfireFileCommunicationSettingsPlaintext: + comm = "plain"; + break; + case MfDesfireFileCommunicationSettingsAuthenticated: + comm = "auth"; + break; + case MfDesfireFileCommunicationSettingsEnciphered: + comm = "enciphered"; + break; + default: + comm = "unknown"; + } + + furi_string_cat_printf(str, "%s %s\n", type, comm); + furi_string_cat_printf( + str, + "r %d w %d rw %d c %d\n", + settings->access_rights >> 12 & 0xF, + settings->access_rights >> 8 & 0xF, + settings->access_rights >> 4 & 0xF, + settings->access_rights & 0xF); + + uint32_t record_count = 1; + uint32_t record_size = 0; + + switch(settings->type) { + case MfDesfireFileTypeStandard: + case MfDesfireFileTypeBackup: + record_size = settings->data.size; + furi_string_cat_printf(str, "size %lu\n", record_size); + break; + case MfDesfireFileTypeValue: + furi_string_cat_printf( + str, "lo %lu hi %lu\n", settings->value.lo_limit, settings->value.hi_limit); + furi_string_cat_printf( + str, + "limit %lu enabled %d\n", + settings->value.limited_credit_value, + settings->value.limited_credit_enabled); + break; + case MfDesfireFileTypeLinearRecord: + case MfDesfireFileTypeCyclicRecord: + record_count = settings->record.cur; + record_size = settings->record.size; + furi_string_cat_printf(str, "size %lu\n", record_size); + furi_string_cat_printf(str, "num %lu max %lu\n", record_count, settings->record.max); + break; + } + + if(simple_array_get_count(data->data) == 0) { + return; + } + + for(uint32_t rec = 0; rec < record_count; rec++) { + furi_string_cat_printf(str, "record %lu\n", rec); + for(uint32_t ch = 0; ch < record_size; ch += 4) { + furi_string_cat_printf(str, "%03lx|", ch); + for(uint32_t i = 0; i < 4; i++) { + if(ch + i < record_size) { + const uint32_t data_index = rec * record_size + ch + i; + const uint8_t data_byte = + *(const uint8_t*)simple_array_cget(data->data, data_index); + furi_string_cat_printf(str, "%02x ", data_byte); + } else { + furi_string_cat_printf(str, " "); + } + } + for(uint32_t i = 0; i < 4 && ch + i < record_size; i++) { + const uint32_t data_index = rec * record_size + ch + i; + const uint8_t data_byte = + *(const uint8_t*)simple_array_cget(data->data, data_index); + if(isprint(data_byte)) { + furi_string_cat_printf(str, "%c", data_byte); + } else { + furi_string_cat_printf(str, "."); + } + } + furi_string_push_back(str, '\n'); + } + furi_string_push_back(str, '\n'); + } +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h new file mode 100644 index 0000000000..ff5e815bff --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_desfire_info( + const MfDesfireData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_desfire_data(const MfDesfireData* data, FuriString* str); + +void nfc_render_mf_desfire_version(const MfDesfireVersion* data, FuriString* str); + +void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriString* str); + +void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str); + +void nfc_render_mf_desfire_key_version( + const MfDesfireKeyVersion* data, + uint32_t index, + FuriString* str); + +void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str); + +void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str); + +void nfc_render_mf_desfire_file_id(const MfDesfireFileId* data, FuriString* str); + +void nfc_render_mf_desfire_file_settings_data( + const MfDesfireFileSettings* settings, + const MfDesfireFileData* data, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c new file mode 100644 index 0000000000..c4fd04c7e5 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -0,0 +1,196 @@ +#include "mf_ultralight.h" +#include "mf_ultralight_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +enum { + SubmenuIndexUnlock = SubmenuIndexCommonMax, + SubmenuIndexUnlockByReader, + SubmenuIndexUnlockByPassword, +}; + +static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight); + + furi_string_reset(instance->text_box_store); + nfc_render_mf_ultralight_dump(mfu, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + +static NfcCommand + nfc_scene_read_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfUltralight); + + NfcApp* instance = context; + const MfUltralightPollerEvent* mf_ultralight_event = event.event_data; + + if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + if(instance->mf_ul_auth->type == MfUltralightAuthTypeXiaomi) { + if(mf_ultralight_generate_xiaomi_pass( + instance->mf_ul_auth, + data->iso14443_3a_data->uid, + data->iso14443_3a_data->uid_len)) { + mf_ultralight_event->data->auth_context.skip_auth = false; + } + } else if(instance->mf_ul_auth->type == MfUltralightAuthTypeAmiibo) { + if(mf_ultralight_generate_amiibo_pass( + instance->mf_ul_auth, + data->iso14443_3a_data->uid, + data->iso14443_3a_data->uid_len)) { + mf_ultralight_event->data->auth_context.skip_auth = false; + } + } else if( + instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->auth_context.skip_auth = false; + } else { + mf_ultralight_event->data->auth_context.skip_auth = true; + } + if(!mf_ultralight_event->data->auth_context.skip_auth) { + mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; + } + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { + instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); +} + +static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { + Submenu* submenu = instance->submenu; + + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + + if(!mf_ultralight_is_all_data_read(data)) { + submenu_add_item( + submenu, + "Unlock", + SubmenuIndexUnlock, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static void nfc_scene_read_success_on_enter_mf_ultralight(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + FuriString* temp_str = furi_string_alloc(); + + bool unlocked = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); + if(unlocked) { + nfc_render_mf_ultralight_pwd_pack(data, temp_str); + } else { + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + + nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeShort, temp_str); + } + + mf_ultralight_auth_reset(instance->mf_ul_auth); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) { + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfUltralight, data); + nfc_listener_start(instance->listener, NULL, NULL); +} + +static bool + nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); + return true; + } + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_scene_read_and_saved_menu_on_enter_mf_ultralight, + .on_event = nfc_scene_read_and_saved_menu_on_event_mf_ultralight, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_read_and_saved_menu_on_enter_mf_ultralight, + .on_event = nfc_scene_read_and_saved_menu_on_event_mf_ultralight, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h new file mode 100644 index 0000000000..83e58732e4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight; diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c new file mode 100644 index 0000000000..5296f48071 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -0,0 +1,45 @@ +#include "mf_ultralight_render.h" + +#include "../iso14443_3a/iso14443_3a_render.h" + +static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, FuriString* str) { + furi_string_cat_printf(str, "\nPages Read: %u/%u", data->pages_read, data->pages_total); + if(data->pages_read != data->pages_total) { + furi_string_cat_printf(str, "\nPassword-protected pages!"); + } +} + +void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) { + bool all_pages = mf_ultralight_is_all_data_read(data); + furi_string_cat_printf(str, "\e#%s pages unlocked!", all_pages ? "All" : "Not all"); + + MfUltralightConfigPages* config; + mf_ultralight_get_config_page(data, &config); + + furi_string_cat_printf(str, "\nPassword: "); + nfc_render_iso14443_3a_format_bytes( + str, config->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); + + furi_string_cat_printf(str, "\nPACK: "); + nfc_render_iso14443_3a_format_bytes(str, config->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + + nfc_render_mf_ultralight_pages_count(data, str); +} + +void nfc_render_mf_ultralight_info( + const MfUltralightData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str); + + nfc_render_mf_ultralight_pages_count(data, str); +} + +void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) { + for(size_t i = 0; i < data->pages_read; i++) { + const uint8_t* page_data = data->page[i].data; + for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) { + furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h new file mode 100644 index 0000000000..5b15bb5911 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_ultralight_info( + const MfUltralightData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str); + +void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c new file mode 100644 index 0000000000..bf6c0842fe --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -0,0 +1,786 @@ +/** + * @file nfc_protocol_support.c + * @brief Common implementation of application-level protocol support. + * + * @see nfc_protocol_support_base.h + * @see nfc_protocol_support_common.h + */ +#include "nfc_protocol_support.h" + +#include "nfc/nfc_app_i.h" +#include "nfc/helpers/nfc_supported_cards.h" + +#include "nfc_protocol_support_defs.h" +#include "nfc_protocol_support_gui_common.h" + +/** + * @brief Common scene entry handler. + * + * @param[in,out] instance pointer to the NFC application instance. + */ +typedef void (*NfcProtocolSupportCommonOnEnter)(NfcApp* instance); + +/** + * @brief Common scene custom event handler. + * + * @param[in,out] instance pointer to the NFC application instance. + * @param[in] event custom event to be handled. + * @returns true if the event was handled, false otherwise. + */ +typedef bool (*NfcProtocolSupportCommonOnEvent)(NfcApp* instance, SceneManagerEvent event); + +/** + * @brief Common scene exit handler. + * + * @param[in,out] instance pointer to the NFC application instance. + */ +typedef void (*NfcProtocolSupportCommonOnExit)(NfcApp* instance); + +/** + * @brief Structure containing common scene handler pointers. + */ +typedef struct { + NfcProtocolSupportCommonOnEnter on_enter; /**< Pointer to the on_enter() function. */ + NfcProtocolSupportCommonOnEvent on_event; /**< Pointer to the on_event() function. */ + NfcProtocolSupportCommonOnExit on_exit; /**< Pointer to the on_exit() function. */ +} NfcProtocolSupportCommonSceneBase; + +static const NfcProtocolSupportCommonSceneBase nfc_protocol_support_scenes[]; + +// Interface functions +void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context) { + furi_assert(scene < NfcProtocolSupportSceneCount); + furi_assert(context); + + NfcApp* instance = context; + nfc_protocol_support_scenes[scene].on_enter(instance); +} + +bool nfc_protocol_support_on_event( + NfcProtocolSupportScene scene, + void* context, + SceneManagerEvent event) { + furi_assert(scene < NfcProtocolSupportSceneCount); + furi_assert(context); + + NfcApp* instance = context; + return nfc_protocol_support_scenes[scene].on_event(instance, event); +} + +void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) { + furi_assert(scene < NfcProtocolSupportSceneCount); + furi_assert(context); + + NfcApp* instance = context; + nfc_protocol_support_scenes[scene].on_exit(instance); +} + +static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { + return nfc_protocol_support[protocol]->features & feature; +} + +// Common scene handlers +// SceneInfo +static void nfc_protocol_support_scene_info_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_info.on_enter(instance); + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureMoreInfo)) { + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +static bool nfc_protocol_support_scene_info_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + // If the card could not be parsed, return to the respective menu + if(!scene_manager_get_scene_state(instance->scene_manager, NfcSceneSupportedCard)) { + const uint32_t scenes[] = {NfcSceneSavedMenu, NfcSceneReadMenu}; + scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, scenes, COUNT_OF(scenes)); + consumed = true; + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_info_on_exit(NfcApp* instance) { + widget_reset(instance->widget); +} + +// SceneMoreInfo +static void nfc_protocol_support_scene_more_info_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_more_info.on_enter(instance); +} + +static bool + nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event); + } + + return consumed; +} + +static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) { + text_box_reset(instance->text_box); + furi_string_reset(instance->text_box_store); +} + +// SceneRead +static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { + popup_set_header( + instance->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 23, &A_Loading_24); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + instance->poller = nfc_poller_alloc(instance->nfc, protocol); + + // Start poller with the appropriate callback + nfc_protocol_support[protocol]->scene_read.on_enter(instance); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + nfc_blink_detect_start(instance); +} + +static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventPollerSuccess) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else if(event.event == NfcCustomEventPollerIncomplete) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + bool card_read = nfc_supported_cards_read(instance->nfc_device, instance->nfc); + if(card_read) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else { + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + consumed = + nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); + } + } else if(event.event == NfcCustomEventPollerFailure) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDetect)) { + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneDetect); + } + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + static const uint32_t possible_scenes[] = {NfcSceneSelectProtocol, NfcSceneStart}; + scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); + consumed = true; + } + + return consumed; +} + +static void nfc_protocol_support_scene_read_on_exit(NfcApp* instance) { + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} + +// SceneReadMenu +static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + Submenu* submenu = instance->submenu; + + submenu_add_item( + submenu, + "Save", + SubmenuIndexCommonSave, + nfc_protocol_support_common_submenu_callback, + instance); + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + submenu_add_item( + submenu, + "Emulate UID", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + + } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + submenu_add_item( + submenu, + "Emulate", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + } + + nfc_protocol_support[protocol]->scene_read_menu.on_enter(instance); + + submenu_add_item( + submenu, + "Info", + SubmenuIndexCommonInfo, + nfc_protocol_support_common_submenu_callback, + instance); + + submenu_set_selected_item( + instance->submenu, + scene_manager_get_scene_state(instance->scene_manager, NfcSceneReadMenu)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +static bool + nfc_protocol_support_scene_read_menu_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneReadMenu, event.event); + + if(event.event == SubmenuIndexCommonSave) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexCommonInfo) { + scene_manager_next_scene(instance->scene_manager, NfcSceneInfo); + consumed = true; + } else if(event.event == SubmenuIndexCommonEmulate) { + dolphin_deed(DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + consumed = true; + } else { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = + nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event); + } + + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0); + } + + return consumed; +} + +// Same for read_menu and saved_menu +static void nfc_protocol_support_scene_read_saved_menu_on_exit(NfcApp* instance) { + submenu_reset(instance->submenu); +} + +// SceneReadSuccess +static void nfc_protocol_support_scene_read_success_on_enter(NfcApp* instance) { + Widget* widget = instance->widget; + + FuriString* temp_str = furi_string_alloc(); + if(nfc_supported_cards_parse(instance->nfc_device, temp_str)) { + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + } else { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_read_success.on_enter(instance); + } + + furi_string_free(temp_str); + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_protocol_support_common_widget_callback, instance); + widget_add_button_element( + widget, GuiButtonTypeRight, "More", nfc_protocol_support_common_widget_callback, instance); + + notification_message_block(instance->notifications, &sequence_set_green_255); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +static bool + nfc_protocol_support_scene_read_success_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + scene_manager_next_scene(instance->scene_manager, NfcSceneRetryConfirm); + consumed = true; + + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu); + consumed = true; + } + + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + + return consumed; +} + +static void nfc_protocol_support_scene_read_success_on_exit(NfcApp* instance) { + notification_message_block(instance->notifications, &sequence_reset_green); + widget_reset(instance->widget); +} + +// SceneSavedMenu +static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + Submenu* submenu = instance->submenu; + + // Header submenu items + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + submenu_add_item( + submenu, + "Emulate UID", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + + } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + submenu_add_item( + submenu, + "Emulate", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + } + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEditUid)) { + submenu_add_item( + submenu, + "Edit UID", + SubmenuIndexCommonEdit, + nfc_protocol_support_common_submenu_callback, + instance); + } + + // Protocol-dependent menu items + nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance); + + // Trailer submenu items + submenu_add_item( + submenu, + "Info", + SubmenuIndexCommonInfo, + nfc_protocol_support_common_submenu_callback, + instance); + submenu_add_item( + submenu, + "Rename", + SubmenuIndexCommonRename, + nfc_protocol_support_common_submenu_callback, + instance); + submenu_add_item( + submenu, + "Delete", + SubmenuIndexCommonDelete, + nfc_protocol_support_common_submenu_callback, + instance); + + if(nfc_has_shadow_file(instance)) { + submenu_add_item( + submenu, + "Restore Data Changes", + SubmenuIndexCommonRestore, + nfc_protocol_support_common_submenu_callback, + instance); + } + + submenu_set_selected_item( + instance->submenu, + scene_manager_get_scene_state(instance->scene_manager, NfcSceneSavedMenu)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +static bool + nfc_protocol_support_scene_saved_menu_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, event.event); + + if(event.event == SubmenuIndexCommonRestore) { + scene_manager_next_scene(instance->scene_manager, NfcSceneRestoreOriginalConfirm); + consumed = true; + } else if(event.event == SubmenuIndexCommonInfo) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSupportedCard); + consumed = true; + } else if(event.event == SubmenuIndexCommonRename) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexCommonDelete) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDelete); + consumed = true; + } else if(event.event == SubmenuIndexCommonEmulate) { + const bool is_added = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType); + dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; + } else { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = + nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event); + } + + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0); + } + + return consumed; +} + +// SceneSaveName + +static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) { + FuriString* folder_path = furi_string_alloc(); + TextInput* text_input = instance->text_input; + + bool name_is_empty = furi_string_empty(instance->file_name); + if(name_is_empty) { + furi_string_set(instance->file_path, NFC_APP_FOLDER); + name_generator_make_auto( + instance->text_store, NFC_TEXT_STORE_SIZE, NFC_APP_FILENAME_PREFIX); + furi_string_set(folder_path, NFC_APP_FOLDER); + } else { + nfc_text_store_set(instance, "%s", furi_string_get_cstr(instance->file_name)); + path_extract_dirname(furi_string_get_cstr(instance->file_path), folder_path); + } + + text_input_set_header_text(text_input, "Name the card"); + text_input_set_result_callback( + text_input, + nfc_protocol_support_common_text_input_done_callback, + instance, + instance->text_store, + NFC_NAME_SIZE, + name_is_empty); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + furi_string_get_cstr(folder_path), + NFC_APP_EXTENSION, + furi_string_get_cstr(instance->file_name)); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + furi_string_free(folder_path); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextInput); +} + +static bool + nfc_protocol_support_scene_save_name_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventTextInputDone) { + if(!furi_string_empty(instance->file_name)) { + nfc_delete(instance); + } + furi_string_set(instance->file_name, instance->text_store); + + if(nfc_save(instance)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed( + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType) ? + DolphinDeedNfcAddSave : + DolphinDeedNfcSave); + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + consumed = nfc_protocol_support[protocol]->scene_save_name.on_event( + instance, event.event); + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) { + void* validator_context = text_input_get_validator_callback_context(instance->text_input); + text_input_set_validator(instance->text_input, NULL, NULL); + validator_is_file_free(validator_context); + + text_input_reset(instance->text_input); +} + +// SceneEmulate +/** + * @brief Current view displayed on the emulation scene. + * + * The emulation scehe has two states: the default one showing information about + * the card being emulated, and the logs which show the raw data received from the reader. + * + * The user has the ability to switch betweeen these two scenes, however the prompt to switch is + * only shown after some information had appered in the log view. + */ +enum { + NfcSceneEmulateStateWidget, /**< Widget view is displayed. */ + NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */ +}; + +static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { + Widget* widget = instance->widget; + TextBox* text_box = instance->text_box; + + FuriString* temp_str = furi_string_alloc(); + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + widget_add_string_element( + widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID"); + + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + + for(size_t i = 0; i < uid_len; ++i) { + furi_string_cat_printf(temp_str, "%02X ", uid[i]); + } + + furi_string_trim(temp_str); + + } else { + widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating"); + furi_string_set( + temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + } + + widget_add_text_box_element( + widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false); + + furi_string_free(temp_str); + + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + furi_string_reset(instance->text_box_store); + + // instance->listener is allocated in the respective on_enter() handler + nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + nfc_blink_emulate_start(instance); +} + +static bool + nfc_protocol_support_scene_emulate_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + const uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneEmulate); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventListenerUpdate) { + // Add data button to widget if data is received for the first time + if(furi_string_size(instance->text_box_store)) { + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "Log", + nfc_protocol_support_common_widget_callback, + instance); + } + // Update TextBox data + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + if(state == NfcSceneEmulateStateWidget) { + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox); + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneEmulateStateTextBox) { + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + consumed = true; + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_emulate_stop_listener(NfcApp* instance) { + nfc_listener_stop(instance->listener); + + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + if(protocol == nfc_listener_get_protocol(instance->listener)) { + const NfcDeviceData* data = nfc_listener_get_data(instance->listener, protocol); + + if(!nfc_device_is_equal_data(instance->nfc_device, protocol, data)) { + nfc_device_set_data(instance->nfc_device, protocol, data); + nfc_save_shadow_file(instance); + } + } + + nfc_listener_free(instance->listener); +} + +static void nfc_protocol_support_scene_emulate_on_exit(NfcApp* instance) { + nfc_protocol_support_scene_emulate_stop_listener(instance); + + // Clear view + widget_reset(instance->widget); + text_box_reset(instance->text_box); + furi_string_reset(instance->text_box_store); + + nfc_blink_stop(instance); +} + +static void nfc_protocol_support_scene_rpc_on_enter(NfcApp* instance) { + UNUSED(instance); +} + +static void nfc_protocol_support_scene_rpc_setup_ui_and_emulate(NfcApp* instance) { + nfc_text_store_set(instance, "emulating\n%s", furi_string_get_cstr(instance->file_name)); + + popup_set_header(instance->popup, "NFC", 89, 42, AlignCenter, AlignBottom); + popup_set_text(instance->popup, instance->text_store, 89, 44, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 0, 12, &I_RFIDDolphinSend_97x61); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + notification_message(instance->notifications, &sequence_display_backlight_on); + nfc_blink_emulate_start(instance); + + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + + instance->rpc_state = NfcRpcStateEmulating; +} + +static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventRpcLoad && instance->rpc_state == NfcRpcStateIdle) { + furi_string_set(instance->file_path, rpc_system_app_get_data(instance->rpc_ctx)); + const bool load_success = nfc_load_file(instance, instance->file_path, false); + if(load_success) { + nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); + } + rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventLoadFile, load_success); + } else if(event.event == NfcCustomEventRpcExit) { + rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventAppExit, true); + scene_manager_stop(instance->scene_manager); + view_dispatcher_stop(instance->view_dispatcher); + } else if(event.event == NfcCustomEventRpcSessionClose) { + scene_manager_stop(instance->scene_manager); + view_dispatcher_stop(instance->view_dispatcher); + } + consumed = true; + } + + return consumed; +} + +static void nfc_protocol_support_scene_rpc_on_exit(NfcApp* instance) { + if(instance->rpc_state == NfcRpcStateEmulating) { + nfc_protocol_support_scene_emulate_stop_listener(instance); + } + + popup_reset(instance->popup); + text_box_reset(instance->text_box); + furi_string_reset(instance->text_box_store); + + nfc_blink_stop(instance); +} + +static const NfcProtocolSupportCommonSceneBase + nfc_protocol_support_scenes[NfcProtocolSupportSceneCount] = { + [NfcProtocolSupportSceneInfo] = + { + .on_enter = nfc_protocol_support_scene_info_on_enter, + .on_event = nfc_protocol_support_scene_info_on_event, + .on_exit = nfc_protocol_support_scene_info_on_exit, + }, + [NfcProtocolSupportSceneMoreInfo] = + { + .on_enter = nfc_protocol_support_scene_more_info_on_enter, + .on_event = nfc_protocol_support_scene_more_info_on_event, + .on_exit = nfc_protocol_support_scene_more_info_on_exit, + }, + [NfcProtocolSupportSceneRead] = + { + .on_enter = nfc_protocol_support_scene_read_on_enter, + .on_event = nfc_protocol_support_scene_read_on_event, + .on_exit = nfc_protocol_support_scene_read_on_exit, + }, + [NfcProtocolSupportSceneReadMenu] = + { + .on_enter = nfc_protocol_support_scene_read_menu_on_enter, + .on_event = nfc_protocol_support_scene_read_menu_on_event, + .on_exit = nfc_protocol_support_scene_read_saved_menu_on_exit, + }, + [NfcProtocolSupportSceneReadSuccess] = + { + .on_enter = nfc_protocol_support_scene_read_success_on_enter, + .on_event = nfc_protocol_support_scene_read_success_on_event, + .on_exit = nfc_protocol_support_scene_read_success_on_exit, + }, + [NfcProtocolSupportSceneSavedMenu] = + { + .on_enter = nfc_protocol_support_scene_saved_menu_on_enter, + .on_event = nfc_protocol_support_scene_saved_menu_on_event, + .on_exit = nfc_protocol_support_scene_read_saved_menu_on_exit, + }, + [NfcProtocolSupportSceneSaveName] = + { + .on_enter = nfc_protocol_support_scene_save_name_on_enter, + .on_event = nfc_protocol_support_scene_save_name_on_event, + .on_exit = nfc_protocol_support_scene_save_name_on_exit, + }, + [NfcProtocolSupportSceneEmulate] = + { + .on_enter = nfc_protocol_support_scene_emulate_on_enter, + .on_event = nfc_protocol_support_scene_emulate_on_event, + .on_exit = nfc_protocol_support_scene_emulate_on_exit, + }, + [NfcProtocolSupportSceneRpc] = + { + .on_enter = nfc_protocol_support_scene_rpc_on_enter, + .on_event = nfc_protocol_support_scene_rpc_on_event, + .on_exit = nfc_protocol_support_scene_rpc_on_exit, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h new file mode 100644 index 0000000000..b6bfde45c0 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h @@ -0,0 +1,113 @@ +/** + * @file nfc_protocol_support.h + * @brief Interface for application-level protocol support. + * + * NFC protocol support helper abstracts common scenes with a single interface + * and lets each protocol decide on concrete implementation. + * + * # Integrating a new protocol into the application + * + * Most of the scenes in the NFC application work through abstract APIs, so they do not need + * protocol-specific versions of themselves. However, when such a situation + * occurs, the protocol support helper provides another level of abstraction to hide + * the protocol-specific details and isolate them to separate modules. + * + * @see nfc_protocol.h for more information on adding library protocols. + * + * The steps for adding support for a library protocol are described below. + * + * ## 1. Create the files + * + * ### 1.1 Recommended file structure + * + * The recommended file structure for a protocol support is as follows: + * + * ```text + * protocol_support + * | + * +- protocol_name + * | + * +- protocol_name.h + * | + * +- protocol_name.c + * | + * +- protocol_name_render.h + * | + * +- protocol_name_render.c + * | + * ``` + * ### 1.2 File structure explanation + * + * | Filename | Explanation | + * |:-----------------------|:------------| + * | protocol_name.h | Interface structure declaration used in `nfc_protocol_support_defs.c`. | + * | protocol_name.c | Protocol-specific scene implemenatations and definitions. | + * | protocol_name_render.h | Protocol-specific rendering (formatting) functions. Used for converting protocol data into textual descriptions. | + * | protocol_name_render.c | Implementations for functions declared in `protocol_name_render.h`.| + * + * ## 2. Implement the code + * + * ### 2.1 Features + * + * Decide what features the protocol will be providing. The features can be combined using bitwise OR (`"|"`). + * This choice influences which scenes will have to be implemented in step 2.2. + * + * @see NfcProtocolFeature for the enumeration of possible features to implement. + * + * ### 2.2 Scenes + * + * If a particular scene is not implemented, its empty placeholder from nfc_protocol_support_gui_common.h must be used instead. + * + * @see nfc_protocol_support_common.h for the enumeration of all scenes that can be implemented. + * @see nfc_protocol_support_base.h for the scene implementation details. + * + * ### 2.3. Registering the protocol support + * + * After completing the protocol support, it must be registered within the application in order for it to be usable. + * + * In nfc_protocol_support_defs.c, include the `protocol_name.h` file and add a new entry in the `nfc_protocol_support[]` + * array under the appropriate index. + * + * ## Done! + * + * @note It will not always be possible to abstract all of the protocol's functionality using the protocol support helper. + * In such cases, creating separate protocol-specific scenes is okay (as an example, note the `nfc/scenes/nfc_scene_mf_classic_*` scenes which didn't fit this paradigm). + */ +#pragma once + +#include + +#include "nfc_protocol_support_common.h" + +/** + * @brief Abstract interface for on_enter() scene handler. + * + * Is to be called whenever a scene is entered to. + * + * @param[in] scene identifier of the scene associated with the handler. + * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). + */ +void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context); + +/** + * @brief Abstract interface for on_event() scene handler. + * + * @param[in] scene identifier of the scene associated with the handler. + * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). + * @param[in] event SceneManager event to be handled by the scene. + * @returns true if the event was consumed, false otherwise. + */ +bool nfc_protocol_support_on_event( + NfcProtocolSupportScene scene, + void* context, + SceneManagerEvent event); + +/** + * @brief Abstract interface for on_exit() scene handler. + * + * Is to be called whenever a scene is exited from. + * + * @param[in] scene identifier of the scene associated with the handler. + * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). + */ +void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h new file mode 100644 index 0000000000..69a6d34d29 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h @@ -0,0 +1,116 @@ +/** + * @file nfc_protocol_support_base.h + * @brief Abstract interface for application-level protocol support. + */ +#pragma once + +#include + +#include "../../nfc_app.h" + +/** + * @brief Scene entry handler. + * + * @param[in,out] instance pointer to the NFC application instance. + */ +typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance); + +/** + * @brief Scene event handler. + * + * @param[in,out] instance pointer to the NFC application instance. + * @param[in] event custom event that has occurred. + * @returns true if the event was handled, false otherwise. + */ +typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event); + +/** + * @brief Abstract scene interface. + * + * on_exit() handler is not implemented due to being redundant. + */ +typedef struct { + NfcProtocolSupportOnEnter on_enter; /**< Pointer to the on_enter() function. */ + NfcProtocolSupportOnEvent on_event; /**< Pointer to the on_event() function. */ +} NfcProtocolSupportSceneBase; + +/** + * @brief Abstract protocol support interface. + */ +typedef struct { + const uint32_t features; /**< Feature bitmask supported by the protocol. */ + + /** + * @brief Handlers for protocol-specific info scene. + * + * This scene displays general information about a saved or recently read card. + * It may include a button that will lead to more information being shown. + */ + NfcProtocolSupportSceneBase scene_info; + + /** + * @brief Handlers for protocol-specific extended info scene. + * + * This scene shows more information about a saved or + * recently read card, such as memory dumps. + * + * It may include (a) button(s) and/or menu(s) that will lead to + * protocol-specific scenes not covered in this helper. + */ + NfcProtocolSupportSceneBase scene_more_info; + + /** + * @brief Handlers for protocol-specific read scene. + * + * This scene is activated when a read operation is in progress. + * It is responsible for creating a poller and for handling its events. + */ + NfcProtocolSupportSceneBase scene_read; + + /** + * @brief Handlers for protocol-specific read menu scene. + * + * This scene presents the user with options available for the + * recenly read card. Such options may include: + * * Saving + * * Getting information + * * Emulating etc. + */ + NfcProtocolSupportSceneBase scene_read_menu; + + /** + * @brief Handlers for protocol-specific read success scene. + * + * This scene is activated after a successful read operation. + * It is responsible for displaying a very short summary about + * the card that was just read. + */ + NfcProtocolSupportSceneBase scene_read_success; + + /** + * @brief Handlers for protocol-specific saved file menu scene. + * + * This scene presents the user with options available for a + * card loaded from file. Such options may include: + * * Renaming + * * Deleting + * * Getting information + * * Emulating etc. + */ + NfcProtocolSupportSceneBase scene_saved_menu; + + /** + * @brief Handlers for protocol-specific name entry scene. + * + * This scene is used to enter a file name when saving or renaming a file. + */ + NfcProtocolSupportSceneBase scene_save_name; + + /** + * @brief Handlers for protocol-specific emulate scene. + * + * This scene is activated when an emulation operation is in progress. + * It is responsible for creating a listener and for handling its events. + */ + NfcProtocolSupportSceneBase scene_emulate; +} NfcProtocolSupportBase; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h new file mode 100644 index 0000000000..6e3214106a --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h @@ -0,0 +1,36 @@ +/** + * @file nfc_protocol_support_common.h + * @brief Common application-level protocol support definitions. + */ +#pragma once + +/** + * @brief Enumeration of protocol features. + */ +typedef enum { + NfcProtocolFeatureNone = 0, /**< No features are supported. */ + NfcProtocolFeatureEmulateUid = 1UL << 0, /**< Partial emulation is supported. */ + NfcProtocolFeatureEmulateFull = 1UL << 1, /**< Complete emulation is supported. */ + NfcProtocolFeatureEditUid = 1UL << 2, /**< UID editing is supported. */ + NfcProtocolFeatureMoreInfo = 1UL << 3, /**< More information is provided. */ +} NfcProtocolFeature; + +/** + * @brief Enumeration of protocol-aware scenes. + * + * These are the scenes that are common to all protocols, but require + * a protocol-specific implementation. + */ +typedef enum { + NfcProtocolSupportSceneInfo, /**< Display general card information. */ + NfcProtocolSupportSceneMoreInfo, /**< Display more card information. */ + NfcProtocolSupportSceneRead, /**< Shown when reading a card. */ + NfcProtocolSupportSceneReadMenu, /**< Menu with options available for the recently read card. */ + NfcProtocolSupportSceneReadSuccess, /**< Shown after having successfully read a card. */ + NfcProtocolSupportSceneSavedMenu, /**< Menu for the card that was loaded from file. */ + NfcProtocolSupportSceneSaveName, /**< Shown when saving or renaming a file. */ + NfcProtocolSupportSceneEmulate, /**< Shown when emulating a card. */ + NfcProtocolSupportSceneRpc, /**< Shown in remote-controlled (RPC) mode. */ + + NfcProtocolSupportSceneCount, /**< Special value equal to total scene count. Internal use. */ +} NfcProtocolSupportScene; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c new file mode 100644 index 0000000000..215ffc4553 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -0,0 +1,45 @@ +/** + * @file nfc_protocol_support_defs.c + * @brief Application-level protocol support definitions. + * + * This file is to be modified whenever support for + * a new protocol is to be added. + */ +#include "nfc_protocol_support_base.h" + +#include + +#include "iso14443_3a/iso14443_3a.h" +#include "iso14443_3b/iso14443_3b.h" +#include "iso14443_4a/iso14443_4a.h" +#include "iso14443_4b/iso14443_4b.h" +#include "iso15693_3/iso15693_3.h" +#include "felica/felica.h" +#include "mf_ultralight/mf_ultralight.h" +#include "mf_classic/mf_classic.h" +#include "mf_desfire/mf_desfire.h" +#include "slix/slix.h" +#include "st25tb/st25tb.h" + +/** + * @brief Array of pointers to concrete protocol support implementations. + * + * When adding support for a new protocol, add it to the end of this array + * under its respective index. + * + * @see nfc_protocol.h + */ +const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_protocol_support_iso14443_3a, + [NfcProtocolIso14443_3b] = &nfc_protocol_support_iso14443_3b, + [NfcProtocolIso14443_4a] = &nfc_protocol_support_iso14443_4a, + [NfcProtocolIso14443_4b] = &nfc_protocol_support_iso14443_4b, + [NfcProtocolIso15693_3] = &nfc_protocol_support_iso15693_3, + [NfcProtocolFelica] = &nfc_protocol_support_felica, + [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, + [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, + [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, + [NfcProtocolSlix] = &nfc_protocol_support_slix, + [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, + /* Add new protocol support implementations here */ +}; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h new file mode 100644 index 0000000000..7a9d5b6374 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h @@ -0,0 +1,12 @@ +/** + * @file nfc_protocol_support_defs.h + * @brief Application-level protocol support declarations. + */ +#pragma once + +#include "nfc_protocol_support_base.h" + +/** + * @brief Declaraion of array of pointers to protocol support implementations. + */ +extern const NfcProtocolSupportBase* nfc_protocol_support[]; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c new file mode 100644 index 0000000000..f3a8551255 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c @@ -0,0 +1,42 @@ +#include "nfc_protocol_support_gui_common.h" + +#include "nfc/nfc_app_i.h" + +void nfc_protocol_support_common_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_protocol_support_common_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + furi_assert(context); + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_protocol_support_common_byte_input_done_callback(void* context) { + furi_assert(context); + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_protocol_support_common_text_input_done_callback(void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone); +} + +void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) { + UNUSED(instance); +} + +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) { + UNUSED(instance); + UNUSED(event); + return true; +} diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h new file mode 100644 index 0000000000..40ba40c8ec --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h @@ -0,0 +1,85 @@ +/** + * @file nfc_protocol_support_gui_common.h + * @brief Common GUI functions and definitions. + */ +#pragma once + +#include + +#include "nfc/nfc_app.h" + +/** + * @brief Common submenu indices. + */ +enum { + SubmenuIndexCommonSave, /**< Save menu option. */ + SubmenuIndexCommonEmulate, /**< Emulate menu option. */ + SubmenuIndexCommonEdit, /**< Edit menu option. */ + SubmenuIndexCommonInfo, /**< Info menu option. */ + SubmenuIndexCommonRename, /**< Rename menu option. */ + SubmenuIndexCommonDelete, /**< Delete menu option. */ + SubmenuIndexCommonRestore, /**< Restore menu option. */ + SubmenuIndexCommonMax, /**< Special value, internal use. */ +}; + +/** + * @brief Common submenu callback. + * + * Called each time the user presses on a selected submenu item. + * + * @param[in,out] context pointer to a user-defined context object. + * @param[in] index index of the item that was activated. + */ +void nfc_protocol_support_common_submenu_callback(void* context, uint32_t index); + +/** + * @brief Common widget callback. + * + * Called each time the user presses on a selected widget element. + * + * @param[in] result identifier of the activated button. + * @param[in] type type of press action. + * @param[in,out] context pointer to a user-defined context object. + */ +void nfc_protocol_support_common_widget_callback( + GuiButtonType result, + InputType type, + void* context); + +/** + * @brief Common byte input callback. + * + * Called each time the user accepts the byte input. + * + * @param[in,out] context pointer to a user-defined context object. + */ +void nfc_protocol_support_common_byte_input_done_callback(void* context); + +/** + * @brief Common text input callback. + * + * Called each time the user accepts the text input. + * + * @param[in,out] context pointer to a user-defined context object. + */ +void nfc_protocol_support_common_text_input_done_callback(void* context); + +/** + * @brief Empty on_enter() handler. + * + * Does nothing. + * + * @param[in] instance pointer to the NFC application instance. + */ +void nfc_protocol_support_common_on_enter_empty(NfcApp* instance); + +/** + * @brief Empty on_event() handler. + * + * Does nothing and returns true. + * + * @param[in] instance pointer to the NFC application instance. + * @param[in] event custom event type that has occurred. + * @returns always true. + */ +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h new file mode 100644 index 0000000000..a2e82ea153 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h @@ -0,0 +1,13 @@ +/** + * @file nfc_protocol_support_render_common.h + * @brief Common formatting-related defines. + */ +#pragma once + +/** + * @brief Displayed information verbosity level. + */ +typedef enum { + NfcProtocolFormatTypeShort, /**< Short format, terse info. */ + NfcProtocolFormatTypeFull, /**< Full format, verbose info. */ +} NfcProtocolFormatType; diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c new file mode 100644 index 0000000000..ad858a75fc --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -0,0 +1,141 @@ +#include "slix.h" +#include "slix_render.h" + +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_slix(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSlix); + + NfcApp* instance = context; + const SlixPollerEvent* slix_event = event.event_data; + + if(slix_event->type == SlixPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_slix(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_slix, instance); +} + +static void nfc_scene_read_success_on_enter_slix(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_slix_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_emulate_listener_callback_slix(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolSlix); + furi_assert(event.event_data); + + NfcApp* nfc = context; + SlixListenerEvent* slix_event = event.event_data; + + if(slix_event->type == SlixListenerEventTypeCustomCommand) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(slix_event->data->buffer); i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(slix_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) { + const SlixData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolSlix); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolSlix, data); + nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance); +} + +static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_slix = { + .features = NfcProtocolFeatureEmulateFull, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_slix, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.h b/applications/main/nfc/helpers/protocol_support/slix/slix.h new file mode 100644 index 0000000000..9c7504ebaf --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_slix; diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.c b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c new file mode 100644 index 0000000000..80f953db97 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c @@ -0,0 +1,74 @@ +#include "slix_render.h" + +#include "../iso15693_3/iso15693_3_render.h" + +void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) { + nfc_render_iso15693_3_brief(slix_get_base_data(data), str); + + if(format_type != NfcProtocolFormatTypeFull) return; + const SlixType slix_type = slix_get_type(data); + + furi_string_cat(str, "\n\e#Passwords\n"); + + static const char* slix_password_names[] = { + "Read", + "Write", + "Privacy", + "Destroy", + "EAS/AFI", + }; + + for(uint32_t i = 0; i < SlixPasswordTypeCount; ++i) { + if(slix_type_supports_password(slix_type, i)) { + furi_string_cat_printf( + str, "%s : %08lX\n", slix_password_names[i], data->passwords[i]); + } + } + + furi_string_cat(str, "\e#Lock bits\n"); + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { + furi_string_cat_printf( + str, "EAS: %s locked\n", data->system_info.lock_bits.eas ? "" : "not"); + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + furi_string_cat_printf( + str, "PPL: %s locked\n", data->system_info.lock_bits.ppl ? "" : "not"); + + const SlixProtection protection = data->system_info.protection; + + furi_string_cat(str, "\e#Page protection\n"); + furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer); + + const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un"; + const char* rl = (protection.condition & SLIX_PP_CONDITION_RL) ? "" : "un"; + + const char* wh = (protection.condition & SLIX_PP_CONDITION_WH) ? "" : "un"; + const char* wl = (protection.condition & SLIX_PP_CONDITION_WL) ? "" : "un"; + + furi_string_cat_printf(str, "R: H %sprotec. L %sprotec.\n", rh, rl); + furi_string_cat_printf(str, "W: H %sprotec. L %sprotec.\n", wh, wl); + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { + furi_string_cat(str, "\e#Privacy\n"); + furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis"); + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { + furi_string_cat(str, "\e#Signature\n"); + for(uint32_t i = 0; i < 4; ++i) { + furi_string_cat_printf(str, "%02X ", data->signature[i]); + } + + furi_string_cat(str, "[ ... ]"); + + for(uint32_t i = 0; i < 3; ++i) { + furi_string_cat_printf(str, " %02X", data->signature[sizeof(SlixSignature) - i - 1]); + } + } + + furi_string_cat(str, "\n\e#ISO15693-3 data"); + nfc_render_iso15693_3_extra(slix_get_base_data(data), str); +} diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.h b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h new file mode 100644 index 0000000000..98ae6dc97f --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c new file mode 100644 index 0000000000..eef723fed3 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -0,0 +1,103 @@ +#include "st25tb.h" +#include "st25tb_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSt25tb); + + NfcApp* instance = context; + const St25tbPollerEvent* st25tb_event = event.event_data; + + if(st25tb_event->type == St25tbPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_st25tb(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_st25tb, instance); +} + +static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_st25tb_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_st25tb = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_st25tb, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h new file mode 100644 index 0000000000..3b635fdefc --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_st25tb; diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c new file mode 100644 index 0000000000..e3a0f3c50f --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c @@ -0,0 +1,22 @@ +#include "st25tb_render.h" +#include + +void nfc_render_st25tb_info( + const St25tbData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + furi_string_cat_printf(str, "UID"); + + for(size_t i = 0; i < ST25TB_UID_SIZE; i++) { + furi_string_cat_printf(str, " %02X", data->uid[i]); + } + + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "\nSys. OTP: %08lX", data->system_otp_block); + furi_string_cat_printf(str, "\nBlocks:"); + for(size_t i = 0; i < st25tb_get_block_count(data->type); i += 2) { + furi_string_cat_printf( + str, "\n %02X %08lX %08lX", i, data->blocks[i], data->blocks[i + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h new file mode 100644 index 0000000000..9f7be34e9b --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_st25tb_info( + const St25tbData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c deleted file mode 100644 index 4ac793b5b1..0000000000 --- a/applications/main/nfc/nfc.c +++ /dev/null @@ -1,323 +0,0 @@ -#include "nfc_i.h" -#include -#include - -bool nfc_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - Nfc* nfc = context; - return scene_manager_handle_custom_event(nfc->scene_manager, event); -} - -bool nfc_back_event_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - return scene_manager_handle_back_event(nfc->scene_manager); -} - -static void nfc_rpc_command_callback(RpcAppSystemEvent event, void* context) { - furi_assert(context); - Nfc* nfc = context; - - furi_assert(nfc->rpc_ctx); - - if(event == RpcAppEventSessionClose) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose); - rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); - nfc->rpc_ctx = NULL; - } else if(event == RpcAppEventAppExit) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); - } else if(event == RpcAppEventLoadFile) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); - } else { - rpc_system_app_confirm(nfc->rpc_ctx, event, false); - } -} - -Nfc* nfc_alloc() { - Nfc* nfc = malloc(sizeof(Nfc)); - - nfc->worker = nfc_worker_alloc(); - nfc->view_dispatcher = view_dispatcher_alloc(); - nfc->scene_manager = scene_manager_alloc(&nfc_scene_handlers, nfc); - view_dispatcher_enable_queue(nfc->view_dispatcher); - view_dispatcher_set_event_callback_context(nfc->view_dispatcher, nfc); - view_dispatcher_set_custom_event_callback(nfc->view_dispatcher, nfc_custom_event_callback); - view_dispatcher_set_navigation_event_callback(nfc->view_dispatcher, nfc_back_event_callback); - - // Nfc device - nfc->dev = nfc_device_alloc(); - furi_string_set(nfc->dev->folder, NFC_APP_FOLDER); - - // Open GUI record - nfc->gui = furi_record_open(RECORD_GUI); - - // Open Notification record - nfc->notifications = furi_record_open(RECORD_NOTIFICATION); - - // Submenu - nfc->submenu = submenu_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewMenu, submenu_get_view(nfc->submenu)); - - // Dialog - nfc->dialog_ex = dialog_ex_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewDialogEx, dialog_ex_get_view(nfc->dialog_ex)); - - // Popup - nfc->popup = popup_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewPopup, popup_get_view(nfc->popup)); - - // Loading - nfc->loading = loading_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewLoading, loading_get_view(nfc->loading)); - - // Text Input - nfc->text_input = text_input_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewTextInput, text_input_get_view(nfc->text_input)); - - // Byte Input - nfc->byte_input = byte_input_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewByteInput, byte_input_get_view(nfc->byte_input)); - - // TextBox - nfc->text_box = text_box_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box)); - nfc->text_box_store = furi_string_alloc(); - - // Custom Widget - nfc->widget = widget_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewWidget, widget_get_view(nfc->widget)); - - // Mifare Classic Dict Attack - nfc->dict_attack = dict_attack_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack)); - - // Detect Reader - nfc->detect_reader = detect_reader_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewDetectReader, detect_reader_get_view(nfc->detect_reader)); - - // Generator - nfc->generator = NULL; - - return nfc; -} - -void nfc_free(Nfc* nfc) { - furi_assert(nfc); - - if(nfc->rpc_state == NfcRpcStateEmulating) { - // Stop worker - nfc_worker_stop(nfc->worker); - } else if(nfc->rpc_state == NfcRpcStateEmulated) { - // Stop worker - nfc_worker_stop(nfc->worker); - // Save data in shadow file - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - } - if(nfc->rpc_ctx) { - rpc_system_app_send_exited(nfc->rpc_ctx); - rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); - nfc->rpc_ctx = NULL; - } - - // Nfc device - nfc_device_free(nfc->dev); - - // Submenu - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewMenu); - submenu_free(nfc->submenu); - - // DialogEx - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDialogEx); - dialog_ex_free(nfc->dialog_ex); - - // Popup - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewPopup); - popup_free(nfc->popup); - - // Loading - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewLoading); - loading_free(nfc->loading); - - // TextInput - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewTextInput); - text_input_free(nfc->text_input); - - // ByteInput - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewByteInput); - byte_input_free(nfc->byte_input); - - // TextBox - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewTextBox); - text_box_free(nfc->text_box); - furi_string_free(nfc->text_box_store); - - // Custom Widget - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewWidget); - widget_free(nfc->widget); - - // Mifare Classic Dict Attack - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDictAttack); - dict_attack_free(nfc->dict_attack); - - // Detect Reader - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDetectReader); - detect_reader_free(nfc->detect_reader); - - // Worker - nfc_worker_stop(nfc->worker); - nfc_worker_free(nfc->worker); - - // View Dispatcher - view_dispatcher_free(nfc->view_dispatcher); - - // Scene Manager - scene_manager_free(nfc->scene_manager); - - // GUI - furi_record_close(RECORD_GUI); - nfc->gui = NULL; - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - nfc->notifications = NULL; - - free(nfc); -} - -void nfc_text_store_set(Nfc* nfc, const char* text, ...) { - va_list args; - va_start(args, text); - - vsnprintf(nfc->text_store, sizeof(nfc->text_store), text, args); - - va_end(args); -} - -void nfc_text_store_clear(Nfc* nfc) { - memset(nfc->text_store, 0, sizeof(nfc->text_store)); -} - -void nfc_blink_read_start(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_cyan); -} - -void nfc_blink_emulate_start(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_magenta); -} - -void nfc_blink_detect_start(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_yellow); -} - -void nfc_blink_stop(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_stop); -} - -bool nfc_save_file(Nfc* nfc) { - furi_string_printf( - nfc->dev->load_path, - "%s/%s%s", - NFC_APP_FOLDER, - nfc->dev->dev_name, - NFC_APP_FILENAME_EXTENSION); - bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - return file_saved; -} - -void nfc_show_loading_popup(void* context, bool show) { - Nfc* nfc = context; - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); - - if(show) { - // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading); - } else { - // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); - } -} - -static bool nfc_is_hal_ready() { - if(!furi_hal_nfc_is_init()) { - // No connection to the chip, show an error screen - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_text( - message, - "Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\nsupport@flipperzero.one", - 0, - 0, - AlignLeft, - AlignTop); - dialog_message_show(dialogs, message); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - return false; - } else { - return true; - } -} - -int32_t nfc_app(void* p) { - if(!nfc_is_hal_ready()) return 0; - - Nfc* nfc = nfc_alloc(); - char* args = p; - - // Check argument and run corresponding scene - if(args && strlen(args)) { - nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); - uint32_t rpc_ctx = 0; - if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) { - nfc->rpc_ctx = (void*)rpc_ctx; - rpc_system_app_set_callback(nfc->rpc_ctx, nfc_rpc_command_callback, nfc); - rpc_system_app_send_started(nfc->rpc_ctx); - view_dispatcher_attach_to_gui( - nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeDesktop); - scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc); - } else { - view_dispatcher_attach_to_gui( - nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); - if(nfc_device_load(nfc->dev, p, true)) { - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); - dolphin_deed(DolphinDeedNfcEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); - dolphin_deed(DolphinDeedNfcEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); - dolphin_deed(DolphinDeedNfcEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - dolphin_deed(DolphinDeedNfcEmulate); - } - } else { - // Exit app - view_dispatcher_stop(nfc->view_dispatcher); - } - } - nfc_device_set_loading_callback(nfc->dev, NULL, nfc); - } else { - view_dispatcher_attach_to_gui( - nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); - scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); - } - - view_dispatcher_run(nfc->view_dispatcher); - - nfc_free(nfc); - - return 0; -} diff --git a/applications/main/nfc/nfc.h b/applications/main/nfc/nfc.h deleted file mode 100644 index e08be6a3aa..0000000000 --- a/applications/main/nfc/nfc.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -typedef struct Nfc Nfc; diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c new file mode 100644 index 0000000000..fe680aa32d --- /dev/null +++ b/applications/main/nfc/nfc_app.c @@ -0,0 +1,499 @@ +#include "nfc_app_i.h" + +#include + +bool nfc_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + NfcApp* nfc = context; + return scene_manager_handle_custom_event(nfc->scene_manager, event); +} + +bool nfc_back_event_callback(void* context) { + furi_assert(context); + NfcApp* nfc = context; + return scene_manager_handle_back_event(nfc->scene_manager); +} + +static void nfc_app_rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { + furi_assert(context); + NfcApp* nfc = (NfcApp*)context; + + furi_assert(nfc->rpc_ctx); + + if(rpc_event == RpcAppEventSessionClose) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose); + rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); + nfc->rpc_ctx = NULL; + } else if(rpc_event == RpcAppEventAppExit) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcExit); + } else if(rpc_event == RpcAppEventLoadFile) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); + } else { + rpc_system_app_confirm(nfc->rpc_ctx, rpc_event, false); + } +} + +NfcApp* nfc_app_alloc() { + NfcApp* instance = malloc(sizeof(NfcApp)); + + instance->view_dispatcher = view_dispatcher_alloc(); + instance->scene_manager = scene_manager_alloc(&nfc_scene_handlers, instance); + view_dispatcher_enable_queue(instance->view_dispatcher); + view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance); + view_dispatcher_set_custom_event_callback( + instance->view_dispatcher, nfc_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + instance->view_dispatcher, nfc_back_event_callback); + + instance->nfc = nfc_alloc(); + + instance->mf_ul_auth = mf_ultralight_auth_alloc(); + instance->mfc_key_cache = mf_classic_key_cache_alloc(); + + // Nfc device + instance->nfc_device = nfc_device_alloc(); + nfc_device_set_loading_callback(instance->nfc_device, nfc_show_loading_popup, instance); + + // Open GUI record + instance->gui = furi_record_open(RECORD_GUI); + + // Open Notification record + instance->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Open Storage record + instance->storage = furi_record_open(RECORD_STORAGE); + + // Open Dialogs record + instance->dialogs = furi_record_open(RECORD_DIALOGS); + + // Submenu + instance->submenu = submenu_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewMenu, submenu_get_view(instance->submenu)); + + // Dialog + instance->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewDialogEx, dialog_ex_get_view(instance->dialog_ex)); + + // Popup + instance->popup = popup_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewPopup, popup_get_view(instance->popup)); + + // Loading + instance->loading = loading_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewLoading, loading_get_view(instance->loading)); + + // Text Input + instance->text_input = text_input_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewTextInput, text_input_get_view(instance->text_input)); + + // Byte Input + instance->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewByteInput, byte_input_get_view(instance->byte_input)); + + // TextBox + instance->text_box = text_box_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewTextBox, text_box_get_view(instance->text_box)); + instance->text_box_store = furi_string_alloc(); + + // Custom Widget + instance->widget = widget_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewWidget, widget_get_view(instance->widget)); + + // Dict attack + + instance->dict_attack = dict_attack_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(instance->dict_attack)); + + // Detect Reader + instance->detect_reader = detect_reader_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcViewDetectReader, + detect_reader_get_view(instance->detect_reader)); + + instance->iso14443_3a_edit_data = iso14443_3a_alloc(); + instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER); + instance->file_name = furi_string_alloc(); + + return instance; +} + +void nfc_app_free(NfcApp* instance) { + furi_assert(instance); + + if(instance->rpc_ctx) { + rpc_system_app_send_exited(instance->rpc_ctx); + rpc_system_app_set_callback(instance->rpc_ctx, NULL, NULL); + } + + nfc_free(instance->nfc); + + mf_ultralight_auth_free(instance->mf_ul_auth); + mf_classic_key_cache_free(instance->mfc_key_cache); + + // Nfc device + nfc_device_free(instance->nfc_device); + + // Submenu + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewMenu); + submenu_free(instance->submenu); + + // DialogEx + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDialogEx); + dialog_ex_free(instance->dialog_ex); + + // Popup + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewPopup); + popup_free(instance->popup); + + // Loading + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewLoading); + loading_free(instance->loading); + + // TextInput + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewTextInput); + text_input_free(instance->text_input); + + // ByteInput + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewByteInput); + byte_input_free(instance->byte_input); + + // TextBox + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewTextBox); + text_box_free(instance->text_box); + furi_string_free(instance->text_box_store); + + // Custom Widget + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewWidget); + widget_free(instance->widget); + + // Dict attack + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDictAttack); + dict_attack_free(instance->dict_attack); + + // Detect reader + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDetectReader); + detect_reader_free(instance->detect_reader); + + // View Dispatcher + view_dispatcher_free(instance->view_dispatcher); + + // Scene Manager + scene_manager_free(instance->scene_manager); + + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_NOTIFICATION); + // GUI + furi_record_close(RECORD_GUI); + instance->gui = NULL; + + instance->notifications = NULL; + + iso14443_3a_free(instance->iso14443_3a_edit_data); + furi_string_free(instance->file_path); + furi_string_free(instance->file_name); + + free(instance); +} + +void nfc_text_store_set(NfcApp* nfc, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(nfc->text_store, sizeof(nfc->text_store), text, args); + + va_end(args); +} + +void nfc_text_store_clear(NfcApp* nfc) { + memset(nfc->text_store, 0, sizeof(nfc->text_store)); +} + +void nfc_blink_read_start(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_cyan); +} + +void nfc_blink_emulate_start(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_magenta); +} + +void nfc_blink_detect_start(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_yellow); +} + +void nfc_blink_stop(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_stop); +} + +void nfc_make_app_folders(NfcApp* instance) { + furi_assert(instance); + + if(!storage_simply_mkdir(instance->storage, NFC_APP_FOLDER)) { + dialog_message_show_storage_error(instance->dialogs, "Cannot create\napp folder"); + } +} + +bool nfc_save_file(NfcApp* instance, FuriString* path) { + furi_assert(instance); + furi_assert(path); + + bool result = nfc_device_save(instance->nfc_device, furi_string_get_cstr(instance->file_path)); + + if(!result) { + dialog_message_show_storage_error(instance->dialogs, "Cannot save\nkey file"); + } + + return result; +} + +static bool nfc_set_shadow_file_path(FuriString* file_path, FuriString* shadow_file_path) { + furi_assert(file_path); + furi_assert(shadow_file_path); + + bool shadow_file_path_set = false; + if(furi_string_end_with(file_path, NFC_APP_SHADOW_EXTENSION)) { + furi_string_set(shadow_file_path, file_path); + shadow_file_path_set = true; + } else if(furi_string_end_with(file_path, NFC_APP_EXTENSION)) { + size_t path_len = furi_string_size(file_path); + // Cut .nfc + furi_string_set_n(shadow_file_path, file_path, 0, path_len - 4); + furi_string_cat_printf(shadow_file_path, "%s", NFC_APP_SHADOW_EXTENSION); + shadow_file_path_set = true; + } + + return shadow_file_path_set; +} + +static bool nfc_has_shadow_file_internal(NfcApp* instance, FuriString* path) { + furi_assert(path); + + bool has_shadow_file = false; + FuriString* shadow_file_path = furi_string_alloc(); + do { + if(furi_string_empty(path)) break; + if(!nfc_set_shadow_file_path(path, shadow_file_path)) break; + has_shadow_file = + storage_common_exists(instance->storage, furi_string_get_cstr(shadow_file_path)); + } while(false); + + furi_string_free(shadow_file_path); + + return has_shadow_file; +} + +bool nfc_has_shadow_file(NfcApp* instance) { + furi_assert(instance); + + return nfc_has_shadow_file_internal(instance, instance->file_path); +} + +static bool nfc_save_internal(NfcApp* instance, const char* extension) { + furi_assert(instance); + furi_assert(extension); + + bool result = false; + + nfc_make_app_folders(instance); + + if(furi_string_end_with(instance->file_path, NFC_APP_EXTENSION) || + (furi_string_end_with(instance->file_path, NFC_APP_SHADOW_EXTENSION))) { + size_t filename_start = furi_string_search_rchar(instance->file_path, '/'); + furi_string_left(instance->file_path, filename_start); + } + + furi_string_cat_printf( + instance->file_path, "/%s%s", furi_string_get_cstr(instance->file_name), extension); + + result = nfc_save_file(instance, instance->file_path); + + return result; +} + +bool nfc_save_shadow_file(NfcApp* instance) { + furi_assert(instance); + + return nfc_save_internal(instance, NFC_APP_SHADOW_EXTENSION); +} + +bool nfc_save(NfcApp* instance) { + furi_assert(instance); + + return nfc_save_internal(instance, NFC_APP_EXTENSION); +} + +bool nfc_load_file(NfcApp* instance, FuriString* path, bool show_dialog) { + furi_assert(instance); + furi_assert(path); + bool result = false; + + FuriString* load_path = furi_string_alloc(); + if(nfc_has_shadow_file_internal(instance, path)) { + nfc_set_shadow_file_path(path, load_path); + } else if(furi_string_end_with(path, NFC_APP_SHADOW_EXTENSION)) { + size_t path_len = furi_string_size(path); + furi_string_set_n(load_path, path, 0, path_len - 4); + furi_string_cat_printf(load_path, "%s", NFC_APP_EXTENSION); + } else { + furi_string_set(load_path, path); + } + + result = nfc_device_load(instance->nfc_device, furi_string_get_cstr(load_path)); + + if(result) { + path_extract_filename(load_path, instance->file_name, true); + } + + if((!result) && (show_dialog)) { + dialog_message_show_storage_error(instance->dialogs, "Cannot load\nkey file"); + } + + furi_string_free(load_path); + + return result; +} + +bool nfc_delete(NfcApp* instance) { + furi_assert(instance); + + if(nfc_has_shadow_file(instance)) { + nfc_delete_shadow_file(instance); + } + + if(furi_string_end_with_str(instance->file_path, NFC_APP_SHADOW_EXTENSION)) { + size_t path_len = furi_string_size(instance->file_path); + furi_string_replace_at(instance->file_path, path_len - 4, 4, NFC_APP_EXTENSION); + } + + return storage_simply_remove(instance->storage, furi_string_get_cstr(instance->file_path)); +} + +bool nfc_delete_shadow_file(NfcApp* instance) { + furi_assert(instance); + + FuriString* shadow_file_path = furi_string_alloc(); + + bool result = nfc_set_shadow_file_path(instance->file_path, shadow_file_path) && + storage_simply_remove(instance->storage, furi_string_get_cstr(shadow_file_path)); + + furi_string_free(shadow_file_path); + return result; +} + +bool nfc_load_from_file_select(NfcApp* instance) { + furi_assert(instance); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, NFC_APP_EXTENSION, &I_Nfc_10px); + 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); + + if(result) { + result = nfc_load_file(instance, instance->file_path, true); + } + + return result; +} + +void nfc_show_loading_popup(void* context, bool show) { + NfcApp* nfc = context; + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(show) { + // Raise timer priority so that animations can play + vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading); + } else { + // Restore default timer priority + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + } +} + +void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count) { + furi_assert(instance); + furi_assert(types); + furi_assert(count < NfcProtocolNum); + + memcpy(instance->protocols_detected, types, count); + instance->protocols_detected_num = count; + instance->protocols_detected_selected_idx = 0; +} + +void nfc_app_reset_detected_protocols(NfcApp* instance) { + furi_assert(instance); + + instance->protocols_detected_selected_idx = 0; + instance->protocols_detected_num = 0; +} + +static bool nfc_is_hal_ready() { + if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { + // No connection to the chip, show an error screen + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_text( + message, + "Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\nsupport@flipperzero.one", + 0, + 0, + AlignLeft, + AlignTop); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + return false; + } else { + return true; + } +} + +int32_t nfc_app(void* p) { + if(!nfc_is_hal_ready()) return 0; + + NfcApp* nfc = nfc_app_alloc(); + const char* args = p; + + if(args && strlen(args)) { + if(sscanf(args, "RPC %p", &nfc->rpc_ctx) == 1) { + rpc_system_app_set_callback(nfc->rpc_ctx, nfc_app_rpc_command_callback, nfc); + rpc_system_app_send_started(nfc->rpc_ctx); + view_dispatcher_attach_to_gui( + nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeDesktop); + scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc); + } else { + view_dispatcher_attach_to_gui( + nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); + + furi_string_set(nfc->file_path, args); + if(nfc_load_file(nfc, nfc->file_path, false)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate); + } else { + view_dispatcher_stop(nfc->view_dispatcher); + } + } + } else { + view_dispatcher_attach_to_gui( + nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); + } + + view_dispatcher_run(nfc->view_dispatcher); + + nfc_app_free(nfc); + + return 0; +} diff --git a/applications/main/nfc/nfc_app.h b/applications/main/nfc/nfc_app.h new file mode 100644 index 0000000000..c3026d3028 --- /dev/null +++ b/applications/main/nfc/nfc_app.h @@ -0,0 +1,19 @@ +/** + * @file nfc_app.h + * @brief NFC application -- start here. + * + * Application for interfacing with NFC cards and other devices via Flipper's built-in NFC hardware. + * + * Main features: + * * Multiple protocols support + * * Card emulation + * * Shadow file support + * * Dynamically loaded parser plugins + * + * @see nfc_protocol.h for information on adding a new library protocol. + * @see nfc_protocol_support.h for information on integrating a library protocol into the app. + * @see nfc_supported_card_plugin.h for information on adding supported card plugins (parsers). + */ +#pragma once + +typedef struct NfcApp NfcApp; diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h new file mode 100644 index 0000000000..3c0092007a --- /dev/null +++ b/applications/main/nfc/nfc_app_i.h @@ -0,0 +1,192 @@ +#pragma once + +#include "nfc_app.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/dict_attack.h" +#include "views/detect_reader.h" +#include "views/dict_attack.h" + +#include +#include "helpers/nfc_custom_event.h" +#include "helpers/mf_ultralight_auth.h" +#include "helpers/mf_user_dict.h" +#include "helpers/mfkey32_logger.h" +#include "helpers/mf_classic_key_cache.h" + +#include +#include +#include + +#include "rpc/rpc_app.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define NFC_NAME_SIZE 22 +#define NFC_TEXT_STORE_SIZE 128 +#define NFC_BYTE_INPUT_STORE_SIZE 10 +#define NFC_LOG_SIZE_MAX (1024) +#define NFC_APP_FOLDER ANY_PATH("nfc") +#define NFC_APP_EXTENSION ".nfc" +#define NFC_APP_SHADOW_EXTENSION ".shd" +#define NFC_APP_FILENAME_PREFIX "NFC" + +#define NFC_APP_MFKEY32_LOGS_FILE_NAME ".mfkey32.log" +#define NFC_APP_MFKEY32_LOGS_FILE_PATH (NFC_APP_FOLDER "/" NFC_APP_MFKEY32_LOGS_FILE_NAME) + +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") + +typedef enum { + NfcRpcStateIdle, + NfcRpcStateEmulating, +} NfcRpcState; + +typedef struct { + NfcDict* dict; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t current_sector; + uint8_t keys_found; + size_t dict_keys_total; + size_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; + bool is_card_present; +} NfcMfClassicDictAttackContext; + +struct NfcApp { + DialogsApp* dialogs; + Storage* storage; + Gui* gui; + ViewDispatcher* view_dispatcher; + NotificationApp* notifications; + SceneManager* scene_manager; + + char text_store[NFC_TEXT_STORE_SIZE + 1]; + FuriString* text_box_store; + uint8_t byte_input_store[NFC_BYTE_INPUT_STORE_SIZE]; + + uint32_t protocols_detected_num; + NfcProtocol protocols_detected[NfcProtocolNum]; + uint32_t protocols_detected_selected_idx; + + RpcAppSystem* rpc_ctx; + NfcRpcState rpc_state; + + // Common Views + Submenu* submenu; + DialogEx* dialog_ex; + Popup* popup; + Loading* loading; + TextInput* text_input; + ByteInput* byte_input; + TextBox* text_box; + Widget* widget; + DetectReader* detect_reader; + DictAttack* dict_attack; + + Nfc* nfc; + NfcPoller* poller; + NfcScanner* scanner; + NfcListener* listener; + + MfUltralightAuth* mf_ul_auth; + NfcMfClassicDictAttackContext nfc_dict_context; + Mfkey32Logger* mfkey32_logger; + MfUserDict* mf_user_dict; + MfClassicKeyCache* mfc_key_cache; + + NfcDevice* nfc_device; + Iso14443_3aData* iso14443_3a_edit_data; + FuriString* file_path; + FuriString* file_name; + FuriTimer* timer; +}; + +typedef enum { + NfcViewMenu, + NfcViewDialogEx, + NfcViewPopup, + NfcViewLoading, + NfcViewTextInput, + NfcViewByteInput, + NfcViewTextBox, + NfcViewWidget, + NfcViewDictAttack, + NfcViewDetectReader, +} NfcView; + +int32_t nfc_task(void* p); + +void nfc_text_store_set(NfcApp* nfc, const char* text, ...); + +void nfc_text_store_clear(NfcApp* nfc); + +void nfc_blink_read_start(NfcApp* nfc); + +void nfc_blink_emulate_start(NfcApp* nfc); + +void nfc_blink_detect_start(NfcApp* nfc); + +void nfc_blink_stop(NfcApp* nfc); + +void nfc_show_loading_popup(void* context, bool show); + +bool nfc_has_shadow_file(NfcApp* instance); + +bool nfc_save_shadow_file(NfcApp* instance); + +bool nfc_delete_shadow_file(NfcApp* instance); + +bool nfc_save(NfcApp* instance); + +bool nfc_delete(NfcApp* instance); + +bool nfc_load_from_file_select(NfcApp* instance); + +bool nfc_load_file(NfcApp* instance, FuriString* path, bool show_dialog); + +bool nfc_save_file(NfcApp* instance, FuriString* path); + +void nfc_make_app_folder(NfcApp* instance); + +void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count); + +void nfc_app_reset_detected_protocols(NfcApp* instance); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index e961743812..b5a40b122d 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -4,90 +4,30 @@ #include #include -#include -#include +#include + +#define FLAG_EVENT (1 << 10) static void nfc_cli_print_usage() { printf("Usage:\r\n"); printf("nfc \r\n"); printf("Cmd list:\r\n"); - printf("\tdetect\t - detect nfc device\r\n"); - printf("\temulate\t - emulate predefined nfca card\r\n"); - printf("\tapdu\t - Send APDU and print response \r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("\tfield\t - turn field on\r\n"); } } -static void nfc_cli_detect(Cli* cli, FuriString* args) { - UNUSED(args); - // Check if nfc worker is not busy - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); - return; - } - - FuriHalNfcDevData dev_data = {}; - bool cmd_exit = false; - furi_hal_nfc_exit_sleep(); - printf("Detecting nfc...\r\nPress Ctrl+C to abort\r\n"); - while(!cmd_exit) { - cmd_exit |= cli_cmd_interrupt_received(cli); - if(furi_hal_nfc_detect(&dev_data, 400)) { - printf("Found: %s ", nfc_get_dev_type(dev_data.type)); - printf("UID length: %d, UID:", dev_data.uid_len); - for(size_t i = 0; i < dev_data.uid_len; i++) { - printf("%02X", dev_data.uid[i]); - } - printf("\r\n"); - break; - } - furi_hal_nfc_sleep(); - furi_delay_ms(50); - } - furi_hal_nfc_sleep(); -} - -static void nfc_cli_emulate(Cli* cli, FuriString* args) { - UNUSED(args); - // Check if nfc worker is not busy - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); - return; - } - - furi_hal_nfc_exit_sleep(); - printf("Emulating NFC-A Type: T2T UID: 36 9C E7 B1 0A C1 34 SAK: 00 ATQA: 00/44\r\n"); - printf("Press Ctrl+C to abort\r\n"); - - FuriHalNfcDevData params = { - .uid = {0x36, 0x9C, 0xe7, 0xb1, 0x0A, 0xC1, 0x34}, - .uid_len = 7, - .atqa = {0x44, 0x00}, - .sak = 0x00, - .type = FuriHalNfcTypeA, - }; - - while(!cli_cmd_interrupt_received(cli)) { - if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 100)) { - printf("Reader detected\r\n"); - furi_hal_nfc_sleep(); - } - furi_delay_ms(50); - } - furi_hal_nfc_sleep(); -} - static void nfc_cli_field(Cli* cli, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); + if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { + printf("NFC chip failed to start\r\n"); return; } - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_field_on(); + furi_hal_nfc_acquire(); + furi_hal_nfc_low_power_mode_stop(); + furi_hal_nfc_poller_field_on(); printf("Field is on. Don't leave device in this mode for too long.\r\n"); printf("Press Ctrl+C to abort\r\n"); @@ -96,73 +36,8 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { furi_delay_ms(50); } - furi_hal_nfc_field_off(); - furi_hal_nfc_sleep(); -} - -static void nfc_cli_apdu(Cli* cli, FuriString* args) { - UNUSED(cli); - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); - return; - } - - furi_hal_nfc_exit_sleep(); - FuriString* data = NULL; - data = furi_string_alloc(); - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData dev_data = {}; - uint8_t* req_buffer = NULL; - uint8_t* resp_buffer = NULL; - size_t apdu_size = 0; - size_t resp_size = 0; - - do { - if(!args_read_string_and_trim(args, data)) { - printf( - "Use like `nfc apdu 00a404000e325041592e5359532e444446303100 00a4040008a0000003010102` \r\n"); - break; - } - - printf("detecting tag\r\n"); - if(!furi_hal_nfc_detect(&dev_data, 300)) { - printf("Failed to detect tag\r\n"); - break; - } - do { - apdu_size = furi_string_size(data) / 2; - req_buffer = malloc(apdu_size); - hex_chars_to_uint8(furi_string_get_cstr(data), req_buffer); - - memcpy(tx_rx.tx_data, req_buffer, apdu_size); - tx_rx.tx_bits = apdu_size * 8; - tx_rx.tx_rx_type = FuriHalNfcTxRxTypeDefault; - - printf("Sending APDU:%s to Tag\r\n", furi_string_get_cstr(data)); - if(!furi_hal_nfc_tx_rx(&tx_rx, 300)) { - printf("Failed to tx_rx\r\n"); - break; - } - resp_size = (tx_rx.rx_bits / 8) * 2; - if(!resp_size) { - printf("No response\r\n"); - continue; - } - resp_buffer = malloc(resp_size); - uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size); - resp_buffer[resp_size] = 0; - printf("Response: %s\r\n", resp_buffer); - free(req_buffer); - free(resp_buffer); - req_buffer = NULL; - resp_buffer = NULL; - } while(args_read_string_and_trim(args, data)); - } while(false); - - free(req_buffer); - free(resp_buffer); - furi_string_free(data); - furi_hal_nfc_sleep(); + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); } static void nfc_cli(Cli* cli, FuriString* args, void* context) { @@ -175,20 +50,6 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { nfc_cli_print_usage(); break; } - if(furi_string_cmp_str(cmd, "detect") == 0) { - nfc_cli_detect(cli, args); - break; - } - if(furi_string_cmp_str(cmd, "emulate") == 0) { - nfc_cli_emulate(cli, args); - break; - } - - if(furi_string_cmp_str(cmd, "apdu") == 0) { - nfc_cli_apdu(cli, args); - break; - } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "field") == 0) { nfc_cli_field(cli, args); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h deleted file mode 100644 index f7e4899029..0000000000 --- a/applications/main/nfc/nfc_i.h +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once - -#include "nfc.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "views/dict_attack.h" -#include "views/detect_reader.h" - -#include -#include - -#include - -#include "rpc/rpc_app.h" - -#include - -ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST); - -#define NFC_TEXT_STORE_SIZE 128 -#define NFC_APP_FOLDER ANY_PATH("nfc") - -typedef enum { - NfcRpcStateIdle, - NfcRpcStateEmulating, - NfcRpcStateEmulated, -} NfcRpcState; - -struct Nfc { - NfcWorker* worker; - ViewDispatcher* view_dispatcher; - Gui* gui; - NotificationApp* notifications; - SceneManager* scene_manager; - NfcDevice* dev; - FuriHalNfcDevData dev_edit_data; - - char text_store[NFC_TEXT_STORE_SIZE + 1]; - FuriString* text_box_store; - uint8_t byte_input_store[6]; - MfClassicUserKeys_t mfc_key_strs; // Used in MFC key listing - - void* rpc_ctx; - NfcRpcState rpc_state; - - // Common Views - Submenu* submenu; - DialogEx* dialog_ex; - Popup* popup; - Loading* loading; - TextInput* text_input; - ByteInput* byte_input; - TextBox* text_box; - Widget* widget; - DictAttack* dict_attack; - DetectReader* detect_reader; - - const NfcGenerator* generator; -}; - -typedef enum { - NfcViewMenu, - NfcViewDialogEx, - NfcViewPopup, - NfcViewLoading, - NfcViewTextInput, - NfcViewByteInput, - NfcViewTextBox, - NfcViewWidget, - NfcViewDictAttack, - NfcViewDetectReader, -} NfcView; - -Nfc* nfc_alloc(); - -int32_t nfc_task(void* p); - -void nfc_text_store_set(Nfc* nfc, const char* text, ...); - -void nfc_text_store_clear(Nfc* nfc); - -void nfc_blink_read_start(Nfc* nfc); - -void nfc_blink_emulate_start(Nfc* nfc); - -void nfc_blink_detect_start(Nfc* nfc); - -void nfc_blink_stop(Nfc* nfc); - -bool nfc_save_file(Nfc* nfc); - -void nfc_show_loading_popup(void* context, bool show); diff --git a/applications/main/nfc/plugins/supported_cards/all_in_one.c b/applications/main/nfc/plugins/supported_cards/all_in_one.c new file mode 100644 index 0000000000..1be23d1f33 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/all_in_one.c @@ -0,0 +1,107 @@ +#include "nfc_supported_card_plugin.h" + +#include +#include + +#define TAG "AllInOne" + +typedef enum { + AllInOneLayoutTypeA, + AllInOneLayoutTypeD, + AllInOneLayoutTypeE2, + AllInOneLayoutTypeE3, + AllInOneLayoutTypeE5, + AllInOneLayoutType2, + AllInOneLayoutTypeUnknown, +} AllInOneLayoutType; + +static AllInOneLayoutType all_in_one_get_layout(const MfUltralightData* data) { + // Switch on the second half of the third byte of page 5 + const uint8_t layout_byte = data->page[5].data[2]; + const uint8_t layout_half_byte = data->page[5].data[2] & 0x0F; + + FURI_LOG_I(TAG, "Layout byte: %02x", layout_byte); + FURI_LOG_I(TAG, "Layout half-byte: %02x", layout_half_byte); + + switch(layout_half_byte) { + // If it is A, the layout type is a type A layout + case 0x0A: + return AllInOneLayoutTypeA; + case 0x0D: + return AllInOneLayoutTypeD; + case 0x02: + return AllInOneLayoutType2; + default: + FURI_LOG_I(TAG, "Unknown layout type: %d", layout_half_byte); + return AllInOneLayoutTypeUnknown; + } +} + +static bool all_in_one_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + if(data->page[4].data[0] != 0x45 || data->page[4].data[1] != 0xD9) { + FURI_LOG_I(TAG, "Pass not verified"); + break; + } + + uint8_t ride_count = 0; + uint32_t serial = 0; + + const AllInOneLayoutType layout_type = all_in_one_get_layout(data); + + if(layout_type == AllInOneLayoutTypeA) { + // If the layout is A then the ride count is stored in the first byte of page 8 + ride_count = data->page[8].data[0]; + } else if(layout_type == AllInOneLayoutTypeD) { + // If the layout is D, the ride count is stored in the second byte of page 9 + ride_count = data->page[9].data[1]; + } else { + FURI_LOG_I(TAG, "Unknown layout: %d", layout_type); + ride_count = 137; + } + + // // The number starts at the second half of the third byte on page 4, and is 32 bits long + // // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte + // // B8 17 A2 A4 BD becomes 81 7A 2A 4B + const uint8_t* serial_data_lo = data->page[4].data; + const uint8_t* serial_data_hi = data->page[5].data; + + serial = (serial_data_lo[2] & 0x0F) << 28 | serial_data_lo[3] << 20 | + serial_data_hi[0] << 12 | serial_data_hi[1] << 4 | serial_data_hi[2] >> 4; + + // Format string for rides count + furi_string_printf( + parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin all_in_one_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = all_in_one_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor all_in_one_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &all_in_one_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* all_in_one_plugin_ep() { + return &all_in_one_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/myki.c b/applications/main/nfc/plugins/supported_cards/myki.c new file mode 100644 index 0000000000..70a6963710 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/myki.c @@ -0,0 +1,116 @@ +/* myki.c - Parser for myki cards (Melbourne, Australia). + * + * Based on the code by Emily Trau (https://github.com/emilytrau) + * Original pull request URL: https://github.com/flipperdevices/flipperzero-firmware/pull/2326 + * Reference: https://github.com/metrodroid/metrodroid/wiki/Myki + */ +#include "nfc_supported_card_plugin.h" + +#include +#include + +static const MfDesfireApplicationId myki_app_id = {.data = {0x00, 0x11, 0xf2}}; +static const MfDesfireFileId myki_file_id = 0x0f; + +static uint8_t myki_calculate_luhn(uint64_t number) { + // https://en.wikipedia.org/wiki/Luhn_algorithm + // Drop existing check digit to form payload + uint64_t payload = number / 10; + int sum = 0; + int position = 0; + + while(payload > 0) { + int digit = payload % 10; + if(position % 2 == 0) { + digit *= 2; + } + if(digit > 9) { + digit = (digit / 10) + (digit % 10); + } + sum += digit; + payload /= 10; + position++; + } + + return (10 - (sum % 10)) % 10; +} + +static bool myki_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = false; + + do { + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_app_id); + if(app == NULL) break; + + typedef struct { + uint32_t top; + uint32_t bottom; + } MykiFile; + + const MfDesfireFileSettings* file_settings = + mf_desfire_get_file_settings(app, &myki_file_id); + + if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard || + file_settings->data.size < sizeof(MykiFile)) + break; + + const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_file_id); + if(file_data == NULL) break; + + const MykiFile* myki_file = simple_array_cget_data(file_data->data); + + // All myki card numbers are prefixed with "308425" + if(myki_file->top != 308425UL) break; + // Card numbers are always 15 digits in length + if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) break; + + uint64_t card_number = myki_file->top * 1000000000ULL + myki_file->bottom * 10UL; + // Stored card number doesn't include check digit + card_number += myki_calculate_luhn(card_number); + + furi_string_set(parsed_data, "\e#myki\n"); + + // Stylise card number according to the physical card + char card_string[20]; + snprintf(card_string, sizeof(card_string), "%llu", card_number); + + // Digit count in each space-separated group + static const uint8_t digit_count[] = {1, 5, 4, 4, 1}; + + for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) { + for(uint32_t j = 0; j < digit_count[i]; ++j) { + furi_string_push_back(parsed_data, card_string[j + k]); + } + furi_string_push_back(parsed_data, ' '); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin myki_plugin = { + .protocol = NfcProtocolMfDesfire, + .verify = NULL, + .read = NULL, + .parse = myki_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor myki_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &myki_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* myki_plugin_ep() { + return &myki_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h b/applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h new file mode 100644 index 0000000000..7883aeb6b0 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h @@ -0,0 +1,94 @@ +/** + * @file nfc_supported_card_plugin.h + * @brief Supported card plugin abstract interface. + * + * Supported card plugins are dynamically loaded libraries that help making sense of + * a particular card's raw data, if a suitable plugin exists. + * + * For example, if some card serves as a bus ticket, instead of just displaying a data dump, + * a suitable plugin will transform that data into a human-readable format, showing the number + * of rides or balance left. + * Because of the highly specialised nature of application-specific cards, a separate plugin + * for each such card type must be implemented. + * + * To add a new plugin, create a uniquely-named .c file in the `supported_cards` directory + * and implement at least the parse() function in the NfcSupportedCardsPlugin structure. + * Then, register the plugin in the `application.fam` file in the `nfc` directory. Use the existing + * entries as an example. After being registered, the plugin will be automatically deployed with the application. + * + * @note the APPID field MUST end with `_parser` so the applicaton would know that this particular file + * is a supported card plugin. + * + * @see nfc_supported_cards.h + */ +#pragma once + +#include + +#include +#include + +/** + * @brief Unique string identifier for supported card plugins. + */ +#define NFC_SUPPORTED_CARD_PLUGIN_APP_ID "NfcSupportedCardPlugin" + +/** + * @brief Currently supported plugin API version. + */ +#define NFC_SUPPORTED_CARD_PLUGIN_API_VERSION 1 + +/** + * @brief Verify that the card is of a supported type. + * + * This function should be implemented if a quick check exists + * allowing to verify that the plugin is working with the appropriate card type. + * Such checks may include, but are not limited to: reading a specific sector, + * performing a certain read operation, etc. + * + * @param[in,out] nfc pointer to an Nfc instance. + * @returns true if the card was successfully verified, false otherwise. + */ +typedef bool (*NfcSupportedCardPluginVerify)(Nfc* nfc); + +/** + * @brief Read the card using a custom procedure. + * + * This function should be implemented if a card requires some special reading + * procedure not covered in the vanilla poller. Examples include, but are not + * limited to: reading with particular security keys, mandatory order of read + * operations, etc. + * + * @param[in,out] nfc pointer to an Nfc instance. + * @param[in,out] device pointer to a device instance to hold the read data. + * @returns true if the card was successfully read, false otherwise. + */ +typedef bool (*NfcSupportedCardPluginRead)(Nfc* nfc, NfcDevice* device); + +/** + * @brief Parse raw data into human-readable representation. + * + * A supported card plugin may contain only this function, if no special verification + * or reading procedures are not required. In any case, the data must be complete and + * available through the `device` parameter at the time of calling. + * + * The output format is free and application-dependent. Multiple lines should + * be separated by newline character. + * + * @param[in] device pointer to a device instance holding the data is to be parsed. + * @param[out] parsed_data pointer to the string to contain the formatted result. + * @returns true if the card was successfully parsed, false otherwise. + */ +typedef bool (*NfcSupportedCardPluginParse)(const NfcDevice* device, FuriString* parsed_data); + +/** + * @brief Supported card plugin interface. + * + * For a minimally functional plugin, only the parse() function must be implemented. + */ +typedef struct { + NfcProtocol protocol; /**< Identifier of the protocol this card type works on top of. */ + NfcSupportedCardPluginVerify verify; /**< Pointer to the verify() function. */ + NfcSupportedCardPluginRead read; /**< Pointer to the read() function. */ + NfcSupportedCardPluginParse parse; /**< Pointer to the parse() function. */ +} NfcSupportedCardsPlugin; diff --git a/applications/main/nfc/plugins/supported_cards/opal.c b/applications/main/nfc/plugins/supported_cards/opal.c new file mode 100644 index 0000000000..c71635d53e --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/opal.c @@ -0,0 +1,233 @@ +/* + * opal.c - Parser for Opal card (Sydney, Australia). + * + * Copyright 2023 Michael Farrell + * + * This will only read "standard" MIFARE DESFire-based Opal cards. Free travel + * cards (including School Opal cards, veteran, vision-impaired persons and + * TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C + * cards and not supported. + * + * Reference: https://github.com/metrodroid/metrodroid/wiki/Opal + * + * Note: The card values are all little-endian (like Flipper), but the above + * reference was originally written based on Java APIs, which are big-endian. + * This implementation presumes a little-endian system. + * + * 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 . + */ + +#include "nfc_supported_card_plugin.h" + +#include +#include +#include + +#include + +static const MfDesfireApplicationId opal_app_id = {.data = {0x31, 0x45, 0x53}}; + +static const MfDesfireFileId opal_file_id = 0x07; + +static const char* opal_modes[5] = + {"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"}; + +static const char* opal_usages[14] = { + "New / Unused", + "Tap on: new journey", + "Tap on: transfer from same mode", + "Tap on: transfer from other mode", + NULL, // Manly Ferry: new journey + NULL, // Manly Ferry: transfer from ferry + NULL, // Manly Ferry: transfer from other + "Tap off: distance fare", + "Tap off: flat fare", + "Automated tap off: failed to tap off", + "Tap off: end of trip without start", + "Tap off: reversal", + "Tap on: rejected", + "Unknown usage", +}; + +// Opal file 0x7 structure. Assumes a little-endian CPU. +typedef struct __attribute__((__packed__)) { + uint32_t serial : 32; + uint8_t check_digit : 4; + bool blocked : 1; + uint16_t txn_number : 16; + int32_t balance : 21; + uint16_t days : 15; + uint16_t minutes : 11; + uint8_t mode : 3; + uint16_t usage : 4; + bool auto_topup : 1; + uint8_t weekly_journeys : 4; + uint16_t checksum : 16; +} OpalFile; + +static_assert(sizeof(OpalFile) == 16, "OpalFile"); + +// Converts an Opal timestamp to FuriHalRtcDateTime. +// +// Opal measures days since 1980-01-01 and minutes since midnight, and presumes +// all days are 1440 minutes. +static void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) { + out->year = 1980; + out->month = 1; + // 1980-01-01 is a Tuesday + out->weekday = ((days + 1) % 7) + 1; + out->hour = minutes / 60; + out->minute = minutes % 60; + out->second = 0; + + // What year is it? + for(;;) { + const uint16_t num_days_in_year = furi_hal_rtc_get_days_per_year(out->year); + if(days < num_days_in_year) break; + days -= num_days_in_year; + out->year++; + } + + // 1-index the day of the year + days++; + + for(;;) { + // What month is it? + const bool is_leap = furi_hal_rtc_is_leap_year(out->year); + const uint8_t num_days_in_month = furi_hal_rtc_get_days_per_month(is_leap, out->month); + if(days <= num_days_in_month) break; + days -= num_days_in_month; + out->month++; + } + + out->day = days; +} + +static bool opal_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + bool parsed = false; + + do { + const MfDesfireApplication* app = mf_desfire_get_application(data, &opal_app_id); + if(app == NULL) break; + + const MfDesfireFileSettings* file_settings = + mf_desfire_get_file_settings(app, &opal_file_id); + if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard || + file_settings->data.size != sizeof(OpalFile)) + break; + + const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &opal_file_id); + if(file_data == NULL) break; + + const OpalFile* opal_file = simple_array_cget_data(file_data->data); + + const uint8_t serial2 = opal_file->serial / 10000000; + const uint16_t serial3 = (opal_file->serial / 1000) % 10000; + const uint16_t serial4 = (opal_file->serial % 1000); + + if(opal_file->check_digit > 9) break; + + // Negative balance. Make this a positive value again and record the + // sign separately, because then we can handle balances of -99..-1 + // cents, as the "dollars" division below would result in a positive + // zero value. + const bool is_negative_balance = (opal_file->balance < 0); + const char* sign = is_negative_balance ? "-" : ""; + const int32_t balance = is_negative_balance ? labs(opal_file->balance) : //-V1081 + opal_file->balance; + const uint8_t balance_cents = balance % 100; + const int32_t balance_dollars = balance / 100; + + FuriHalRtcDateTime timestamp; + opal_date_time_to_furi(opal_file->days, opal_file->minutes, ×tamp); + + // Usages 4..6 associated with the Manly Ferry, which correspond to + // usages 1..3 for other modes. + const bool is_manly_ferry = (opal_file->usage >= 4) && (opal_file->usage <= 6); + + // 3..7 are "reserved", but we use 4 to indicate the Manly Ferry. + const uint8_t mode = is_manly_ferry ? 4 : opal_file->mode; + const uint8_t usage = is_manly_ferry ? opal_file->usage - 3 : opal_file->usage; + + const char* mode_str = opal_modes[mode > 4 ? 3 : mode]; + const char* usage_str = opal_usages[usage > 12 ? 13 : usage]; + + furi_string_printf( + parsed_data, + "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", + sign, + balance_dollars, + balance_cents, + serial2, + serial3, + serial4, + opal_file->check_digit, + mode_str, + usage_str); + + FuriString* timestamp_str = furi_string_alloc(); + + locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); + furi_string_cat(parsed_data, timestamp_str); + furi_string_cat(parsed_data, " at "); + + locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false); + furi_string_cat(parsed_data, timestamp_str); + + furi_string_free(timestamp_str); + + furi_string_cat_printf( + parsed_data, + "\nWeekly journeys: %hhu, Txn #%hu\n", + opal_file->weekly_journeys, + opal_file->txn_number); + + if(opal_file->auto_topup) { + furi_string_cat_str(parsed_data, "Auto-topup enabled\n"); + } + + if(opal_file->blocked) { + furi_string_cat_str(parsed_data, "Card blocked\n"); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin opal_plugin = { + .protocol = NfcProtocolMfDesfire, + .verify = NULL, + .read = NULL, + .parse = opal_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor opal_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &opal_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* opal_plugin_ep() { + return &opal_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c new file mode 100644 index 0000000000..cb8c0093d0 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -0,0 +1,219 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Plantain" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} PlantainCardConfig; + +static const MfClassicKeyPair plantain_1k_keys[] = { + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, + {.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, + {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, + {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, + {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, + {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, +}; + +static const MfClassicKeyPair plantain_4k_keys[] = { + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, + {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, + {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, + {.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, + {.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056}, + {.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, + {.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406}, + {.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, {.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA}, + {.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, {.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3}, + {.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, {.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290}, + {.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9}, + {.a = 0xfd8705e721b0, .b = 0x296fc317a513}, {.a = 0x22052b480d11, .b = 0xe19504c39461}, + {.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a}, + {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085}, +}; + +static bool plantain_get_card_config(PlantainCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->data_sector = 8; + config->keys = plantain_1k_keys; + } else if(type == MfClassicType4k) { + config->data_sector = 8; + config->keys = plantain_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool plantain_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + PlantainCardConfig cfg = {}; + if(!plantain_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_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; + } + + verified = true; + } while(false); + + return verified; +} + +static bool plantain_verify(Nfc* nfc) { + return plantain_verify_type(nfc, MfClassicType1k) || + plantain_verify_type(nfc, MfClassicType4k); +} + +static bool plantain_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_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + PlantainCardConfig cfg = {}; + if(!plantain_get_card_config(&cfg, data->type)) break; + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_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 plantain_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 + PlantainCardConfig cfg = {}; + if(!plantain_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != cfg.keys[cfg.data_sector].a) break; + + // Point to block 0 of sector 4, value 0 + const uint8_t* temp_ptr = data->block[16].data; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = data->block[0].data; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + + furi_string_printf( + parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin plantain_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = plantain_verify, + .read = plantain_read, + .parse = plantain_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor plantain_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &plantain_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* plantain_plugin_ep() { + return &plantain_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c new file mode 100644 index 0000000000..d42b977c6c --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -0,0 +1,214 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Troika" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} TroikaCardConfig; + +static const MfClassicKeyPair troika_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, + {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, + {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dba}, + {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, + {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, + {.a = 0x9becdf3d9273, .b = 0xf8493407799d}, + {.a = 0x08b386463229, .b = 0x5efbaecef46b}, + {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0}, + {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, +}; + +static const MfClassicKeyPair troika_4k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dbb}, + {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, + {.a = 0x9becdf3d9273, .b = 0xf8493407799d}, {.a = 0x08b386463229, .b = 0x5efbaecef46b}, + {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x6b02733bb6ec, .b = 0x7038cd25c408}, {.a = 0x403d706ba880, .b = 0xb39d19a280df}, + {.a = 0xc11f4597efb5, .b = 0x70d901648cb9}, {.a = 0x0db520c78c1c, .b = 0x73e5b9d9d3a4}, + {.a = 0x3ebce0925b2f, .b = 0x372cc880f216}, {.a = 0x16a27af45407, .b = 0x9868925175ba}, + {.a = 0xaba208516740, .b = 0xce26ecb95252}, {.a = 0xcd64e567abcd, .b = 0x8f79c4fd8a01}, + {.a = 0x764cd061f1e6, .b = 0xa74332f74994}, {.a = 0x1cc219e9fec1, .b = 0xb90de525ceb6}, + {.a = 0x2fe3cb83ea43, .b = 0xfba88f109b32}, {.a = 0x07894ffec1d6, .b = 0xefcb0e689db3}, + {.a = 0x04c297b91308, .b = 0xc8454c154cb5}, {.a = 0x7a38e3511a38, .b = 0xab16584c972a}, + {.a = 0x7545df809202, .b = 0xecf751084a80}, {.a = 0x5125974cd391, .b = 0xd3eafb5df46d}, + {.a = 0x7a86aa203788, .b = 0xe41242278ca2}, {.a = 0xafcef64c9913, .b = 0x9db96dca4324}, + {.a = 0x04eaa462f70b, .b = 0xac17b93e2fae}, {.a = 0xe734c210f27e, .b = 0x29ba8c3e9fda}, + {.a = 0xd5524f591eed, .b = 0x5daf42861b4d}, {.a = 0xe4821a377b75, .b = 0xe8709e486465}, + {.a = 0x518dc6eea089, .b = 0x97c64ac98ca4}, {.a = 0xbb52f8cce07f, .b = 0x6b6119752c70}, +}; + +static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->data_sector = 8; + config->keys = troika_1k_keys; + } else if(type == MfClassicType4k) { + config->data_sector = 4; + config->keys = troika_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool troika_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + TroikaCardConfig cfg = {}; + if(!troika_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_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; + } + + verified = true; + } while(false); + + return verified; +} + +static bool troika_verify(Nfc* nfc) { + return troika_verify_type(nfc, MfClassicType1k) || troika_verify_type(nfc, MfClassicType4k); +} + +static bool troika_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_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + TroikaCardConfig cfg = {}; + if(!troika_get_card_config(&cfg, data->type)) 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(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_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 troika_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 + TroikaCardConfig cfg = {}; + if(!troika_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != cfg.keys[cfg.data_sector].a) break; + + // Parse data + const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + + const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5]; + uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; + temp_ptr = &data->block[start_block_num].data[2]; + + uint32_t number = 0; + for(size_t i = 1; i < 5; i++) { + number <<= 8; + number |= temp_ptr[i]; + } + number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; + + furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin troika_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = troika_verify, + .read = troika_read, + .parse = troika_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor troika_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &troika_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* troika_plugin_ep() { + return &troika_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c new file mode 100644 index 0000000000..fb964103ee --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -0,0 +1,189 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "TwoCities" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair two_cities_4k_keys[] = { + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, + {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, + {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, + {.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, + {.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056}, + {.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, + {.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406}, + {.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, {.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA}, + {.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, {.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3}, + {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, + {.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a}, + {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085}, +}; + +bool two_cities_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 4; + 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(two_cities_4k_keys[verify_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_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 two_cities_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_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(two_cities_4k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(two_cities_4k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_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 two_cities_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, 4); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + if(key != two_cities_4k_keys[4].a) return false; + + // ===== + // PLANTAIN + // ===== + + // Point to block 0 of sector 4, value 0 + const uint8_t* temp_ptr = data->block[16].data; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = data->block[0].data; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + + // ===== + // --PLANTAIN-- + // ===== + // TROIKA + // ===== + + const uint8_t* troika_temp_ptr = &data->block[33].data[5]; + uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25; + troika_temp_ptr = &data->block[32].data[2]; + uint32_t troika_number = 0; + for(size_t i = 0; i < 4; i++) { + troika_number <<= 8; + troika_number |= troika_temp_ptr[i]; + } + troika_number >>= 4; + + furi_string_printf( + parsed_data, + "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", + card_number, + balance, + troika_number, + troika_balance); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin two_cities_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = two_cities_verify, + .read = two_cities_read, + .parse = two_cities_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor two_cities_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &two_cities_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* two_cities_plugin_ep() { + return &two_cities_plugin_descriptor; +} diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index f11d147983..f415c66a6f 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -1,72 +1,57 @@ ADD_SCENE(nfc, start, Start) -ADD_SCENE(nfc, read, Read) +ADD_SCENE(nfc, file_select, FileSelect) ADD_SCENE(nfc, saved_menu, SavedMenu) -ADD_SCENE(nfc, extra_actions, ExtraActions) -ADD_SCENE(nfc, set_type, SetType) -ADD_SCENE(nfc, set_sak, SetSak) -ADD_SCENE(nfc, set_atqa, SetAtqa) -ADD_SCENE(nfc, set_uid, SetUid) -ADD_SCENE(nfc, generate_info, GenerateInfo) -ADD_SCENE(nfc, read_card_success, ReadCardSuccess) ADD_SCENE(nfc, save_name, SaveName) ADD_SCENE(nfc, save_success, SaveSuccess) -ADD_SCENE(nfc, file_select, FileSelect) -ADD_SCENE(nfc, emulate_uid, EmulateUid) -ADD_SCENE(nfc, nfca_read_success, NfcaReadSuccess) -ADD_SCENE(nfc, nfca_menu, NfcaMenu) -ADD_SCENE(nfc, nfcv_menu, NfcVMenu) -ADD_SCENE(nfc, nfcv_unlock_menu, NfcVUnlockMenu) -ADD_SCENE(nfc, nfcv_key_input, NfcVKeyInput) -ADD_SCENE(nfc, nfcv_unlock, NfcVUnlock) -ADD_SCENE(nfc, nfcv_emulate, NfcVEmulate) -ADD_SCENE(nfc, nfcv_sniff, NfcVSniff) -ADD_SCENE(nfc, nfcv_read_success, NfcVReadSuccess) -ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess) -ADD_SCENE(nfc, mf_ultralight_data, MfUltralightData) -ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu) -ADD_SCENE(nfc, mf_ultralight_emulate, MfUltralightEmulate) -ADD_SCENE(nfc, mf_ultralight_read_auth, MfUltralightReadAuth) -ADD_SCENE(nfc, mf_ultralight_read_auth_result, MfUltralightReadAuthResult) -ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) -ADD_SCENE(nfc, mf_ultralight_unlock_auto, MfUltralightUnlockAuto) -ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) -ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) -ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess) -ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu) -ADD_SCENE(nfc, mf_desfire_data, MfDesfireData) -ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) -ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess) -ADD_SCENE(nfc, mf_classic_data, MfClassicData) -ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu) -ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate) -ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) -ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) -ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) -ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) -ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) -ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) -ADD_SCENE(nfc, mf_classic_write, MfClassicWrite) -ADD_SCENE(nfc, mf_classic_write_success, MfClassicWriteSuccess) -ADD_SCENE(nfc, mf_classic_write_fail, MfClassicWriteFail) -ADD_SCENE(nfc, mf_classic_update, MfClassicUpdate) -ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess) -ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) -ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) -ADD_SCENE(nfc, emv_menu, EmvMenu) -ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) -ADD_SCENE(nfc, device_info, DeviceInfo) ADD_SCENE(nfc, delete, Delete) ADD_SCENE(nfc, delete_success, DeleteSuccess) ADD_SCENE(nfc, restore_original_confirm, RestoreOriginalConfirm) ADD_SCENE(nfc, restore_original, RestoreOriginal) + +ADD_SCENE(nfc, detect, Detect) +ADD_SCENE(nfc, read, Read) +ADD_SCENE(nfc, info, Info) +ADD_SCENE(nfc, more_info, MoreInfo) +ADD_SCENE(nfc, supported_card, SupportedCard) +ADD_SCENE(nfc, select_protocol, SelectProtocol) +ADD_SCENE(nfc, extra_actions, ExtraActions) +ADD_SCENE(nfc, read_success, ReadSuccess) +ADD_SCENE(nfc, read_menu, ReadMenu) +ADD_SCENE(nfc, emulate, Emulate) +ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) -ADD_SCENE(nfc, dict_not_found, DictNotFound) -ADD_SCENE(nfc, rpc, Rpc) -ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, retry_confirm, RetryConfirm) -ADD_SCENE(nfc, detect_reader, DetectReader) -ADD_SCENE(nfc, mfkey_nonces_info, MfkeyNoncesInfo) -ADD_SCENE(nfc, mfkey_complete, MfkeyComplete) -ADD_SCENE(nfc, nfc_data_info, NfcDataInfo) -ADD_SCENE(nfc, read_card_type, ReadCardType) +ADD_SCENE(nfc, exit_confirm, ExitConfirm) + +ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) +ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) +ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) +ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass) + +ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) +ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) + +ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) +ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader) +ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo) +ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) +ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) +ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) +ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial) +ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess) +ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail) +ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) + +ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) +ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) +ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) +ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) +ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) + +ADD_SCENE(nfc, set_type, SetType) +ADD_SCENE(nfc, set_sak, SetSak) +ADD_SCENE(nfc, set_atqa, SetAtqa) +ADD_SCENE(nfc, set_uid, SetUid) + +ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_debug.c b/applications/main/nfc/scenes/nfc_scene_debug.c index ed079c2ed9..97592f2e27 100644 --- a/applications/main/nfc/scenes/nfc_scene_debug.c +++ b/applications/main/nfc/scenes/nfc_scene_debug.c @@ -1,4 +1,4 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" enum SubmenuDebugIndex { SubmenuDebugIndexField, @@ -6,29 +6,26 @@ enum SubmenuDebugIndex { }; void nfc_scene_debug_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } void nfc_scene_debug_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; Submenu* submenu = nfc->submenu; submenu_add_item( submenu, "Field", SubmenuDebugIndexField, nfc_scene_debug_submenu_callback, nfc); - submenu_add_item( - submenu, "Apdu", SubmenuDebugIndexApdu, nfc_scene_debug_submenu_callback, nfc); submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDebug)); - nfc_device_clear(nfc->dev); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -37,18 +34,13 @@ bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexField); scene_manager_next_scene(nfc->scene_manager, NfcSceneField); consumed = true; - } else if(event.event == SubmenuDebugIndexApdu) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexApdu); - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateApduSequence); - consumed = true; } } return consumed; } void nfc_scene_debug_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index 0808db45a3..c1a676168a 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -1,21 +1,20 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; if(type == InputTypeShort) { view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } } void nfc_scene_delete_on_enter(void* context) { - Nfc* nfc = context; - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; + NfcApp* nfc = context; // Setup Custom Widget view FuriString* temp_str; temp_str = furi_string_alloc(); - furi_string_printf(temp_str, "\e#Delete %s?\e#", nfc->dev->dev_name); + furi_string_printf(temp_str, "\e#Delete %s?\e#", furi_string_get_cstr(nfc->file_name)); widget_add_text_box_element( nfc->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str), false); widget_add_button_element( @@ -23,47 +22,33 @@ void nfc_scene_delete_on_enter(void* context) { widget_add_button_element( nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc); + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(nfc->nfc_device, &uid_len); + furi_string_set(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", uid[i]); } widget_add_string_element( nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - NfcProtocol protocol = nfc->dev->dev_data.protocol; - const char* nfc_type = "NFC-A"; - - if(protocol == NfcDeviceProtocolEMV) { - furi_string_set(temp_str, "EMV bank card"); - } else if(protocol == NfcDeviceProtocolMifareUl) { - furi_string_set(temp_str, nfc_mf_ul_type(nfc->dev->dev_data.mf_ul_data.type, true)); - } else if(protocol == NfcDeviceProtocolMifareClassic) { - furi_string_set(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type)); - } else if(protocol == NfcDeviceProtocolMifareDesfire) { - furi_string_set(temp_str, "MIFARE DESFire"); - } else if(protocol == NfcDeviceProtocolNfcV) { - furi_string_set(temp_str, "ISO15693 tag"); - nfc_type = "NFC-V"; - } else { - furi_string_set(temp_str, "Unknown ISO tag"); - } + furi_string_set_str(temp_str, nfc_device_get_name(nfc->nfc_device, NfcDeviceNameTypeFull)); widget_add_string_element( nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, nfc_type); furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == GuiButtonTypeRight) { - if(nfc_device_delete(nfc->dev, true)) { + if(nfc_delete(nfc)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { scene_manager_search_and_switch_to_previous_scene( @@ -76,7 +61,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_delete_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; widget_reset(nfc->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index 795363527f..f0c22eec4d 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -1,12 +1,12 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_delete_success_popup_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_delete_success_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view Popup* popup = nfc->popup; @@ -20,7 +20,7 @@ void nfc_scene_delete_success_on_enter(void* context) { } bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -38,7 +38,7 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_delete_success_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view popup_reset(nfc->popup); diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c new file mode 100644 index 0000000000..326b1458c0 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -0,0 +1,59 @@ +#include "../nfc_app_i.h" +#include + +void nfc_scene_detect_scan_callback(NfcScannerEvent event, void* context) { + furi_assert(context); + + NfcApp* instance = context; + + if(event.type == NfcScannerEventTypeDetected) { + nfc_app_set_detected_protocols(instance, event.data.protocols, event.data.protocol_num); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerExit); + } +} + +void nfc_scene_detect_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + popup_reset(instance->popup); + popup_set_text( + instance->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + nfc_app_reset_detected_protocols(instance); + + instance->scanner = nfc_scanner_alloc(instance->nfc); + nfc_scanner_start(instance->scanner, nfc_scene_detect_scan_callback, instance); + + nfc_blink_detect_start(instance); +} + +bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventWorkerExit) { + if(instance->protocols_detected_num > 1) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); + } else { + scene_manager_next_scene(instance->scene_manager, NfcSceneRead); + } + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_detect_on_exit(void* context) { + NfcApp* instance = context; + + nfc_scanner_stop(instance->scanner); + nfc_scanner_free(instance->scanner); + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c deleted file mode 100644 index 745946157b..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_detect_reader.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U) - -static const NotificationSequence sequence_detect_reader = { - &message_green_255, - &message_blue_255, - &message_do_not_reset, - NULL, -}; - -bool nfc_detect_reader_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - return true; -} - -void nfc_scene_detect_reader_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_detect_reader_on_enter(void* context) { - Nfc* nfc = context; - - detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); - detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); - NfcDeviceData* dev_data = &nfc->dev->dev_data; - if(dev_data->nfc_data.uid_len) { - detect_reader_set_uid( - nfc->detect_reader, dev_data->nfc_data.uid, dev_data->nfc_data.uid_len); - } - - // Store number of collected nonces in scene state - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0); - notification_message(nfc->notifications, &sequence_detect_reader); - - nfc_worker_start( - nfc->worker, - NfcWorkerStateAnalyzeReader, - &nfc->dev->dev_data, - nfc_detect_reader_worker_callback, - nfc); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDetectReader); -} - -bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - uint32_t nonces_collected = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDetectReader); - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - nfc_worker_stop(nfc->worker); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyNoncesInfo); - consumed = true; - } else if(event.event == NfcWorkerEventDetectReaderMfkeyCollected) { - nonces_collected += 2; - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDetectReader, nonces_collected); - detect_reader_set_nonces_collected(nfc->detect_reader, nonces_collected); - if(nonces_collected >= NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { - detect_reader_set_state(nfc->detect_reader, DetectReaderStateDone); - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_single_vibro); - notification_message(nfc->notifications, &sequence_set_green_255); - nfc_worker_stop(nfc->worker); - } - consumed = true; - } else if(event.event == NfcWorkerEventDetectReaderDetected) { - if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { - notification_message(nfc->notifications, &sequence_blink_start_cyan); - detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderDetected); - } - } else if(event.event == NfcWorkerEventDetectReaderLost) { - if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_detect_reader); - detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderLost); - } - } - } - - return consumed; -} - -void nfc_scene_detect_reader_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - detect_reader_reset(nfc->detect_reader); - - // Stop notifications - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_reset_green); -} diff --git a/applications/main/nfc/scenes/nfc_scene_device_info.c b/applications/main/nfc/scenes/nfc_scene_device_info.c deleted file mode 100644 index 5d51c0816c..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_device_info.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "../nfc_i.h" -#include "../helpers/nfc_emv_parser.h" - -void nfc_scene_device_info_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_device_info_on_enter(void* context) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - if(dev_data->protocol == NfcDeviceProtocolEMV) { - EmvData* emv_data = &dev_data->emv_data; - furi_string_printf(temp_str, "\e#%s\n", emv_data->name); - for(uint8_t i = 0; i < emv_data->number_len; i += 2) { - furi_string_cat_printf( - temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); - } - furi_string_trim(temp_str); - - // Add expiration date - if(emv_data->exp_mon) { - furi_string_cat_printf( - temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); - } - // Parse currency code - if((emv_data->currency_code)) { - FuriString* currency_name; - currency_name = furi_string_alloc(); - if(nfc_emv_parser_get_currency_name( - nfc->dev->storage, emv_data->currency_code, currency_name)) { - furi_string_cat_printf( - temp_str, "\nCur: %s ", furi_string_get_cstr(currency_name)); - } - furi_string_free(currency_name); - } - // Parse country code - if((emv_data->country_code)) { - FuriString* country_name; - country_name = furi_string_alloc(); - if(nfc_emv_parser_get_country_name( - nfc->dev->storage, emv_data->country_code, country_name)) { - furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name)); - } - furi_string_free(country_name); - } - } else if( - dev_data->protocol == NfcDeviceProtocolMifareClassic || - dev_data->protocol == NfcDeviceProtocolMifareDesfire || - dev_data->protocol == NfcDeviceProtocolMifareUl) { - furi_string_set(temp_str, nfc->dev->dev_data.parsed_data); - } - - widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - widget_add_button_element( - nfc->widget, GuiButtonTypeRight, "More", nfc_scene_device_info_widget_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_device_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_device_info_on_exit(void* context) { - Nfc* nfc = context; - - // Clear views - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_dict_not_found.c b/applications/main/nfc/scenes/nfc_scene_dict_not_found.c deleted file mode 100644 index 781c5a9325..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_dict_not_found.c +++ /dev/null @@ -1,52 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_dict_not_found_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_dict_not_found_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - Popup* popup = nfc->popup; - popup_set_text( - popup, - "Function requires\nan SD card with\nfresh databases.", - 82, - 24, - AlignCenter, - AlignCenter); - popup_set_icon(popup, 6, 10, &I_SDQuestion_35x43); - popup_set_timeout(popup, 2500); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_dict_not_found_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_dict_not_found_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeys); - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneExtraActions); - } else { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); - } - } - } - return consumed; -} - -void nfc_scene_dict_not_found_on_exit(void* context) { - Nfc* nfc = context; - popup_reset(nfc->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emulate.c b/applications/main/nfc/scenes/nfc_scene_emulate.c new file mode 100644 index 0000000000..6f217f3154 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_emulate.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_emulate_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneEmulate, context); +} + +bool nfc_scene_emulate_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneEmulate, context, event); +} + +void nfc_scene_emulate_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneEmulate, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c deleted file mode 100644 index 358ad2ab6f..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_emulate_apdu_sequence_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - Popup* popup = nfc->popup; - popup_set_header(popup, "Run APDU reader", 64, 31, AlignCenter, AlignTop); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - bool consumed = false; - return consumed; -} - -void nfc_scene_emulate_apdu_sequence_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c deleted file mode 100644 index 7316eebe01..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ /dev/null @@ -1,144 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX (200) - -enum { - NfcSceneEmulateUidStateWidget, - NfcSceneEmulateUidStateTextBox, -}; - -bool nfc_emulate_uid_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit); - return true; -} - -void nfc_scene_emulate_uid_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_emulate_uid_textbox_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -// Add widget with device name or inform that data received -static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - widget_reset(widget); - FuriString* info_str; - info_str = furi_string_alloc(); - - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); - widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID"); - if(strcmp(nfc->dev->dev_name, "") != 0) { - furi_string_printf(info_str, "%s", nfc->dev->dev_name); - } else { - for(uint8_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(info_str, "%02X ", data->uid[i]); - } - } - furi_string_trim(info_str); - widget_add_text_box_element( - widget, 57, 28, 67, 25, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); - furi_string_free(info_str); - if(data_received) { - widget_add_button_element( - widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_uid_widget_callback, nfc); - } -} - -void nfc_scene_emulate_uid_on_enter(void* context) { - Nfc* nfc = context; - - // Setup Widget - nfc_scene_emulate_uid_widget_config(nfc, false); - // Setup TextBox - TextBox* text_box = nfc->text_box; - text_box_set_font(text_box, TextBoxFontHex); - text_box_set_focus(text_box, TextBoxFocusEnd); - furi_string_reset(nfc->text_box_store); - - // Set Widget state and view - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - // Start worker - memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); - nfc_worker_start( - nfc->worker, - NfcWorkerStateUidEmulate, - &nfc->dev->dev_data, - nfc_emulate_uid_worker_callback, - nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcReaderRequestData* reader_data = &nfc->dev->dev_data.reader_data; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateUid); - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventWorkerExit) { - // Add data button to widget if data is received for the first time - if(!furi_string_size(nfc->text_box_store)) { - nfc_scene_emulate_uid_widget_config(nfc, true); - } - // Update TextBox data - if(furi_string_size(nfc->text_box_store) < NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); - for(uint16_t i = 0; i < reader_data->size; i++) { - furi_string_cat_printf(nfc->text_box_store, " %02X", reader_data->data[i]); - } - furi_string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - } - memset(reader_data, 0, sizeof(NfcReaderRequestData)); - consumed = true; - } else if(event.event == GuiButtonTypeCenter && state == NfcSceneEmulateUidStateWidget) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateTextBox); - consumed = true; - } else if(event.event == NfcCustomEventViewExit && state == NfcSceneEmulateUidStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state == NfcSceneEmulateUidStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_emulate_uid_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - widget_reset(nfc->widget); - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emv_menu.c b/applications/main/nfc/scenes/nfc_scene_emv_menu.c deleted file mode 100644 index eb1e10043b..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_emv_menu.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "../nfc_i.h" - -enum SubmenuIndex { - SubmenuIndexInfo, -}; - -void nfc_scene_emv_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_emv_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_emv_menu_submenu_callback, nfc); - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_emv_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneEmvMenu, event.event); - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_emv_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c deleted file mode 100644 index 005b76cb21..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c +++ /dev/null @@ -1,113 +0,0 @@ -#include "../nfc_i.h" -#include "../helpers/nfc_emv_parser.h" - -void nfc_scene_emv_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_emv_read_success_on_enter(void* context) { - Nfc* nfc = context; - EmvData* emv_data = &nfc->dev->dev_data.emv_data; - - // Setup Custom Widget view - widget_add_button_element( - nfc->widget, GuiButtonTypeLeft, "Retry", nfc_scene_emv_read_success_widget_callback, nfc); - widget_add_button_element( - nfc->widget, GuiButtonTypeRight, "More", nfc_scene_emv_read_success_widget_callback, nfc); - - FuriString* temp_str; - if(emv_data->name[0] != '\0') { - temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name); - } else { - temp_str = furi_string_alloc_printf("\e#Unknown Bank Card\n"); - } - if(emv_data->number_len) { - for(uint8_t i = 0; i < emv_data->number_len; i += 2) { - furi_string_cat_printf( - temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); - } - furi_string_trim(temp_str); - } else if(emv_data->aid_len) { - furi_string_cat_printf(temp_str, "Can't parse data from app\n"); - // Parse AID name - FuriString* aid_name; - aid_name = furi_string_alloc(); - if(nfc_emv_parser_get_aid_name( - nfc->dev->storage, emv_data->aid, emv_data->aid_len, aid_name)) { - furi_string_cat_printf(temp_str, "AID: %s", furi_string_get_cstr(aid_name)); - } else { - furi_string_cat_printf(temp_str, "AID: "); - for(uint8_t i = 0; i < emv_data->aid_len; i++) { - furi_string_cat_printf(temp_str, "%02X", emv_data->aid[i]); - } - } - furi_string_free(aid_name); - } - - // Add expiration date - if(emv_data->exp_mon) { - furi_string_cat_printf( - temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); - } - // Parse currency code - if((emv_data->currency_code)) { - FuriString* currency_name; - currency_name = furi_string_alloc(); - if(nfc_emv_parser_get_currency_name( - nfc->dev->storage, emv_data->currency_code, currency_name)) { - furi_string_cat_printf(temp_str, "\nCur: %s ", furi_string_get_cstr(currency_name)); - } - furi_string_free(currency_name); - } - // Parse country code - if((emv_data->country_code)) { - FuriString* country_name; - country_name = furi_string_alloc(); - if(nfc_emv_parser_get_country_name( - nfc->dev->storage, emv_data->country_code, country_name)) { - furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name)); - } - furi_string_free(country_name); - } - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_emv_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - return consumed; -} - -void nfc_scene_emv_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index 3ce4f6de83..c024d31295 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_exit_confirm_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Exit"); @@ -22,16 +22,16 @@ void nfc_scene_exit_confirm_on_enter(void* context) { } bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadCardType)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSelectProtocol)) { consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneReadCardType); + nfc->scene_manager, NfcSceneSelectProtocol); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); @@ -45,7 +45,7 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_exit_confirm_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clean view dialog_ex_reset(nfc->dialog_ex); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 7f5bc7e758..7f51b71741 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -1,92 +1,72 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, - SubmenuIndexNfcVUnlock, - SubmenuIndexNfcVSniff, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); + view_dispatcher_send_custom_event(instance->view_dispatcher, index); } void nfc_scene_extra_actions_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; + NfcApp* instance = context; + Submenu* submenu = instance->submenu; submenu_add_item( submenu, "Read Specific Card Type", SubmenuIndexReadCardType, nfc_scene_extra_actions_submenu_callback, - nfc); + instance); submenu_add_item( submenu, "Mifare Classic Keys", SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, - nfc); + instance); submenu_add_item( submenu, "Unlock NTAG/Ultralight", SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Unlock SLIX-L", - SubmenuIndexNfcVUnlock, - nfc_scene_extra_actions_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Listen NfcV Reader", - SubmenuIndexNfcVSniff, - nfc_scene_extra_actions_submenu_callback, - nfc); + instance); submenu_set_selected_item( - submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + submenu, scene_manager_get_scene_state(instance->scene_manager, NfcSceneExtraActions)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexMfClassicKeys) { - if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeys); + if(nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); + scene_manager_previous_scene(instance->scene_manager); } consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + mf_ultralight_auth_reset(instance->mf_ul_auth); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); consumed = true; } else if(event.event == SubmenuIndexReadCardType) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0); - scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType); - consumed = true; - } else if(event.event == SubmenuIndexNfcVUnlock) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlockMenu); - consumed = true; - } else if(event.event == SubmenuIndexNfcVSniff) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVSniff); + scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); consumed = true; } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneExtraActions, event.event); } return consumed; } void nfc_scene_extra_actions_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - submenu_reset(nfc->submenu); + submenu_reset(instance->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_field.c b/applications/main/nfc/scenes/nfc_scene_field.c index e3eb6a7088..d5eb916a85 100644 --- a/applications/main/nfc/scenes/nfc_scene_field.c +++ b/applications/main/nfc/scenes/nfc_scene_field.c @@ -1,10 +1,10 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_field_on_enter(void* context) { - Nfc* nfc = context; - - furi_hal_nfc_field_on(); + NfcApp* nfc = context; + furi_hal_nfc_low_power_mode_stop(); + furi_hal_nfc_poller_field_on(); Popup* popup = nfc->popup; popup_set_header( popup, @@ -25,9 +25,9 @@ bool nfc_scene_field_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_field_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; - furi_hal_nfc_field_off(); + furi_hal_nfc_low_power_mode_start(); notification_internal_message(nfc->notifications, &sequence_reset_blue); popup_reset(nfc->popup); } diff --git a/applications/main/nfc/scenes/nfc_scene_file_select.c b/applications/main/nfc/scenes/nfc_scene_file_select.c index ce7ec92f46..e1edcadb1b 100644 --- a/applications/main/nfc/scenes/nfc_scene_file_select.c +++ b/applications/main/nfc/scenes/nfc_scene_file_select.c @@ -1,28 +1,20 @@ -#include "../nfc_i.h" -#include "nfc/nfc_device.h" +#include "../nfc_app_i.h" void nfc_scene_file_select_on_enter(void* context) { - Nfc* nfc = context; - nfc_device_data_clear(&nfc->dev->dev_data); + NfcApp* instance = context; - // Process file_select return - nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); - if(!furi_string_size(nfc->dev->load_path)) { - furi_string_set_str(nfc->dev->load_path, NFC_APP_FOLDER); - } - if(nfc_file_select(nfc->dev)) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, 0); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); + if(nfc_load_from_file_select(instance)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSavedMenu); } else { - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); + scene_manager_previous_scene(instance->scene_manager); } - nfc_device_set_loading_callback(nfc->dev, NULL, nfc); } bool nfc_scene_file_select_on_event(void* context, SceneManagerEvent event) { UNUSED(context); UNUSED(event); - return false; + bool consumed = false; + return consumed; } void nfc_scene_file_select_on_exit(void* context) { diff --git a/applications/main/nfc/scenes/nfc_scene_generate_info.c b/applications/main/nfc/scenes/nfc_scene_generate_info.c index 7b84ae43b1..c4f86ff4a6 100644 --- a/applications/main/nfc/scenes/nfc_scene_generate_info.c +++ b/applications/main/nfc/scenes/nfc_scene_generate_info.c @@ -1,50 +1,55 @@ -#include "../nfc_i.h" -#include "lib/nfc/helpers/nfc_generators.h" +#include "../nfc_app_i.h" -void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; +void nfc_scene_generate_info_widget_callback(GuiButtonType result, InputType type, void* context) { + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + if(type == InputTypeShort) { + if(result == GuiButtonTypeRight) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } + } } void nfc_scene_generate_info_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + furi_assert((protocol == NfcProtocolMfUltralight) || (protocol == NfcProtocolMfClassic)); + + const Iso14443_3aData* iso14443_3a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_3a); // Setup dialog view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - DialogEx* dialog_ex = nfc->dialog_ex; - dialog_ex_set_right_button_text(dialog_ex, "More"); + Widget* widget = instance->widget; + widget_add_button_element( + widget, GuiButtonTypeRight, "More", nfc_scene_generate_info_widget_callback, instance); // Create info text - FuriString* info_str = furi_string_alloc_printf( - "%s\n%s\nUID:", nfc->generator->name, nfc_get_dev_type(data->type)); + NfcDataGeneratorType type = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneGenerateInfo); + const char* name = nfc_data_generator_get_name(type); + widget_add_string_element(widget, 0, 0, AlignLeft, AlignTop, FontPrimary, name); + widget_add_string_element(widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "NFC-A"); + FuriString* temp_str = furi_string_alloc_printf("UID:"); // Append UID - for(int i = 0; i < data->uid_len; ++i) { - furi_string_cat_printf(info_str, " %02X", data->uid[i]); + for(int i = 0; i < iso14443_3a_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", iso14443_3a_data->uid[i]); } - nfc_text_store_set(nfc, furi_string_get_cstr(info_str)); - furi_string_free(info_str); - - dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 0, AlignLeft, AlignTop); - dialog_ex_set_context(dialog_ex, nfc); - dialog_ex_set_result_callback(dialog_ex, nfc_scene_generate_info_dialog_callback); + widget_add_string_element( + widget, 0, 25, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DialogExResultRight) { - // Switch either to NfcSceneMfClassicMenu or NfcSceneMfUltralightMenu - if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu); - } else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu); - } + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu); consumed = true; } } @@ -53,8 +58,8 @@ bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_generate_info_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clean views - dialog_ex_reset(nfc->dialog_ex); + widget_reset(instance->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_info.c b/applications/main/nfc/scenes/nfc_scene_info.c new file mode 100644 index 0000000000..6e9d504975 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_info.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_info_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneInfo, context); +} + +bool nfc_scene_info_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneInfo, context, event); +} + +void nfc_scene_info_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneInfo, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c deleted file mode 100644 index dcb02d3645..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_classic_data_on_enter(void* context) { - Nfc* nfc = context; - MfClassicType type = nfc->dev->dev_data.mf_classic_data.type; - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; - TextBox* text_box = nfc->text_box; - - text_box_set_font(text_box, TextBoxFontHex); - - int card_blocks = 0; - if(type == MfClassicType1k) { - card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; - } else if(type == MfClassicType4k) { - // 16 sectors of 4 blocks each plus 8 sectors of 16 blocks each - card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4 + 8 * 16; - } else if(type == MfClassicTypeMini) { - card_blocks = MF_MINI_TOTAL_SECTORS_NUM * 4; - } - - int bytes_written = 0; - for(int block_num = 0; block_num < card_blocks; block_num++) { - bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); - if(is_sec_trailer) { - uint8_t sector_num = mf_classic_get_sector_by_block(block_num); - MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(data, sector_num); - // Key A - for(size_t i = 0; i < sizeof(sec_tr->key_a); i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { - furi_string_cat_printf( - nfc->text_box_store, "%02X%02X ", sec_tr->key_a[i], sec_tr->key_a[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - // Access bytes - for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf( - nfc->text_box_store, - "%02X%02X ", - sec_tr->access_bits[i], - sec_tr->access_bits[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - // Key B - for(size_t i = 0; i < sizeof(sec_tr->key_b); i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { - furi_string_cat_printf( - nfc->text_box_store, "%02X%02X ", sec_tr->key_b[i], sec_tr->key_b[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - } else { - // Write data block - for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf( - nfc->text_box_store, - "%02X%02X ", - data->block[block_num].value[i], - data->block[block_num].value[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - } - } - text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); -} - -bool nfc_scene_mf_classic_data_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void nfc_scene_mf_classic_data_on_exit(void* context) { - Nfc* nfc = context; - - // Clean view - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c new file mode 100644 index 0000000000..987f81837a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c @@ -0,0 +1,154 @@ +#include "../nfc_app_i.h" + +#include + +#define NXP_MANUFACTURER_ID (0x04) + +#define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U) +#define NFC_SCENE_DETECT_READER_WAIT_NONCES_TIMEOUT_MS (1000) + +static const NotificationSequence sequence_detect_reader = { + &message_green_255, + &message_blue_255, + &message_do_not_reset, + NULL, +}; + +void nfc_scene_mf_classic_detect_reader_view_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +NfcCommand nfc_scene_mf_classic_detect_listener_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcApp* instance = context; + MfClassicListenerEvent* mfc_event = event.event_data; + + if(mfc_event->type == MfClassicListenerEventTypeAuthContextPartCollected) { + MfClassicAuthContext* auth_ctx = &mfc_event->data->auth_context; + mfkey32_logger_add_nonce(instance->mfkey32_logger, auth_ctx); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerUpdate); + } + + return NfcCommandContinue; +} + +void nfc_scene_mf_classic_timer_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventTimerExpired); +} + +void nfc_scene_mf_classic_detect_reader_on_enter(void* context) { + NfcApp* instance = context; + + if(nfc_device_get_protocol(instance->nfc_device) == NfcProtocolInvalid) { + Iso14443_3aData iso3_data = { + .uid_len = 7, + .uid = {0}, + .atqa = {0x44, 0x00}, + .sak = 0x08, + }; + iso3_data.uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&iso3_data.uid[1], iso3_data.uid_len - 1); + MfClassicData* mfc_data = mf_classic_alloc(); + + mfc_data->type = MfClassicType4k; + iso14443_3a_copy(mfc_data->iso14443_3a_data, &iso3_data); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); + + mf_classic_free(mfc_data); + } + + const Iso14443_3aData* iso3_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_3a); + uint32_t cuid = iso14443_3a_get_cuid(iso3_data); + + instance->mfkey32_logger = mfkey32_logger_alloc(cuid); + instance->timer = + furi_timer_alloc(nfc_scene_mf_classic_timer_callback, FuriTimerTypeOnce, instance); + + detect_reader_set_nonces_max(instance->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); + detect_reader_set_callback( + instance->detect_reader, nfc_scene_mf_classic_detect_reader_view_callback, instance); + + notification_message(instance->notifications, &sequence_detect_reader); + + instance->listener = nfc_listener_alloc( + instance->nfc, + NfcProtocolMfClassic, + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic)); + nfc_listener_start( + instance->listener, nfc_scene_mf_classic_detect_listener_callback, instance); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDetectReader); +} + +bool nfc_scene_mf_classic_detect_reader_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventWorkerUpdate) { + furi_timer_stop(instance->timer); + notification_message(instance->notifications, &sequence_blink_start_cyan); + + size_t nonces_pairs = 2 * mfkey32_logger_get_params_num(instance->mfkey32_logger); + detect_reader_set_state(instance->detect_reader, DetectReaderStateReaderDetected); + detect_reader_set_nonces_collected(instance->detect_reader, nonces_pairs); + if(nonces_pairs >= NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { + if(instance->listener) { + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + instance->listener = NULL; + } + detect_reader_set_state(instance->detect_reader, DetectReaderStateDone); + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_single_vibro); + notification_message(instance->notifications, &sequence_set_green_255); + } else { + furi_timer_start(instance->timer, NFC_SCENE_DETECT_READER_WAIT_NONCES_TIMEOUT_MS); + } + consumed = true; + } else if(event.event == NfcCustomEventTimerExpired) { + detect_reader_set_state(instance->detect_reader, DetectReaderStateReaderLost); + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_detect_reader); + } else if(event.event == NfcCustomEventViewExit) { + if(instance->listener) { + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + instance->listener = NULL; + } + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicMfkeyNoncesInfo); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(instance->listener) { + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + instance->listener = NULL; + } + mfkey32_logger_free(instance->mfkey32_logger); + } + + return consumed; +} + +void nfc_scene_mf_classic_detect_reader_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + detect_reader_reset(instance->detect_reader); + + furi_timer_stop(instance->timer); + furi_timer_free(instance->timer); + + // Stop notifications + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_reset_green); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 5bd24d7eac..ff7af9e1e8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -1,186 +1,270 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" + #include +#include #define TAG "NfcMfClassicDictAttack" typedef enum { - DictAttackStateIdle, DictAttackStateUserDictInProgress, - DictAttackStateFlipperDictInProgress, + DictAttackStateSystemDictInProgress, } DictAttackState; -bool nfc_dict_attack_worker_callback(NfcWorkerEvent event, void* context) { +NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) { furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - return true; -} + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolMfClassic); -void nfc_dict_attack_dict_attack_result_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventDictAttackSkip); + NfcCommand command = NfcCommandContinue; + MfClassicPollerEvent* mfc_event = event.event_data; + + NfcApp* instance = context; + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + instance->nfc_dict_context.is_card_present = true; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + instance->nfc_dict_context.is_card_present = false; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; + mfc_event->data->poller_mode.data = mfc_data; + instance->nfc_dict_context.sectors_total = + mf_classic_get_total_sectors_num(mfc_data->type); + mf_classic_get_read_sectors_and_keys( + mfc_data, + &instance->nfc_dict_context.sectors_read, + &instance->nfc_dict_context.keys_found); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) { + MfClassicKey key = {}; + if(nfc_dict_get_next_key(instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) { + mfc_event->data->key_request_data.key = key; + mfc_event->data->key_request_data.key_provided = true; + instance->nfc_dict_context.dict_keys_current++; + if(instance->nfc_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } + } else { + mfc_event->data->key_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeDataUpdate) { + MfClassicPollerEventDataUpdate* data_update = &mfc_event->data->data_update; + instance->nfc_dict_context.sectors_read = data_update->sectors_read; + instance->nfc_dict_context.keys_found = data_update->keys_found; + instance->nfc_dict_context.current_sector = data_update->current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { + nfc_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.current_sector = + mfc_event->data->next_sector_data.current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyB) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStart) { + instance->nfc_dict_context.key_attack_current_sector = + mfc_event->data->key_attack_data.current_sector; + instance->nfc_dict_context.is_key_attack = true; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStop) { + nfc_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.dict_keys_current = 0; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackComplete); + command = NfcCommandStop; + } + + return command; } -static void nfc_scene_mf_classic_dict_attack_update_view(Nfc* nfc) { - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; - uint8_t sectors_read = 0; - uint8_t keys_found = 0; +void nfc_dict_attack_dict_attack_result_callback(DictAttackEvent event, void* context) { + furi_assert(context); + NfcApp* instance = context; - // Calculate found keys and read sectors - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - dict_attack_set_keys_found(nfc->dict_attack, keys_found); - dict_attack_set_sector_read(nfc->dict_attack, sectors_read); + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip); + } } -static void nfc_scene_mf_classic_dict_attack_prepare_view(Nfc* nfc, DictAttackState state) { - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; - NfcMfClassicDictAttackData* dict_attack_data = &nfc->dev->dev_data.mf_classic_dict_attack_data; - NfcWorkerState worker_state = NfcWorkerStateReady; - MfClassicDict* dict = NULL; +static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { + NfcMfClassicDictAttackContext* mfc_dict = &instance->nfc_dict_context; - // Identify scene state - if(state == DictAttackStateIdle) { - if(mf_classic_dict_check_presence(MfClassicDictTypeUser)) { - state = DictAttackStateUserDictInProgress; - } else { - state = DictAttackStateFlipperDictInProgress; - } - } else if(state == DictAttackStateUserDictInProgress) { - state = DictAttackStateFlipperDictInProgress; + if(mfc_dict->is_key_attack) { + dict_attack_set_key_attack(instance->dict_attack, mfc_dict->key_attack_current_sector); + } else { + dict_attack_reset_key_attack(instance->dict_attack); + dict_attack_set_sectors_total(instance->dict_attack, mfc_dict->sectors_total); + dict_attack_set_sectors_read(instance->dict_attack, mfc_dict->sectors_read); + dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); + dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); + dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); } +} - // Setup view +static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(state == DictAttackStateUserDictInProgress) { - worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "MF Classic User Dictionary"); - dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - - // If failed to load user dictionary - try the system dictionary - if(!dict) { - FURI_LOG_E(TAG, "User dictionary not found"); - state = DictAttackStateFlipperDictInProgress; - } - } - if(state == DictAttackStateFlipperDictInProgress) { - worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "MF Classic System Dictionary"); - dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); - if(!dict) { - FURI_LOG_E(TAG, "Flipper dictionary not found"); - // Pass through to let the worker handle the failure - } + do { + if(!nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + + instance->nfc_dict_context.dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + if(nfc_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { + nfc_dict_free(instance->nfc_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + + dict_attack_set_header(instance->dict_attack, "MF Classic User Dictionary"); + } while(false); } - // Free previous dictionary - if(dict_attack_data->dict) { - mf_classic_dict_free(dict_attack_data->dict); + if(state == DictAttackStateSystemDictInProgress) { + instance->nfc_dict_context.dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, NfcDictModeOpenExisting, sizeof(MfClassicKey)); + dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); } - dict_attack_data->dict = dict; - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicDictAttack, state); - dict_attack_set_callback(nfc->dict_attack, nfc_dict_attack_dict_attack_result_callback, nfc); - dict_attack_set_current_sector(nfc->dict_attack, 0); - dict_attack_set_card_detected(nfc->dict_attack, data->type); + + instance->nfc_dict_context.dict_keys_total = + nfc_dict_get_total_keys(instance->nfc_dict_context.dict); dict_attack_set_total_dict_keys( - nfc->dict_attack, dict ? mf_classic_dict_get_total_keys(dict) : 0); - nfc_scene_mf_classic_dict_attack_update_view(nfc); - nfc_worker_start( - nfc->worker, worker_state, &nfc->dev->dev_data, nfc_dict_attack_worker_callback, nfc); + instance->dict_attack, instance->nfc_dict_context.dict_keys_total); + instance->nfc_dict_context.dict_keys_current = 0; + + dict_attack_set_callback( + instance->dict_attack, nfc_dict_attack_dict_attack_result_callback, instance); + nfc_scene_mf_classic_dict_attack_update_view(instance); + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack, state); } void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { - Nfc* nfc = context; - nfc_scene_mf_classic_dict_attack_prepare_view(nfc, DictAttackStateIdle); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDictAttack); - nfc_blink_read_start(nfc); - notification_message(nfc->notifications, &sequence_display_backlight_enforce_on); + NfcApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); + nfc_blink_read_start(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_on); + + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); } bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; + NfcApp* instance = context; bool consumed = false; uint32_t state = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicDictAttack); + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventSuccess) { + if(event.event == NfcCustomEventDictAttackComplete) { if(state == DictAttackStateUserDictInProgress) { - nfc_worker_stop(nfc->worker); - nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state); + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + nfc_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); consumed = true; } else { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } - } else if(event.event == NfcWorkerEventAborted) { - if(state == DictAttackStateUserDictInProgress && - dict_attack_get_card_state(nfc->dict_attack)) { - nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state); - consumed = true; - } else { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); - // Counting failed attempts too - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } - } else if(event.event == NfcWorkerEventCardDetected) { - dict_attack_set_card_detected(nfc->dict_attack, data->type); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - dict_attack_set_card_removed(nfc->dict_attack); + } else if(event.event == NfcCustomEventCardDetected) { + dict_attack_set_card_state(instance->dict_attack, true); consumed = true; - } else if(event.event == NfcWorkerEventFoundKeyA) { - dict_attack_inc_keys_found(nfc->dict_attack); - consumed = true; - } else if(event.event == NfcWorkerEventFoundKeyB) { - dict_attack_inc_keys_found(nfc->dict_attack); - consumed = true; - } else if(event.event == NfcWorkerEventNewSector) { - nfc_scene_mf_classic_dict_attack_update_view(nfc); - dict_attack_inc_current_sector(nfc->dict_attack); - consumed = true; - } else if(event.event == NfcWorkerEventNewDictKeyBatch) { - nfc_scene_mf_classic_dict_attack_update_view(nfc); - dict_attack_inc_current_dict_key(nfc->dict_attack, NFC_DICT_KEY_BATCH_SIZE); + } else if(event.event == NfcCustomEventCardLost) { + dict_attack_set_card_state(instance->dict_attack, false); consumed = true; + } else if(event.event == NfcCustomEventDictAttackDataUpdate) { + nfc_scene_mf_classic_dict_attack_update_view(instance); } else if(event.event == NfcCustomEventDictAttackSkip) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); if(state == DictAttackStateUserDictInProgress) { - nfc_worker_stop(nfc->worker); + if(instance->nfc_dict_context.is_card_present) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + nfc_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + } else { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } consumed = true; - } else if(state == DictAttackStateFlipperDictInProgress) { - nfc_worker_stop(nfc->worker); + } else if(state == DictAttackStateSystemDictInProgress) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } - } else if(event.event == NfcWorkerEventKeyAttackStart) { - dict_attack_set_key_attack( - nfc->dict_attack, - true, - nfc->dev->dev_data.mf_classic_dict_attack_data.current_sector); - } else if(event.event == NfcWorkerEventKeyAttackStop) { - dict_attack_set_key_attack(nfc->dict_attack, false, 0); - } else if(event.event == NfcWorkerEventKeyAttackNextSector) { - dict_attack_inc_key_attack_current_sector(nfc->dict_attack); } } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); consumed = true; } return consumed; } void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { - Nfc* nfc = context; - NfcMfClassicDictAttackData* dict_attack_data = &nfc->dev->dev_data.mf_classic_dict_attack_data; - // Stop worker - nfc_worker_stop(nfc->worker); - if(dict_attack_data->dict) { - mf_classic_dict_free(dict_attack_data->dict); - dict_attack_data->dict = NULL; - } - dict_attack_reset(nfc->dict_attack); - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_display_backlight_enforce_auto); + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + dict_attack_reset(instance->dict_attack); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + + nfc_dict_free(instance->nfc_dict_context.dict); + + instance->nfc_dict_context.current_sector = 0; + instance->nfc_dict_context.sectors_total = 0; + instance->nfc_dict_context.sectors_read = 0; + instance->nfc_dict_context.keys_found = 0; + instance->nfc_dict_context.dict_keys_total = 0; + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.key_attack_current_sector = 0; + instance->nfc_dict_context.is_card_present = false; + + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c deleted file mode 100644 index 8c0f493e12..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ /dev/null @@ -1,69 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL) -#define NFC_MF_CLASSIC_DATA_CHANGED (1UL) - -bool nfc_mf_classic_emulate_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - Nfc* nfc = context; - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_CHANGED); - return true; -} - -void nfc_scene_mf_classic_emulate_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - Popup* popup = nfc->popup; - popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); - if(strcmp(nfc->dev->dev_name, "") != 0) { - nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); - } else { - nfc_text_store_set(nfc, "MIFARE\nClassic"); - } - popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); - popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicEmulate, - &nfc->dev->dev_data, - nfc_mf_classic_emulate_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeBack) { - // Stop worker - nfc_worker_stop(nfc->worker); - // Check if data changed and save in shadow file - if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicEmulate) == - NFC_MF_CLASSIC_DATA_CHANGED) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_NOT_CHANGED); - // Save shadow file - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - } - consumed = false; - } - return consumed; -} - -void nfc_scene_mf_classic_emulate_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index 8a7dc2c183..3106c740ae 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -1,62 +1,86 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" + +#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100) void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* instance = context; if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + view_dispatcher_send_custom_event(instance->view_dispatcher, result); } } void nfc_scene_mf_classic_keys_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Load flipper dict keys total uint32_t flipper_dict_keys_total = 0; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, NfcDictModeOpenExisting, sizeof(MfClassicKey)); if(dict) { - flipper_dict_keys_total = mf_classic_dict_get_total_keys(dict); - mf_classic_dict_free(dict); + flipper_dict_keys_total = nfc_dict_get_total_keys(dict); + nfc_dict_free(dict); } + // Load user dict keys total uint32_t user_dict_keys_total = 0; - dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); if(dict) { - user_dict_keys_total = mf_classic_dict_get_total_keys(dict); - mf_classic_dict_free(dict); + user_dict_keys_total = nfc_dict_get_total_keys(dict); + nfc_dict_free(dict); } + FuriString* temp_str = furi_string_alloc(); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Classic Keys"); + furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 20, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total); widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Classic Keys"); - char temp_str[32]; - snprintf(temp_str, sizeof(temp_str), "System dict: %lu", flipper_dict_keys_total); - widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str); - snprintf(temp_str, sizeof(temp_str), "User dict: %lu", user_dict_keys_total); - widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str); + instance->widget, + 0, + 32, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36); widget_add_button_element( - nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc); - widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36); + instance->widget, + GuiButtonTypeCenter, + "Add", + nfc_scene_mf_classic_keys_widget_callback, + instance); if(user_dict_keys_total > 0) { widget_add_button_element( - nfc->widget, + instance->widget, GuiButtonTypeRight, "List", nfc_scene_mf_classic_keys_widget_callback, - nfc); + instance); } + furi_string_free(temp_str); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeCenter) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeysAdd); consumed = true; } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeysList); consumed = true; } } @@ -65,7 +89,7 @@ bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event) } void nfc_scene_mf_classic_keys_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - widget_reset(nfc->widget); + widget_reset(instance->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index 3a999f0311..8240003435 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -1,60 +1,62 @@ -#include "../nfc_i.h" -#include +#include "../nfc_app_i.h" void nfc_scene_mf_classic_keys_add_byte_input_callback(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); } void nfc_scene_mf_classic_keys_add_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter the key in hex"); byte_input_set_result_callback( byte_input, nfc_scene_mf_classic_keys_add_byte_input_callback, NULL, - nfc, - nfc->byte_input_store, - 6); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + instance, + instance->byte_input_store, + sizeof(MfClassicKey)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { // Add key to dict - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - if(dict) { - if(mf_classic_dict_is_key_present(dict, nfc->byte_input_store)) { - scene_manager_next_scene( - nfc->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); - } else if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); - dolphin_deed(DolphinDeedNfcMfcAdd); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); - } + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + furi_assert(dict); + + MfClassicKey key = {}; + memcpy(key.data, instance->byte_input_store, sizeof(MfClassicKey)); + if(nfc_dict_is_key_present(dict, key.data, sizeof(MfClassicKey))) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); + } else if(nfc_dict_add_key(dict, key.data, sizeof(MfClassicKey))) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed(DolphinDeedNfcMfcAdd); } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); + scene_manager_previous_scene(instance->scene_manager); } - mf_classic_dict_free(dict); + + nfc_dict_free(dict); consumed = true; } } + return consumed; } void nfc_scene_mf_classic_keys_add_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c index 0ea3f59a45..b245a2a552 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c @@ -1,42 +1,40 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_classic_keys_delete_widget_callback( GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* instance = context; if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + view_dispatcher_send_custom_event(instance->view_dispatcher, result); } } void nfc_scene_mf_classic_keys_delete_on_enter(void* context) { - Nfc* nfc = context; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + NfcApp* instance = context; + uint32_t key_index = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete); - // Setup Custom Widget view - FuriString* key_str; - key_str = furi_string_alloc(); + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicKeysDelete); + FuriString* key_str = furi_string_alloc(); widget_add_string_element( - nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); widget_add_button_element( - nfc->widget, + instance->widget, GuiButtonTypeLeft, "Cancel", nfc_scene_mf_classic_keys_delete_widget_callback, - nfc); + instance); widget_add_button_element( - nfc->widget, + instance->widget, GuiButtonTypeRight, "Delete", nfc_scene_mf_classic_keys_delete_widget_callback, - nfc); + instance); - mf_classic_dict_get_key_at_index_str(dict, key_str, key_index); + mf_user_dict_get_key_str(instance->mf_user_dict, key_index, key_str); widget_add_string_element( - nfc->widget, + instance->widget, 64, 32, AlignCenter, @@ -45,39 +43,35 @@ void nfc_scene_mf_classic_keys_delete_on_enter(void* context) { furi_string_get_cstr(key_str)); furi_string_free(key_str); - mf_classic_dict_free(dict); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_mf_classic_keys_delete_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; - uint32_t key_index = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeys); - } else if(event.event == GuiButtonTypeRight) { - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - if(mf_classic_dict_delete_index(dict, key_index)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); + if(event.event == GuiButtonTypeRight) { + uint32_t key_index = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfClassicKeysDelete); + if(mf_user_dict_delete_key(instance->mf_user_dict, key_index)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess); } else { - scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeys); + scene_manager_previous_scene(instance->scene_manager); } - mf_classic_dict_free(dict); - consumed = true; + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(instance->scene_manager); } + consumed = true; } return consumed; } void nfc_scene_mf_classic_keys_delete_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - widget_reset(nfc->widget); + mf_user_dict_free(instance->mf_user_dict); + widget_reset(instance->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c index 57f9fe6562..7370c06840 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c @@ -1,100 +1,51 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" #define NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX (100) void nfc_scene_mf_classic_keys_list_submenu_callback(void* context, uint32_t index) { - furi_assert(context); + NfcApp* instance = context; - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); + view_dispatcher_send_custom_event(instance->view_dispatcher, index); } -void nfc_scene_mf_classic_keys_list_popup_callback(void* context) { - furi_assert(context); - - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} +void nfc_scene_mf_classic_keys_list_on_enter(void* context) { + NfcApp* instance = context; -void nfc_scene_mf_classic_keys_list_prepare(Nfc* nfc, MfClassicDict* dict) { - Submenu* submenu = nfc->submenu; - uint32_t index = 0; - FuriString* temp_key; - temp_key = furi_string_alloc(); + instance->mf_user_dict = mf_user_dict_alloc(NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX); - submenu_set_header(submenu, "Select key to delete:"); - while(mf_classic_dict_get_next_key_str(dict, temp_key)) { - char* current_key = (char*)malloc(sizeof(char) * 13); - strncpy(current_key, furi_string_get_cstr(temp_key), 12); - MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); - FURI_LOG_D("ListKeys", "Key %lu: %s", index, current_key); + submenu_set_header(instance->submenu, "Select key to delete:"); + FuriString* temp_str = furi_string_alloc(); + for(size_t i = 0; i < mf_user_dict_get_keys_cnt(instance->mf_user_dict); i++) { + mf_user_dict_get_key_str(instance->mf_user_dict, i, temp_str); submenu_add_item( - submenu, current_key, index++, nfc_scene_mf_classic_keys_list_submenu_callback, nfc); + instance->submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_mf_classic_keys_list_submenu_callback, + instance); } - furi_string_free(temp_key); -} + furi_string_free(temp_str); -void nfc_scene_mf_classic_keys_list_on_enter(void* context) { - Nfc* nfc = context; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - MfClassicUserKeys_init(nfc->mfc_key_strs); - if(dict) { - uint32_t total_user_keys = mf_classic_dict_get_total_keys(dict); - if(total_user_keys < NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX) { - nfc_scene_mf_classic_keys_list_prepare(nfc, dict); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); - } else { - popup_set_header(nfc->popup, "Too many keys!", 64, 0, AlignCenter, AlignTop); - popup_set_text( - nfc->popup, - "Edit user dictionary\nwith file browser", - 64, - 12, - AlignCenter, - AlignTop); - popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback); - popup_set_context(nfc->popup, nfc); - popup_set_timeout(nfc->popup, 3000); - popup_enable_timeout(nfc->popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } - mf_classic_dict_free(dict); - } else { - popup_set_header( - nfc->popup, "Failed to load dictionary", 64, 32, AlignCenter, AlignCenter); - popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback); - popup_set_context(nfc->popup, nfc); - popup_set_timeout(nfc->popup, 3000); - popup_enable_timeout(nfc->popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } bool nfc_scene_mf_classic_keys_list_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; + bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } else { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete); - consumed = true; - } + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicKeysDelete, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeysDelete); + } else if(event.type == SceneManagerEventTypeBack) { + mf_user_dict_free(instance->mf_user_dict); } + return consumed; } void nfc_scene_mf_classic_keys_list_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - MfClassicUserKeys_it_t it; - for(MfClassicUserKeys_it(it, nfc->mfc_key_strs); !MfClassicUserKeys_end_p(it); - MfClassicUserKeys_next(it)) { - free(*MfClassicUserKeys_ref(it)); - } - MfClassicUserKeys_clear(nfc->mfc_key_strs); - submenu_reset(nfc->submenu); - popup_reset(nfc->popup); + submenu_reset(instance->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c index ab41989b2c..991c956c1c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c @@ -1,15 +1,16 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_classic_keys_warn_duplicate_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Setup view - Popup* popup = nfc->popup; + Popup* popup = instance->popup; popup_set_icon(popup, 72, 16, &I_DolphinCommon_56x48); popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); popup_set_text( @@ -20,28 +21,29 @@ void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { 24, AlignLeft, AlignTop); - popup_set_timeout(popup, 5000); - popup_set_context(popup, nfc); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_classic_keys_warn_duplicate_popup_callback); popup_enable_timeout(popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); } bool nfc_scene_mf_classic_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeysAdd); + instance->scene_manager, NfcSceneMfClassicKeysAdd); } } + return consumed; } void nfc_scene_mf_classic_keys_warn_duplicate_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - popup_reset(nfc->popup); + popup_reset(instance->popup); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c deleted file mode 100644 index 9c41636764..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSave, - SubmenuIndexEmulate, - SubmenuIndexDetectReader, - SubmenuIndexInfo, -}; - -void nfc_scene_mf_classic_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_classic_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Save", SubmenuIndexSave, nfc_scene_mf_classic_menu_submenu_callback, nfc); - submenu_add_item( - submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mf_classic_menu_submenu_callback, nfc); - if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { - submenu_add_item( - submenu, - "Detect Reader", - SubmenuIndexDetectReader, - nfc_scene_mf_classic_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_classic_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu, event.event); - if(event.event == SubmenuIndexSave) { - nfc->dev->format = NfcDeviceSaveFormatMifareClassic; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEmulate) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexDetectReader) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - dolphin_deed(DolphinDeedNfcDetectReader); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_mf_classic_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c new file mode 100644 index 0000000000..8e07043e25 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c @@ -0,0 +1,58 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_mfkey_complete_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_classic_mfkey_complete_on_enter(void* context) { + NfcApp* instance = context; + + widget_add_string_element( + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Complete!"); + widget_add_string_multiline_element( + instance->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + "Now use Mfkey32\nto extract keys"); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "OK", + nfc_scene_mf_classic_mfkey_complete_callback, + instance); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } + } else if(event.type == SceneManagerEventTypeBack) { + const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, prev_scenes, COUNT_OF(prev_scenes)); + } + + return consumed; +} + +void nfc_scene_mf_classic_mfkey_complete_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c new file mode 100644 index 0000000000..7f968b1dd6 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c @@ -0,0 +1,72 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_mfkey_nonces_info_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_classic_mfkey_nonces_info_on_enter(void* context) { + NfcApp* instance = context; + + FuriString* temp_str = furi_string_alloc(); + + size_t mfkey_params_saved = mfkey32_logger_get_params_num(instance->mfkey32_logger); + furi_string_printf(temp_str, "Nonce pairs saved: %zu\n", mfkey_params_saved); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(temp_str)); + widget_add_string_element( + instance->widget, 0, 12, AlignLeft, AlignTop, FontSecondary, "Authenticated sectors:"); + + mfkey32_logger_get_params_data(instance->mfkey32_logger, temp_str); + widget_add_text_scroll_element( + instance->widget, 0, 22, 128, 42, furi_string_get_cstr(temp_str)); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "OK", + nfc_scene_mf_classic_mfkey_nonces_info_callback, + instance); + + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_mfkey_nonces_info_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + if(mfkey32_logger_save_params( + instance->mfkey32_logger, NFC_APP_MFKEY32_LOGS_FILE_PATH)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicMfkeyComplete); + } else { + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, prev_scenes, COUNT_OF(prev_scenes)); + } + + return consumed; +} + +void nfc_scene_mf_classic_mfkey_nonces_info_on_exit(void* context) { + NfcApp* instance = context; + + mfkey32_logger_free(instance->mfkey32_logger); + + // Clear view + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c deleted file mode 100644 index 444c9a540e..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_classic_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_classic_read_success_on_enter(void* context) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - MfClassicData* mf_data = &dev_data->mf_classic_data; - - // Setup view - Widget* widget = nfc->widget; - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_mf_classic_read_success_widget_callback, nfc); - - FuriString* temp_str = NULL; - if(furi_string_size(nfc->dev->dev_data.parsed_data)) { - temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); - } else { - temp_str = furi_string_alloc_printf("\e#%s\n", nfc_mf_classic_type(mf_data->type)); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < dev_data->nfc_data.uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", dev_data->nfc_data.uid[i]); - } - uint8_t sectors_total = mf_classic_get_total_sectors_num(mf_data->type); - uint8_t keys_total = sectors_total * 2; - uint8_t keys_found = 0; - uint8_t sectors_read = 0; - mf_classic_get_read_sectors_and_keys(mf_data, §ors_read, &keys_found); - furi_string_cat_printf(temp_str, "\nKeys Found: %d/%d", keys_found, keys_total); - furi_string_cat_printf(temp_str, "\nSectors Read: %d/%d", sectors_read, sectors_total); - } - - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_mf_classic_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c deleted file mode 100644 index ffef1b7b9f..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "../nfc_i.h" -#include - -enum { - NfcSceneMfClassicUpdateStateCardSearch, - NfcSceneMfClassicUpdateStateCardFound, -}; - -bool nfc_mf_classic_update_worker_callback(NfcWorkerEvent event, void* context) { - furi_assert(context); - - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - - return true; -} - -static void nfc_scene_mf_classic_update_setup_view(Nfc* nfc) { - Popup* popup = nfc->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicUpdate); - - if(state == NfcSceneMfClassicUpdateStateCardSearch) { - popup_set_text( - nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter); - popup_set_icon(popup, 12, 23, &A_Loading_24); - } - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_classic_update_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); - nfc_scene_mf_classic_update_setup_view(nfc); - - // Setup and start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicUpdate, - &nfc->dev->dev_data, - nfc_mf_classic_update_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventSuccess) { - nfc_worker_stop(nfc->worker); - if(nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path))) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); - } - consumed = true; - } else if(event.event == NfcWorkerEventWrongCard) { - nfc_worker_stop(nfc->worker); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneMfClassicUpdate, - NfcSceneMfClassicUpdateStateCardFound); - nfc_scene_mf_classic_update_setup_view(nfc); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneMfClassicUpdate, - NfcSceneMfClassicUpdateStateCardSearch); - nfc_scene_mf_classic_update_setup_view(nfc); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_mf_classic_update_on_exit(void* context) { - Nfc* nfc = context; - nfc_worker_stop(nfc->worker); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c new file mode 100644 index 0000000000..961afdf531 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c @@ -0,0 +1,144 @@ +#include "../nfc_app_i.h" + +#include + +enum { + NfcSceneMfClassicUpdateInitialStateCardSearch, + NfcSceneMfClassicUpdateInitialStateCardFound, +}; + +NfcCommand nfc_mf_classic_update_initial_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + const MfClassicPollerEvent* mfc_event = event.event_data; + NfcApp* instance = context; + + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* updated_data = nfc_poller_get_data(instance->poller); + const MfClassicData* old_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + if(iso14443_3a_is_equal(updated_data->iso14443_3a_data, old_data->iso14443_3a_data)) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeRead; + } else { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) { + uint8_t sector_num = 0; + MfClassicKey key = {}; + MfClassicKeyType key_type = MfClassicKeyTypeA; + if(mf_classic_key_cahce_get_next_key( + instance->mfc_key_cache, §or_num, &key, &key_type)) { + mfc_event->data->read_sector_request_data.sector_num = sector_num; + mfc_event->data->read_sector_request_data.key = key; + mfc_event->data->read_sector_request_data.key_type = key_type; + mfc_event->data->read_sector_request_data.key_provided = true; + } else { + mfc_event->data->read_sector_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + const MfClassicData* updated_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, updated_data); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerExit); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_mf_classic_update_initial_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + + if(state == NfcSceneMfClassicUpdateInitialStateCardSearch) { + 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, "Updating\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_classic_update_initial_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcEmulate); + + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + mf_classic_key_cache_load_from_data(instance->mfc_key_cache, mfc_data); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardSearch); + nfc_scene_mf_classic_update_initial_setup_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_mf_classic_update_initial_worker_callback, instance); + nfc_blink_emulate_start(instance); +} + +bool nfc_scene_mf_classic_update_initial_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, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardFound); + nfc_scene_mf_classic_update_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventCardLost) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardSearch); + nfc_scene_mf_classic_update_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventWrongCard) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } else if(event.event == NfcCustomEventWorkerExit) { + if(nfc_save_shadow_file(instance)) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicUpdateInitialSuccess); + } else { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } + } + } + + return consumed; +} + +void nfc_scene_mf_classic_update_initial_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, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardSearch); + // Clear view + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c new file mode 100644 index 0000000000..02e307b01b --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c @@ -0,0 +1,43 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_update_initial_success_popup_callback(void* context) { + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_update_initial_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, "Updated!", 11, 20, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_classic_update_initial_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_classic_update_initial_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_classic_update_initial_success_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c deleted file mode 100644 index fb1868459d..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mf_classic_update_success_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_classic_update_success_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(nfc->notifications, &sequence_success); - - Popup* popup = nfc->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_mf_classic_update_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneFileSelect); - } - } - return consumed; -} - -void nfc_scene_mf_classic_update_success_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c deleted file mode 100644 index 20ebfcc70a..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "../nfc_i.h" -#include - -enum { - NfcSceneMfClassicWriteStateCardSearch, - NfcSceneMfClassicWriteStateCardFound, -}; - -bool nfc_mf_classic_write_worker_callback(NfcWorkerEvent event, void* context) { - furi_assert(context); - - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - - return true; -} - -static void nfc_scene_mf_classic_write_setup_view(Nfc* nfc) { - Popup* popup = nfc->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicWrite); - - if(state == NfcSceneMfClassicWriteStateCardSearch) { - popup_set_text( - nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); - popup_set_icon(nfc->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(nfc->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_classic_write_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); - nfc_scene_mf_classic_write_setup_view(nfc); - - // Setup and start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicWrite, - &nfc->dev->dev_data, - nfc_mf_classic_write_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_classic_write_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventSuccess) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventFail) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteFail); - consumed = true; - } else if(event.event == NfcWorkerEventWrongCard) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardFound); - nfc_scene_mf_classic_write_setup_view(nfc); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); - nfc_scene_mf_classic_write_setup_view(nfc); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_on_exit(void* context) { - Nfc* nfc = context; - - nfc_worker_stop(nfc->worker); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c deleted file mode 100644 index aeea6eef06..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_classic_write_fail_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_classic_write_fail_on_enter(void* context) { - Nfc* nfc = context; - Widget* widget = nfc->widget; - - notification_message(nfc->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, - "Not all sectors\nwere written\ncorrectly."); - - widget_add_button_element( - widget, GuiButtonTypeLeft, "Finish", nfc_scene_mf_classic_write_fail_widget_callback, nfc); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_write_fail_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneFileSelect); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneSavedMenu); - } - return consumed; -} - -void nfc_scene_mf_classic_write_fail_on_exit(void* context) { - Nfc* nfc = context; - - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c new file mode 100644 index 0000000000..79f1def1d1 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c @@ -0,0 +1,146 @@ +#include "../nfc_app_i.h" + +#include + +enum { + NfcSceneMfClassicWriteInitialStateCardSearch, + NfcSceneMfClassicWriteInitialStateCardFound, +}; + +NfcCommand + nfc_scene_mf_classic_write_initial_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfClassicPollerEvent* mfc_event = event.event_data; + const MfClassicData* write_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* tag_data = nfc_poller_get_data(instance->poller); + if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite; + } else { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) { + uint8_t sector = mfc_event->data->sec_tr_data.sector_num; + uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector); + if(mf_classic_is_block_read(write_data, sec_tr)) { + mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr]; + mfc_event->data->sec_tr_data.sector_trailer_provided = true; + } else { + mfc_event->data->sec_tr_data.sector_trailer_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) { + uint8_t block_num = mfc_event->data->write_block_data.block_num; + if(mf_classic_is_block_read(write_data, block_num)) { + mfc_event->data->write_block_data.write_block = write_data->block[block_num]; + mfc_event->data->write_block_data.write_block_provided = true; + } else { + mfc_event->data->write_block_data.write_block_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(mfc_event->type == MfClassicPollerEventTypeFail) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } + return command; +} + +static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial); + + if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { + 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_classic_write_initial_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcEmulate); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardSearch); + nfc_scene_mf_classic_write_initial_setup_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start( + instance->poller, nfc_scene_mf_classic_write_initial_worker_callback, instance); + + nfc_blink_emulate_start(instance); +} + +bool nfc_scene_mf_classic_write_initial_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, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardFound); + nfc_scene_mf_classic_write_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventCardLost) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardSearch); + nfc_scene_mf_classic_write_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventWrongCard) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicWriteInitialSuccess); + consumed = true; + } else if(event.event == NfcCustomEventPollerFailure) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitialFail); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_classic_write_initial_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, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardSearch); + // Clear view + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c new file mode 100644 index 0000000000..f85e5a80c3 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c @@ -0,0 +1,62 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_write_initial_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_classic_write_initial_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, + "Not all sectors\nwere written\ncorrectly."); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Finish", + nfc_scene_mf_classic_write_initial_fail_widget_callback, + instance); + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_write_initial_fail_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); + } + return consumed; +} + +void nfc_scene_mf_classic_write_initial_fail_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c new file mode 100644 index 0000000000..acb75cd2e9 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c @@ -0,0 +1,43 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_write_initial_success_popup_callback(void* context) { + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_write_initial_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_classic_write_initial_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_classic_write_initial_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_classic_write_initial_success_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c deleted file mode 100644 index 00030d4fe8..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mf_classic_write_success_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_classic_write_success_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(nfc->notifications, &sequence_success); - - Popup* popup = nfc->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, nfc); - popup_set_callback(popup, nfc_scene_mf_classic_write_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneFileSelect); - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_success_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c index 2c56270e36..50025048af 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c @@ -1,20 +1,20 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_classic_wrong_card_widget_callback( GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* instance = context; if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + view_dispatcher_send_custom_event(instance->view_dispatcher, result); } } void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { - Nfc* nfc = context; - Widget* widget = nfc->widget; + NfcApp* instance = context; + Widget* widget = instance->widget; - notification_message(nfc->notifications, &sequence_error); + notification_message(instance->notifications, &sequence_error); widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); widget_add_string_element( @@ -28,26 +28,30 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { FontSecondary, "Data management\nis only possible\nwith initial card"); widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_wrong_card_widget_callback, nfc); + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_mf_classic_wrong_card_widget_callback, + instance); // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc->scene_manager); + consumed = scene_manager_previous_scene(instance->scene_manager); } } return consumed; } void nfc_scene_mf_classic_wrong_card_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - widget_reset(nfc->widget); + widget_reset(instance->widget); } \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c index 882dc5fea8..8d6a92b6c7 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c @@ -1,103 +1,93 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -#define TAG "NfcSceneMfDesfireApp" +#include "../helpers/protocol_support/mf_desfire/mf_desfire_render.h" enum SubmenuIndex { SubmenuIndexAppInfo, SubmenuIndexDynamic, // dynamic indexes start here }; -void nfc_scene_mf_desfire_popup_callback(void* context) { - furi_assert(context); +static void nfc_scene_mf_desfire_app_submenu_callback(void* context, uint32_t index) { + NfcApp* nfc = context; - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } -MifareDesfireApplication* nfc_scene_mf_desfire_app_get_app(Nfc* nfc) { - uint32_t app_idx = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >> - 1; - MifareDesfireApplication* app = nfc->dev->dev_data.mf_df_data.app_head; - for(uint32_t i = 0; i < app_idx && app; i++) { - app = app->next; - } - return app; -} +void nfc_scene_mf_desfire_app_on_enter(void* context) { + NfcApp* nfc = context; -void nfc_scene_mf_desfire_app_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + text_box_set_font(nfc->text_box, TextBoxFontHex); + submenu_add_item( + nfc->submenu, + "App info", + SubmenuIndexAppInfo, + nfc_scene_mf_desfire_app_submenu_callback, + nfc); - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} + const uint32_t app_idx = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >> 1; -void nfc_scene_mf_desfire_app_on_enter(void* context) { - Nfc* nfc = context; - MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); - if(!app) { - popup_set_icon(nfc->popup, 5, 5, &I_WarningDolphin_45x42); - popup_set_header(nfc->popup, "Empty card!", 55, 12, AlignLeft, AlignBottom); - popup_set_callback(nfc->popup, nfc_scene_mf_desfire_popup_callback); - popup_set_context(nfc->popup, nfc); - popup_set_timeout(nfc->popup, 3000); - popup_enable_timeout(nfc->popup); - popup_set_text(nfc->popup, "No application\nfound.", 55, 15, AlignLeft, AlignTop); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } else { - text_box_set_font(nfc->text_box, TextBoxFontHex); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + const MfDesfireApplication* app = simple_array_cget(data->applications, app_idx); + + FuriString* label = furi_string_alloc(); + + for(uint32_t i = 0; i < simple_array_get_count(app->file_ids); ++i) { + const MfDesfireFileId file_id = + *(const MfDesfireFileId*)simple_array_cget(app->file_ids, i); + furi_string_printf(label, "File %d", file_id); submenu_add_item( nfc->submenu, - "App info", - SubmenuIndexAppInfo, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, nfc_scene_mf_desfire_app_submenu_callback, nfc); + } - FuriString* label = furi_string_alloc(); - int idx = SubmenuIndexDynamic; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - furi_string_printf(label, "File %d", file->id); - submenu_add_item( - nfc->submenu, - furi_string_get_cstr(label), - idx++, - nfc_scene_mf_desfire_app_submenu_callback, - nfc); - } - furi_string_free(label); + furi_string_free(label); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); - } + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp); + + const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp); if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else { - MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + + const uint32_t app_index = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >> 1; + const MfDesfireApplication* app = simple_array_cget(data->applications, app_index); + TextBox* text_box = nfc->text_box; furi_string_reset(nfc->text_box_store); if(event.event == SubmenuIndexAppInfo) { - mf_df_cat_application_info(app, nfc->text_box_store); + const MfDesfireApplicationId* app_id = + simple_array_cget(data->application_ids, app_index); + nfc_render_mf_desfire_application_id(app_id, nfc->text_box_store); + nfc_render_mf_desfire_application(app, nfc->text_box_store); } else { - uint16_t index = event.event - SubmenuIndexDynamic; - MifareDesfireFile* file = app->file_head; - for(int i = 0; file && i < index; i++) { - file = file->next; - } - if(!file) { - return false; - } - mf_df_cat_file(file, nfc->text_box_store); + const uint32_t file_index = event.event - SubmenuIndexDynamic; + const MfDesfireFileId* file_id = simple_array_cget(app->file_ids, file_index); + const MfDesfireFileSettings* file_settings = + simple_array_cget(app->file_settings, file_index); + const MfDesfireFileData* file_data = simple_array_cget(app->file_data, file_index); + nfc_render_mf_desfire_file_id(file_id, nfc->text_box_store); + nfc_render_mf_desfire_file_settings_data( + file_settings, file_data, nfc->text_box_store); } text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); consumed = true; } + } else if(event.type == SceneManagerEventTypeBack) { if(state & 1) { view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); @@ -110,10 +100,9 @@ bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_mf_desfire_app_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear views - popup_reset(nfc->popup); text_box_reset(nfc->text_box); furi_string_reset(nfc->text_box_store); submenu_reset(nfc->submenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c deleted file mode 100644 index c7caee8dc6..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c +++ /dev/null @@ -1,104 +0,0 @@ -#include "../nfc_i.h" - -#define TAG "NfcSceneMfDesfireData" - -enum { - MifareDesfireDataStateMenu, - MifareDesfireDataStateItem, // MUST be last, states >= this correspond with submenu index -}; - -enum SubmenuIndex { - SubmenuIndexCardInfo, - SubmenuIndexDynamic, // dynamic indexes start here -}; - -void nfc_scene_mf_desfire_data_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = (Nfc*)context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_desfire_data_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireData); - MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; - - text_box_set_font(nfc->text_box, TextBoxFontHex); - - submenu_add_item( - submenu, - "Card info", - SubmenuIndexCardInfo, - nfc_scene_mf_desfire_data_submenu_callback, - nfc); - - FuriString* label = furi_string_alloc(); - int idx = SubmenuIndexDynamic; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - furi_string_printf(label, "App %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - submenu_add_item( - submenu, - furi_string_get_cstr(label), - idx++, - nfc_scene_mf_desfire_data_submenu_callback, - nfc); - } - furi_string_free(label); - - if(state >= MifareDesfireDataStateItem) { - submenu_set_selected_item( - nfc->submenu, state - MifareDesfireDataStateItem + SubmenuIndexDynamic); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateMenu); - } - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_desfire_data_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireData); - MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; - - if(event.type == SceneManagerEventTypeCustom) { - TextBox* text_box = nfc->text_box; - furi_string_reset(nfc->text_box_store); - if(event.event == SubmenuIndexCardInfo) { - mf_df_cat_card_info(data, nfc->text_box_store); - text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneMfDesfireData, - MifareDesfireDataStateItem + SubmenuIndexCardInfo); - consumed = true; - } else { - uint16_t index = event.event - SubmenuIndexDynamic; - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateItem + index); - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, index << 1); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state >= MifareDesfireDataStateItem) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateMenu); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_desfire_data_on_exit(void* context) { - Nfc* nfc = context; - - // Clear views - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c deleted file mode 100644 index 9cebefedfa..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSave, - SubmenuIndexEmulateUid, - SubmenuIndexInfo, -}; - -void nfc_scene_mf_desfire_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_desfire_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Save", SubmenuIndexSave, nfc_scene_mf_desfire_menu_submenu_callback, nfc); - submenu_add_item( - submenu, - "Emulate UID", - SubmenuIndexEmulateUid, - nfc_scene_mf_desfire_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_desfire_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_desfire_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSave) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfDesfireMenu, SubmenuIndexSave); - nfc->dev->format = NfcDeviceSaveFormatMifareDesfire; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEmulateUid) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_desfire_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c new file mode 100644 index 0000000000..76834e3f4f --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c @@ -0,0 +1,112 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/mf_desfire/mf_desfire_render.h" + +enum { + MifareDesfireMoreInfoStateMenu, + MifareDesfireMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index +}; + +enum SubmenuIndex { + SubmenuIndexCardInfo, + SubmenuIndexDynamic, // dynamic indices start here +}; + +void nfc_scene_mf_desfire_more_info_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireMoreInfo); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + + text_box_set_font(nfc->text_box, TextBoxFontHex); + + submenu_add_item( + submenu, + "Card info", + SubmenuIndexCardInfo, + nfc_protocol_support_common_submenu_callback, + nfc); + + FuriString* label = furi_string_alloc(); + + for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { + const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); + furi_string_printf( + label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + } + + furi_string_free(label); + + if(state >= MifareDesfireMoreInfoStateItem) { + submenu_set_selected_item( + nfc->submenu, state - MifareDesfireMoreInfoStateItem + SubmenuIndexDynamic); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfDesfireMoreInfo, MifareDesfireMoreInfoStateMenu); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_desfire_more_info_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireMoreInfo); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + + if(event.type == SceneManagerEventTypeCustom) { + TextBox* text_box = nfc->text_box; + furi_string_reset(nfc->text_box_store); + + if(event.event == SubmenuIndexCardInfo) { + nfc_render_mf_desfire_data(data, nfc->text_box_store); + text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneMfDesfireMoreInfo, + MifareDesfireMoreInfoStateItem + SubmenuIndexCardInfo); + consumed = true; + } else { + const uint32_t index = event.event - SubmenuIndexDynamic; + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneMfDesfireMoreInfo, + MifareDesfireMoreInfoStateItem + index); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, index << 1); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state >= MifareDesfireMoreInfoStateItem) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfDesfireMoreInfo, MifareDesfireMoreInfoStateMenu); + } else { + // Return directly to the Info scene + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_mf_desfire_more_info_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c deleted file mode 100644 index 633549eb5d..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mf_desfire_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_desfire_read_success_on_enter(void* context) { - Nfc* nfc = context; - - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; - Widget* widget = nfc->widget; - - // Prepare string for data display - FuriString* temp_str = NULL; - if(furi_string_size(nfc->dev->dev_data.parsed_data)) { - temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); - } else { - temp_str = furi_string_alloc_printf("\e#MIFARE DESFire\n"); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - - uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); - uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%lu", bytes_total); - if(data->version.sw_storage & 1) { - furi_string_push_back(temp_str, '+'); - } - furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); - - uint16_t n_apps = 0; - uint16_t n_files = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - n_apps++; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - n_files++; - } - } - furi_string_cat_printf(temp_str, "%d Application", n_apps); - if(n_apps != 1) { - furi_string_push_back(temp_str, 's'); - } - furi_string_cat_printf(temp_str, ", %d file", n_files); - if(n_files != 1) { - furi_string_push_back(temp_str, 's'); - } - } - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - // Add text scroll element - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - // Add button elements - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_desfire_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_mf_desfire_read_success_widget_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_desfire_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_mf_desfire_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clean dialog - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c new file mode 100644 index 0000000000..1c4be46306 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c @@ -0,0 +1,67 @@ +#include "../nfc_app_i.h" + +NfcCommand + nfc_scene_mf_ultralight_capture_pass_worker_callback(NfcGenericEvent event, void* context) { + NfcApp* instance = context; + MfUltralightListenerEvent* mfu_event = event.event_data; + MfUltralightAuth* mauth = instance->mf_ul_auth; + + if(mfu_event->type == MfUltralightListenerEventTypeAuth) { + mauth->password = mfu_event->data->password; + view_dispatcher_send_custom_event( + instance->view_dispatcher, MfUltralightListenerEventTypeAuth); + } + + return NfcCommandContinue; +} + +void nfc_scene_mf_ultralight_capture_pass_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + widget_add_string_multiline_element( + instance->widget, + 54, + 30, + AlignLeft, + AlignCenter, + FontPrimary, + "Touch the\nreader to get\npassword..."); + widget_add_icon_element(instance->widget, 0, 15, &I_Modern_reader_18x34); + widget_add_icon_element(instance->widget, 20, 12, &I_Move_flipper_26x39); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + + // Start worker + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfUltralight, data); + nfc_listener_start( + instance->listener, nfc_scene_mf_ultralight_capture_pass_worker_callback, instance); + + nfc_blink_read_start(instance); +} + +bool nfc_scene_mf_ultralight_capture_pass_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == MfUltralightListenerEventTypeAuth) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_mf_ultralight_capture_pass_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + widget_reset(instance->widget); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c deleted file mode 100644 index 8cd223ee64..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_ultralight_data_on_enter(void* context) { - Nfc* nfc = context; - MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; - TextBox* text_box = nfc->text_box; - - text_box_set_font(text_box, TextBoxFontHex); - for(uint16_t i = 0; i < data->data_size; i += 2) { - if(!(i % 8) && i) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - furi_string_cat_printf(nfc->text_box_store, "%02X%02X ", data->data[i], data->data[i + 1]); - } - text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); -} - -bool nfc_scene_mf_ultralight_data_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void nfc_scene_mf_ultralight_data_on_exit(void* context) { - Nfc* nfc = context; - - // Clean view - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); -} \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c deleted file mode 100644 index 9d8f17f9a2..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ /dev/null @@ -1,74 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_MF_UL_DATA_NOT_CHANGED (0UL) -#define NFC_MF_UL_DATA_CHANGED (1UL) - -bool nfc_mf_ultralight_emulate_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - Nfc* nfc = context; - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_CHANGED); - return true; -} - -void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - MfUltralightType type = nfc->dev->dev_data.mf_ul_data.type; - bool is_ultralight = (type == MfUltralightTypeUL11) || (type == MfUltralightTypeUL21) || - (type == MfUltralightTypeUnknown); - Popup* popup = nfc->popup; - popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); - if(strcmp(nfc->dev->dev_name, "") != 0) { - nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); - } else if(is_ultralight) { - nfc_text_store_set(nfc, "MIFARE\nUltralight"); - } else { - nfc_text_store_set(nfc, "MIFARE\nNTAG"); - } - popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); - popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfUltralightEmulate, - &nfc->dev->dev_data, - nfc_mf_ultralight_emulate_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeBack) { - // Stop worker - nfc_worker_stop(nfc->worker); - // Check if data changed and save in shadow file - if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate) == - NFC_MF_UL_DATA_CHANGED) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_NOT_CHANGED); - // Save shadow file - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - } - consumed = false; - } - return consumed; -} - -void nfc_scene_mf_ultralight_emulate_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c index 089187d5bc..6db6023d27 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_ultralight_key_input_byte_input_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); } void nfc_scene_mf_ultralight_key_input_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view ByteInput* byte_input = nfc->byte_input; @@ -17,13 +17,13 @@ void nfc_scene_mf_ultralight_key_input_on_enter(void* context) { nfc_scene_mf_ultralight_key_input_byte_input_callback, NULL, nfc, - nfc->byte_input_store, + nfc->mf_ul_auth->password.data, 4); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); } bool nfc_scene_mf_ultralight_key_input_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -36,7 +36,7 @@ bool nfc_scene_mf_ultralight_key_input_on_event(void* context, SceneManagerEvent } void nfc_scene_mf_ultralight_key_input_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c deleted file mode 100644 index b3bd780f4b..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c +++ /dev/null @@ -1,89 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexUnlock, - SubmenuIndexSave, - SubmenuIndexEmulate, - SubmenuIndexInfo, -}; - -void nfc_scene_mf_ultralight_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_ultralight_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; - - if(!mf_ul_is_full_capture(data) && data->type != MfUltralightTypeULC) { - submenu_add_item( - submenu, - "Unlock", - SubmenuIndexUnlock, - nfc_scene_mf_ultralight_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); - if(mf_ul_emulation_supported(data)) { - submenu_add_item( - submenu, - "Emulate", - SubmenuIndexEmulate, - nfc_scene_mf_ultralight_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSave) { - nfc->dev->format = NfcDeviceSaveFormatMifareUl; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEmulate) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexUnlock) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightMenu, event.event); - - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_mf_ultralight_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c deleted file mode 100644 index 2ab5e3f3f4..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c +++ /dev/null @@ -1,116 +0,0 @@ -#include "../nfc_i.h" - -typedef enum { - NfcSceneMfUlReadStateIdle, - NfcSceneMfUlReadStateDetecting, - NfcSceneMfUlReadStateReading, - NfcSceneMfUlReadStateNotSupportedCard, -} NfcSceneMfUlReadState; - -bool nfc_scene_mf_ultralight_read_auth_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - - if(event == NfcWorkerEventMfUltralightPassKey) { - memcpy(nfc->dev->dev_data.mf_ul_data.auth_key, nfc->byte_input_store, 4); - } else { - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - } - return true; -} - -void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState state) { - uint32_t curr_state = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth); - if(curr_state != state) { - if(state == NfcSceneMfUlReadStateDetecting) { - popup_reset(nfc->popup); - popup_set_text(nfc->popup, "Apply the\ntarget card", 97, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); - nfc_blink_read_start(nfc); - } else if(state == NfcSceneMfUlReadStateReading) { - popup_reset(nfc->popup); - popup_set_header( - nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); - nfc_blink_detect_start(nfc); - } else if(state == NfcSceneMfUlReadStateNotSupportedCard) { - popup_reset(nfc->popup); - popup_set_header(nfc->popup, "Wrong type of card!", 64, 3, AlignCenter, AlignTop); - popup_set_text( - nfc->popup, - "Only MIFARE\nUltralight & NTAG\nare supported", - 4, - 22, - AlignLeft, - AlignTop); - popup_set_icon(nfc->popup, 73, 20, &I_DolphinCommon_56x48); - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_error); - notification_message(nfc->notifications, &sequence_set_red_255); - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth, state); - } -} - -void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) { - Nfc* nfc = context; - - nfc_device_clear(nfc->dev); - // Setup view - nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateDetecting); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - // Start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateReadMfUltralightReadAuth, - &nfc->dev->dev_data, - nfc_scene_mf_ultralight_read_auth_worker_callback, - nfc); -} - -bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if((event.event == NfcWorkerEventSuccess) || (event.event == NfcWorkerEventFail)) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuthResult); - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateReading); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateDetecting); - consumed = true; - } else if(event.event == NfcWorkerEventWrongCardDetected) { - nfc_scene_mf_ultralight_read_auth_set_state( - nfc, NfcSceneMfUlReadStateNotSupportedCard); - } - } else if(event.type == SceneManagerEventTypeBack) { - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - NfcScene next_scene; - if(mf_ul_data->auth_method == MfUltralightAuthMethodManual) { - next_scene = NfcSceneMfUltralightKeyInput; - } else if(mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { - next_scene = NfcSceneMfUltralightUnlockAuto; - } else { - next_scene = NfcSceneMfUltralightUnlockMenu; - } - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); - } - return consumed; -} - -void nfc_scene_mf_ultralight_read_auth_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - nfc_blink_stop(nfc); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfUltralightReadAuth, NfcSceneMfUlReadStateIdle); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c deleted file mode 100644 index b125e99912..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c +++ /dev/null @@ -1,116 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_ultralight_read_auth_result_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { - Nfc* nfc = context; - - // Setup dialog view - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data); - Widget* widget = nfc->widget; - const char* title; - FuriString* temp_str; - temp_str = furi_string_alloc(); - - if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { - if(mf_ul_data->auth_success) { - title = "All pages are unlocked!"; - } else { - title = "All unlocked but failed auth!"; - } - } else { - title = "Not all pages unlocked!"; - } - widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title); - furi_string_set(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - widget_add_string_element( - widget, 0, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - if(mf_ul_data->auth_success) { - furi_string_printf( - temp_str, - "Password: %02X %02X %02X %02X", - config_pages->auth_data.pwd.raw[0], - config_pages->auth_data.pwd.raw[1], - config_pages->auth_data.pwd.raw[2], - config_pages->auth_data.pwd.raw[3]); - widget_add_string_element( - widget, 0, 28, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - furi_string_printf( - temp_str, - "PACK: %02X %02X", - config_pages->auth_data.pack.raw[0], - config_pages->auth_data.pack.raw[1]); - widget_add_string_element( - widget, 0, 39, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - } - furi_string_printf( - temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); - widget_add_string_element( - widget, 0, 50, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_button_element( - widget, - GuiButtonTypeRight, - "Save", - nfc_scene_mf_ultralight_read_auth_result_widget_callback, - nfc); - - furi_string_free(temp_str); - notification_message(nfc->notifications, &sequence_set_green_255); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_ultralight_read_auth_result_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeRight) { - nfc->dev->format = NfcDeviceSaveFormatMifareUl; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - if(mf_ul_data->auth_method == MfUltralightAuthMethodManual || - mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } else { - NfcScene next_scene; - if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { - next_scene = NfcSceneMfUltralightMenu; - } else { - next_scene = NfcSceneMfUltralightUnlockMenu; - } - - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); - } - } - - return consumed; -} - -void nfc_scene_mf_ultralight_read_auth_result_on_exit(void* context) { - Nfc* nfc = context; - - // Clean views - widget_reset(nfc->widget); - - notification_message_block(nfc->notifications, &sequence_reset_green); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c deleted file mode 100644 index cb5ccd6e82..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_ultralight_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { - Nfc* nfc = context; - - // Setup widget view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - Widget* widget = nfc->widget; - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - nfc_scene_mf_ultralight_read_success_widget_callback, - nfc); - widget_add_button_element( - widget, - GuiButtonTypeRight, - "More", - nfc_scene_mf_ultralight_read_success_widget_callback, - nfc); - - FuriString* temp_str = NULL; - if(furi_string_size(nfc->dev->dev_data.parsed_data)) { - temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); - } else { - temp_str = furi_string_alloc_printf("\e#%s\n", nfc_mf_ul_type(mf_ul_data->type, true)); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", data->uid[i]); - } - furi_string_cat_printf( - temp_str, "\nPages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); - if(mf_ul_data->data_read != mf_ul_data->data_size) { - furi_string_cat_printf(temp_str, "\nPassword-protected pages!"); - } - } - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_ultralight_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_mf_ultralight_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clean view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c deleted file mode 100644 index c59fe3a7d0..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "../nfc_i.h" - -bool nfc_scene_mf_ultralight_unlock_auto_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - return true; -} - -void nfc_scene_mf_ultralight_unlock_auto_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - widget_add_string_multiline_element( - nfc->widget, - 54, - 30, - AlignLeft, - AlignCenter, - FontPrimary, - "Touch the\nreader to get\npassword..."); - widget_add_icon_element(nfc->widget, 0, 15, &I_Modern_reader_18x34); - widget_add_icon_element(nfc->widget, 20, 12, &I_Move_flipper_26x39); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - - // Start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfUltralightEmulate, - &nfc->dev->dev_data, - nfc_scene_mf_ultralight_unlock_auto_worker_callback, - nfc); - - nfc_blink_read_start(nfc); -} - -bool nfc_scene_mf_ultralight_unlock_auto_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if((event.event == NfcWorkerEventMfUltralightPwdAuth)) { - MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; - memcpy(nfc->byte_input_store, auth->pwd.raw, sizeof(auth->pwd.raw)); - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAuto; - nfc_worker_stop(nfc->worker); - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_mf_ultralight_unlock_auto_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - widget_reset(nfc->widget); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c index 484629b0bb..4d97040ee2 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c @@ -1,29 +1,29 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" enum SubmenuIndex { - SubmenuIndexMfUlUnlockMenuAuto, + SubmenuIndexMfUlUnlockMenuReader, SubmenuIndexMfUlUnlockMenuAmeebo, SubmenuIndexMfUlUnlockMenuXiaomi, SubmenuIndexMfUlUnlockMenuManual, }; void nfc_scene_mf_ultralight_unlock_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; Submenu* submenu = nfc->submenu; uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); - if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { + if(nfc_device_get_protocol(nfc->nfc_device) == NfcProtocolMfUltralight) { submenu_add_item( submenu, "Unlock With Reader", - SubmenuIndexMfUlUnlockMenuAuto, + SubmenuIndexMfUlUnlockMenuReader, nfc_scene_mf_ultralight_unlock_menu_submenu_callback, nfc); } @@ -50,24 +50,25 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { } bool nfc_scene_mf_ultralight_unlock_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexMfUlUnlockMenuManual) { - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodManual; + nfc->mf_ul_auth->type = MfUltralightAuthTypeManual; scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightKeyInput); consumed = true; } else if(event.event == SubmenuIndexMfUlUnlockMenuAmeebo) { - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAmeebo; + nfc->mf_ul_auth->type = MfUltralightAuthTypeAmiibo; scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); consumed = true; } else if(event.event == SubmenuIndexMfUlUnlockMenuXiaomi) { - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodXiaomi; + nfc->mf_ul_auth->type = MfUltralightAuthTypeXiaomi; scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); consumed = true; - } else if(event.event == SubmenuIndexMfUlUnlockMenuAuto) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); + } else if(event.event == SubmenuIndexMfUlUnlockMenuReader) { + nfc->mf_ul_auth->type = MfUltralightAuthTypeReader; + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightCapturePass); consumed = true; } scene_manager_set_scene_state( @@ -77,7 +78,7 @@ bool nfc_scene_mf_ultralight_unlock_menu_on_event(void* context, SceneManagerEve } void nfc_scene_mf_ultralight_unlock_menu_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index af2eca0ce5..6be051cedd 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -1,45 +1,41 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" #include void nfc_scene_mf_ultralight_unlock_warn_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; - MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_unlock_warn_dialog_callback); - if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { + MfUltralightAuthType type = nfc->mf_ul_auth->type; + if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) { // Build dialog text - MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; FuriString* password_str = furi_string_alloc_set_str("Try to unlock the card with\npassword: "); - for(size_t i = 0; i < sizeof(auth->pwd.raw); ++i) { - furi_string_cat_printf(password_str, "%02X ", nfc->byte_input_store[i]); + for(size_t i = 0; i < sizeof(nfc->mf_ul_auth->password.data); i++) { + furi_string_cat_printf(password_str, "%02X ", nfc->mf_ul_auth->password.data[i]); } furi_string_cat_str(password_str, "?\nCaution, a wrong password\ncan block the card!"); nfc_text_store_set(nfc, furi_string_get_cstr(password_str)); furi_string_free(password_str); - dialog_ex_set_header( - dialog_ex, - auth_method == MfUltralightAuthMethodAuto ? "Password captured!" : "Risky function!", - 64, - 0, - AlignCenter, - AlignTop); + const char* message = (type == MfUltralightAuthTypeReader) ? "Password captured!" : + "Risky function!"; + dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 12, AlignCenter, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Continue"); - if(auth_method == MfUltralightAuthMethodAuto) + if(type == MfUltralightAuthTypeReader) { notification_message(nfc->notifications, &sequence_set_green_255); + } } else { dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); dialog_ex_set_text( @@ -52,19 +48,20 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { } bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; - MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; - if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { + 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) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); dolphin_deed(DolphinDeedNfcRead); consumed = true; } else if(event.event == DialogExResultLeft) { - if(auth_method == MfUltralightAuthMethodAuto) { + if(type == MfUltralightAuthTypeReader) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); } else { @@ -78,7 +75,9 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve } else { if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultCenter) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); + 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; } @@ -89,7 +88,7 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve } void nfc_scene_mf_ultralight_unlock_warn_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; dialog_ex_reset(nfc->dialog_ex); nfc_text_store_clear(nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c deleted file mode 100644 index 04515f24ff..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mfkey_complete_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mfkey_complete_on_enter(void* context) { - Nfc* nfc = context; - - widget_add_string_element(nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Complete!"); - widget_add_string_multiline_element( - nfc->widget, - 64, - 32, - AlignCenter, - AlignCenter, - FontSecondary, - "Now use Mfkey32\nto extract keys"); - widget_add_button_element( - nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_complete_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mfkey_complete_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeCenter) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); - } - } else if(event.event == SceneManagerEventTypeBack) { - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); - } - - return consumed; -} - -void nfc_scene_mfkey_complete_on_exit(void* context) { - Nfc* nfc = context; - - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c b/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c deleted file mode 100644 index 6d9852f3e6..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mfkey_nonces_info_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mfkey_nonces_info_on_enter(void* context) { - Nfc* nfc = context; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - uint16_t nonces_saved = mfkey32_get_auth_sectors(temp_str); - widget_add_text_scroll_element(nfc->widget, 0, 22, 128, 42, furi_string_get_cstr(temp_str)); - furi_string_printf(temp_str, "Nonce pairs saved: %d", nonces_saved); - widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(temp_str)); - widget_add_string_element( - nfc->widget, 0, 12, AlignLeft, AlignTop, FontSecondary, "Authenticated sectors:"); - - widget_add_button_element( - nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_nonces_info_callback, nfc); - - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mfkey_nonces_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeCenter) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyComplete); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); - } - - return consumed; -} - -void nfc_scene_mfkey_nonces_info_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_more_info.c b/applications/main/nfc/scenes/nfc_scene_more_info.c new file mode 100644 index 0000000000..b74e30295d --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_more_info.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_more_info_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneMoreInfo, context); +} + +bool nfc_scene_more_info_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneMoreInfo, context, event); +} + +void nfc_scene_more_info_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneMoreInfo, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c deleted file mode 100644 index 66a9174df4..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ /dev/null @@ -1,309 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_nfc_data_info_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_slix_build_string( - FuriString* temp_str, - NfcVData* nfcv_data, - SlixTypeFeatures features, - const char* type) { - furi_string_cat_printf(temp_str, "Type: %s\n", type); - furi_string_cat_printf(temp_str, "Keys:\n"); - if(features & SlixFeatureRead) { - furi_string_cat_printf( - temp_str, - " Read %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyRead) ? "" : " (unset)"); - } - if(features & SlixFeatureWrite) { - furi_string_cat_printf( - temp_str, - " Write %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyWrite) ? "" : " (unset)"); - } - if(features & SlixFeaturePrivacy) { - furi_string_cat_printf( - temp_str, - " Privacy %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyPrivacy) ? "" : " (unset)"); - furi_string_cat_printf( - temp_str, - " Privacy mode %s\n", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ENABLED" : "DISABLED"); - } - if(features & SlixFeatureDestroy) { - furi_string_cat_printf( - temp_str, - " Destroy %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyDestroy) ? "" : " (unset)"); - } - if(features & SlixFeatureEas) { - furi_string_cat_printf( - temp_str, - " EAS %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyEas) ? "" : " (unset)"); - } - if(features & SlixFeatureSignature) { - furi_string_cat_printf( - temp_str, - "Signature %08llX...\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.signature, 4)); - } - furi_string_cat_printf( - temp_str, - "DSFID: %02X %s\n", - nfcv_data->dsfid, - (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : ""); - furi_string_cat_printf( - temp_str, - "AFI: %02X %s\n", - nfcv_data->afi, - (nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : ""); - furi_string_cat_printf( - temp_str, - "EAS: %s\n", - (nfcv_data->security_status[0] & NfcVLockBitEas) ? "locked" : "not locked"); - - if(features & SlixFeatureProtection) { - furi_string_cat_printf( - temp_str, - "PPL: %s\n", - (nfcv_data->security_status[0] & NfcVLockBitPpl) ? "locked" : "not locked"); - furi_string_cat_printf(temp_str, "Prot.ptr %02X\n", nfcv_data->sub_data.slix.pp_pointer); - furi_string_cat_printf(temp_str, "Prot.con %02X\n", nfcv_data->sub_data.slix.pp_condition); - } -} - -void nfc_scene_nfc_data_info_on_enter(void* context) { - Nfc* nfc = context; - Widget* widget = nfc->widget; - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - NfcProtocol protocol = dev_data->protocol; - uint8_t text_scroll_height = 0; - if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) || - (protocol == NfcDeviceProtocolMifareClassic)) { - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc); - text_scroll_height = 52; - } else { - text_scroll_height = 64; - } - - FuriString* temp_str; - temp_str = furi_string_alloc(); - // Set name if present - if(nfc->dev->dev_name[0] != '\0') { - furi_string_printf(temp_str, "\ec%s\n", nfc->dev->dev_name); - } - - // Set tag type - if(protocol == NfcDeviceProtocolEMV) { - furi_string_cat_printf(temp_str, "\e#EMV Bank Card\n"); - } else if(protocol == NfcDeviceProtocolMifareUl) { - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_mf_ul_type(dev_data->mf_ul_data.type, true)); - } else if(protocol == NfcDeviceProtocolMifareClassic) { - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); - } else if(protocol == NfcDeviceProtocolMifareDesfire) { - furi_string_cat_printf(temp_str, "\e#MIFARE DESFire\n"); - } else if(protocol == NfcDeviceProtocolNfcV) { - switch(dev_data->nfcv_data.sub_type) { - case NfcVTypePlain: - furi_string_cat_printf(temp_str, "\e#ISO15693\n"); - break; - case NfcVTypeSlix: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); - break; - case NfcVTypeSlixS: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); - break; - case NfcVTypeSlixL: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); - break; - case NfcVTypeSlix2: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); - break; - default: - furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); - break; - } - } else { - furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); - } - - // Set tag iso data - if(protocol == NfcDeviceProtocolNfcV) { - NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; - - furi_string_cat_printf(temp_str, "UID:\n"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - furi_string_cat_printf(temp_str, "\n"); - - furi_string_cat_printf(temp_str, "IC Ref: %d\n", nfcv_data->ic_ref); - furi_string_cat_printf(temp_str, "Blocks: %d\n", nfcv_data->block_num); - furi_string_cat_printf(temp_str, "Blocksize: %d\n", nfcv_data->block_size); - - switch(dev_data->nfcv_data.sub_type) { - case NfcVTypePlain: - furi_string_cat_printf(temp_str, "Type: Plain\n"); - break; - case NfcVTypeSlix: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix, "SLIX"); - break; - case NfcVTypeSlixS: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixS, "SLIX-S"); - break; - case NfcVTypeSlixL: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixL, "SLIX-L"); - break; - case NfcVTypeSlix2: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix2, "SLIX2"); - break; - default: - furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); - break; - } - - furi_string_cat_printf( - temp_str, "Data (%d byte)\n", nfcv_data->block_num * nfcv_data->block_size); - - int maxBlocks = nfcv_data->block_num; - if(maxBlocks > 32) { - maxBlocks = 32; - furi_string_cat_printf(temp_str, "(truncated to %d blocks)\n", maxBlocks); - } - - for(int block = 0; block < maxBlocks; block++) { - const char* status = (nfcv_data->security_status[block] & 0x01) ? "(lck)" : ""; - for(int pos = 0; pos < nfcv_data->block_size; pos++) { - furi_string_cat_printf( - temp_str, " %02X", nfcv_data->data[block * nfcv_data->block_size + pos]); - } - furi_string_cat_printf(temp_str, " %s\n", status); - } - - } else { - char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; - furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - furi_string_cat_printf( - temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); - furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); - } - - // Set application specific data - if(protocol == NfcDeviceProtocolMifareDesfire) { - MifareDesfireData* data = &dev_data->mf_df_data; - uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); - uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%lu", bytes_total); - if(data->version.sw_storage & 1) { - furi_string_push_back(temp_str, '+'); - } - furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); - - uint16_t n_apps = 0; - uint16_t n_files = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - n_apps++; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - n_files++; - } - } - furi_string_cat_printf(temp_str, "%d Application", n_apps); - if(n_apps != 1) { - furi_string_push_back(temp_str, 's'); - } - furi_string_cat_printf(temp_str, ", %d file", n_files); - if(n_files != 1) { - furi_string_push_back(temp_str, 's'); - } - } else if(protocol == NfcDeviceProtocolMifareUl) { - MfUltralightData* data = &dev_data->mf_ul_data; - furi_string_cat_printf( - temp_str, "\nPages Read %d/%d", data->data_read / 4, data->data_size / 4); - if(data->data_size > data->data_read) { - furi_string_cat_printf(temp_str, "\nPassword-protected"); - } else if(data->auth_success) { - MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); - if(config_pages) { - furi_string_cat_printf( - temp_str, - "\nPassword: %02X %02X %02X %02X", - config_pages->auth_data.pwd.raw[0], - config_pages->auth_data.pwd.raw[1], - config_pages->auth_data.pwd.raw[2], - config_pages->auth_data.pwd.raw[3]); - furi_string_cat_printf( - temp_str, - "\nPACK: %02X %02X", - config_pages->auth_data.pack.raw[0], - config_pages->auth_data.pack.raw[1]); - } - } - } else if(protocol == NfcDeviceProtocolMifareClassic) { - MfClassicData* data = &dev_data->mf_classic_data; - uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); - uint8_t keys_total = sectors_total * 2; - uint8_t keys_found = 0; - uint8_t sectors_read = 0; - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - furi_string_cat_printf(temp_str, "\nKeys Found %d/%d", keys_found, keys_total); - furi_string_cat_printf(temp_str, "\nSectors Read %d/%d", sectors_read, sectors_total); - } - - // Add text scroll widget - widget_add_text_scroll_element( - widget, 0, 0, 128, text_scroll_height, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcProtocol protocol = nfc->dev->dev_data.protocol; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeRight) { - if(protocol == NfcDeviceProtocolMifareDesfire) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireData); - consumed = true; - } else if(protocol == NfcDeviceProtocolMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); - consumed = true; - } else if(protocol == NfcDeviceProtocolMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData); - } else if(protocol == NfcDeviceProtocolNfcV) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); - consumed = true; - } - } - } - - return consumed; -} - -void nfc_scene_nfc_data_info_on_exit(void* context) { - Nfc* nfc = context; - - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c b/applications/main/nfc/scenes/nfc_scene_nfca_menu.c deleted file mode 100644 index 9779470a38..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSaveUid, - SubmenuIndexEmulateUid, - SubmenuIndexInfo, -}; - -void nfc_scene_nfca_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_nfca_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Save UID", SubmenuIndexSaveUid, nfc_scene_nfca_menu_submenu_callback, nfc); - submenu_add_item( - submenu, "Emulate UID", SubmenuIndexEmulateUid, nfc_scene_nfca_menu_submenu_callback, nfc); - submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_nfca_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcaMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_nfca_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSaveUid) { - nfc->dev->format = NfcDeviceSaveFormatUid; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEmulateUid) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcaMenu, event.event); - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_nfca_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c deleted file mode 100644 index a38f31a981..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_nfca_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfca_read_success_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - - FuriString* temp_str; - temp_str = furi_string_alloc_set("\e#Unknown ISO tag\n"); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - char iso_type = FURI_BIT(data->sak, 5) ? '4' : '3'; - furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", data->uid[i]); - } - furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); - furi_string_cat_printf(temp_str, " SAK: %02X", data->sak); - - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_nfca_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_nfca_read_success_widget_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_nfca_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - return consumed; -} - -void nfc_scene_nfca_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c deleted file mode 100644 index d812988bdf..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (200) - -enum { - NfcSceneNfcVEmulateStateWidget, - NfcSceneNfcVEmulateStateTextBox, -}; - -bool nfc_scene_nfcv_emulate_worker_callback(NfcWorkerEvent event, void* context) { - furi_assert(context); - Nfc* nfc = context; - - switch(event) { - case NfcWorkerEventNfcVCommandExecuted: - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); - } - break; - case NfcWorkerEventNfcVContentChanged: - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); - break; - default: - break; - } - return true; -} - -void nfc_scene_nfcv_emulate_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfcv_emulate_textbox_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -static void nfc_scene_nfcv_emulate_widget_config(Nfc* nfc, bool data_received) { - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - widget_reset(widget); - FuriString* info_str; - info_str = furi_string_alloc(); - - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); - widget_add_string_multiline_element( - widget, 87, 13, AlignCenter, AlignTop, FontPrimary, "Emulating\nNFC V"); - if(strcmp(nfc->dev->dev_name, "") != 0) { - furi_string_printf(info_str, "%s", nfc->dev->dev_name); - } else { - for(uint8_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(info_str, "%02X ", data->uid[i]); - } - } - furi_string_trim(info_str); - widget_add_text_box_element( - widget, 52, 40, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); - furi_string_free(info_str); - if(data_received) { - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - widget_add_button_element( - widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_emulate_widget_callback, nfc); - } - } -} - -void nfc_scene_nfcv_emulate_on_enter(void* context) { - Nfc* nfc = context; - - // Setup Widget - nfc_scene_nfcv_emulate_widget_config(nfc, false); - // Setup TextBox - TextBox* text_box = nfc->text_box; - text_box_set_font(text_box, TextBoxFontHex); - text_box_set_focus(text_box, TextBoxFocusEnd); - text_box_set_text(text_box, ""); - furi_string_reset(nfc->text_box_store); - - // Set Widget state and view - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - // Start worker - memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVEmulate, - &nfc->dev->dev_data, - nfc_scene_nfcv_emulate_worker_callback, - nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_nfcv_emulate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVEmulate); - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventUpdateLog) { - // Add data button to widget if data is received for the first time - if(strlen(nfcv_data->last_command) > 0) { - if(!furi_string_size(nfc->text_box_store)) { - nfc_scene_nfcv_emulate_widget_config(nfc, true); - } - /* use the last n bytes from the log so there's enough space for the new log entry */ - size_t maxSize = - NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); - if(furi_string_size(nfc->text_box_store) >= maxSize) { - furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); - } - furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); - furi_string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - - /* clear previously logged command */ - strcpy(nfcv_data->last_command, ""); - } - consumed = true; - } else if(event.event == NfcCustomEventSaveShadow) { - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - consumed = true; - } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVEmulateStateWidget) { - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateTextBox); - } - consumed = true; - } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVEmulateStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state == NfcSceneNfcVEmulateStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_nfcv_emulate_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - widget_reset(nfc->widget); - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c deleted file mode 100644 index 13d903c4b7..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_nfcv_key_input_byte_input_callback(void* context) { - Nfc* nfc = context; - NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; - - memcpy(data->key_privacy, nfc->byte_input_store, 4); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); -} - -void nfc_scene_nfcv_key_input_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - ByteInput* byte_input = nfc->byte_input; - byte_input_set_header_text(byte_input, "Enter The Password In Hex"); - byte_input_set_result_callback( - byte_input, - nfc_scene_nfcv_key_input_byte_input_callback, - NULL, - nfc, - nfc->byte_input_store, - 4); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); -} - -bool nfc_scene_nfcv_key_input_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventByteInputDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); - dolphin_deed(DolphinDeedNfcRead); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_nfcv_key_input_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c deleted file mode 100644 index 60eb354e85..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSave, - SubmenuIndexEmulate, - SubmenuIndexInfo, -}; - -void nfc_scene_nfcv_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_nfcv_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_nfcv_menu_submenu_callback, nfc); - submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_nfcv_menu_submenu_callback, nfc); - submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_nfcv_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_nfcv_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSave) { - nfc->dev->format = NfcDeviceSaveFormatNfcV; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEmulate) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVMenu, event.event); - - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_nfcv_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c deleted file mode 100644 index 04e60611d0..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_nfcv_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfcv_read_success_on_enter(void* context) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - // Setup view - Widget* widget = nfc->widget; - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_nfcv_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_nfcv_read_success_widget_callback, nfc); - - FuriString* temp_str = furi_string_alloc(); - - switch(dev_data->nfcv_data.sub_type) { - case NfcVTypePlain: - furi_string_cat_printf(temp_str, "\e#ISO15693\n"); - break; - case NfcVTypeSlix: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); - break; - case NfcVTypeSlixS: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); - break; - case NfcVTypeSlixL: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); - break; - case NfcVTypeSlix2: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); - break; - default: - furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); - break; - } - furi_string_cat_printf(temp_str, "UID:\n"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - furi_string_cat_printf(temp_str, "\n"); - furi_string_cat_printf(temp_str, "(see More->Info for details)\n"); - - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_nfcv_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_nfcv_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c deleted file mode 100644 index 2c0f17981b..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c +++ /dev/null @@ -1,155 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (800) - -enum { - NfcSceneNfcVSniffStateWidget, - NfcSceneNfcVSniffStateTextBox, -}; - -bool nfc_scene_nfcv_sniff_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - furi_assert(context); - Nfc* nfc = context; - - switch(event) { - case NfcWorkerEventNfcVCommandExecuted: - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); - break; - case NfcWorkerEventNfcVContentChanged: - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); - break; - default: - break; - } - return true; -} - -void nfc_scene_nfcv_sniff_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfcv_sniff_textbox_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -static void nfc_scene_nfcv_sniff_widget_config(Nfc* nfc, bool data_received) { - Widget* widget = nfc->widget; - widget_reset(widget); - FuriString* info_str; - info_str = furi_string_alloc(); - - widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); - widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Listen NfcV"); - furi_string_trim(info_str); - widget_add_text_box_element( - widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); - furi_string_free(info_str); - if(data_received) { - widget_add_button_element( - widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_sniff_widget_callback, nfc); - } -} - -void nfc_scene_nfcv_sniff_on_enter(void* context) { - Nfc* nfc = context; - - // Setup Widget - nfc_scene_nfcv_sniff_widget_config(nfc, false); - // Setup TextBox - TextBox* text_box = nfc->text_box; - text_box_set_font(text_box, TextBoxFontHex); - text_box_set_focus(text_box, TextBoxFocusEnd); - text_box_set_text(text_box, ""); - furi_string_reset(nfc->text_box_store); - - // Set Widget state and view - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - // Start worker - memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVSniff, - &nfc->dev->dev_data, - nfc_scene_nfcv_sniff_worker_callback, - nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_nfcv_sniff_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVSniff); - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventUpdateLog) { - // Add data button to widget if data is received for the first time - if(strlen(nfcv_data->last_command) > 0) { - if(!furi_string_size(nfc->text_box_store)) { - nfc_scene_nfcv_sniff_widget_config(nfc, true); - } - /* use the last n bytes from the log so there's enough space for the new log entry */ - size_t maxSize = - NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); - if(furi_string_size(nfc->text_box_store) >= maxSize) { - furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); - } - furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); - furi_string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - - /* clear previously logged command */ - strcpy(nfcv_data->last_command, ""); - } - consumed = true; - } else if(event.event == NfcCustomEventSaveShadow) { - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - consumed = true; - } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVSniffStateWidget) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateTextBox); - consumed = true; - } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVSniffStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state == NfcSceneNfcVSniffStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_nfcv_sniff_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - widget_reset(nfc->widget); - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c deleted file mode 100644 index 38d7ad563d..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c +++ /dev/null @@ -1,154 +0,0 @@ -#include "../nfc_i.h" -#include - -typedef enum { - NfcSceneNfcVUnlockStateIdle, - NfcSceneNfcVUnlockStateDetecting, - NfcSceneNfcVUnlockStateUnlocked, - NfcSceneNfcVUnlockStateAlreadyUnlocked, - NfcSceneNfcVUnlockStateNotSupportedCard, -} NfcSceneNfcVUnlockState; - -static bool nfc_scene_nfcv_unlock_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; - - if(event == NfcWorkerEventNfcVPassKey) { - memcpy(data->key_privacy, nfc->byte_input_store, 4); - } else { - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - } - return true; -} - -void nfc_scene_nfcv_unlock_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_nfcv_unlock_set_state(Nfc* nfc, NfcSceneNfcVUnlockState state) { - FuriHalNfcDevData* nfc_data = &(nfc->dev->dev_data.nfc_data); - NfcVData* nfcv_data = &(nfc->dev->dev_data.nfcv_data); - - uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock); - if(curr_state != state) { - Popup* popup = nfc->popup; - if(state == NfcSceneNfcVUnlockStateDetecting) { - popup_reset(popup); - popup_set_text( - popup, "Put figurine on\nFlipper's back", 97, 24, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50); - } else if(state == NfcSceneNfcVUnlockStateUnlocked) { - popup_reset(popup); - - if(nfc_worker_get_state(nfc->worker) == NfcWorkerStateNfcVUnlockAndSave) { - snprintf( - nfc->dev->dev_name, - sizeof(nfc->dev->dev_name), - "SLIX_%02X%02X%02X%02X%02X%02X%02X%02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); - - nfc->dev->format = NfcDeviceSaveFormatNfcV; - - if(nfc_save_file(nfc)) { - popup_set_header(popup, "Successfully\nsaved", 94, 3, AlignCenter, AlignTop); - } else { - popup_set_header( - popup, "Unlocked but\nsave failed!", 94, 3, AlignCenter, AlignTop); - } - } else { - popup_set_header(popup, "Successfully\nunlocked", 94, 3, AlignCenter, AlignTop); - } - - notification_message(nfc->notifications, &sequence_single_vibro); - //notification_message(nfc->notifications, &sequence_success); - - popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); - popup_set_timeout(popup, 1500); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - dolphin_deed(DolphinDeedNfcReadSuccess); - - } else if(state == NfcSceneNfcVUnlockStateAlreadyUnlocked) { - popup_reset(popup); - - popup_set_header(popup, "Already\nUnlocked!", 94, 3, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); - popup_set_timeout(popup, 1500); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } else if(state == NfcSceneNfcVUnlockStateNotSupportedCard) { - popup_reset(popup); - popup_set_header(popup, "Wrong Type Of Card!", 64, 3, AlignCenter, AlignTop); - popup_set_text(popup, nfcv_data->error, 4, 22, AlignLeft, AlignTop); - popup_set_icon(popup, 73, 20, &I_DolphinCommon_56x48); - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock, state); - } -} - -void nfc_scene_nfcv_unlock_on_enter(void* context) { - Nfc* nfc = context; - - nfc_device_clear(nfc->dev); - // Setup view - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - - // Start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVUnlockAndSave, - &nfc->dev->dev_data, - nfc_scene_nfcv_unlock_worker_callback, - nfc); - - nfc_blink_read_start(nfc); -} - -bool nfc_scene_nfcv_unlock_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventCardDetected) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateUnlocked); - consumed = true; - } else if(event.event == NfcWorkerEventAborted) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateAlreadyUnlocked); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); - consumed = true; - } else if(event.event == NfcWorkerEventWrongCardDetected) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateNotSupportedCard); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneNfcVUnlockMenu); - } - return consumed; -} - -void nfc_scene_nfcv_unlock_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - nfc_blink_stop(nfc); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVUnlock, NfcSceneNfcVUnlockStateIdle); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c deleted file mode 100644 index 2f73672567..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexNfcVUnlockMenuManual, - SubmenuIndexNfcVUnlockMenuTonieBox, -}; - -void nfc_scene_nfcv_unlock_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_nfcv_unlock_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu); - submenu_add_item( - submenu, - "Enter PWD Manually", - SubmenuIndexNfcVUnlockMenuManual, - nfc_scene_nfcv_unlock_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Auth As TonieBox", - SubmenuIndexNfcVUnlockMenuTonieBox, - nfc_scene_nfcv_unlock_menu_submenu_callback, - nfc); - submenu_set_selected_item(submenu, state); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_nfcv_unlock_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexNfcVUnlockMenuManual) { - nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodManual; - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVKeyInput); - consumed = true; - } else if(event.event == SubmenuIndexNfcVUnlockMenuTonieBox) { - nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodTonieBox; - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); - dolphin_deed(DolphinDeedNfcRead); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu, event.event); - } - return consumed; -} - -void nfc_scene_nfcv_unlock_menu_on_exit(void* context) { - Nfc* nfc = context; - - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 1690a95575..e9603afd1a 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -1,123 +1,13 @@ -#include "../nfc_i.h" -#include - -typedef enum { - NfcSceneReadStateIdle, - NfcSceneReadStateDetecting, - NfcSceneReadStateReading, -} NfcSceneReadState; - -bool nfc_scene_read_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - bool consumed = false; - if(event == NfcWorkerEventReadMfClassicLoadKeyCache) { - consumed = nfc_device_load_key_cache(nfc->dev); - } else { - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - consumed = true; - } - return consumed; -} - -void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) { - uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneRead); - if(curr_state != state) { - if(state == NfcSceneReadStateDetecting) { - popup_reset(nfc->popup); - popup_set_text( - nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); - } else if(state == NfcSceneReadStateReading) { - popup_reset(nfc->popup); - popup_set_header( - nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneRead, state); - } -} +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_read_on_enter(void* context) { - Nfc* nfc = context; - - nfc_device_clear(nfc->dev); - // Setup view - nfc_scene_read_set_state(nfc, NfcSceneReadStateDetecting); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - // Start worker - nfc_worker_start( - nfc->worker, NfcWorkerStateRead, &nfc->dev->dev_data, nfc_scene_read_worker_callback, nfc); - - nfc_blink_read_start(nfc); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneRead, context); } bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if((event.event == NfcWorkerEventReadUidNfcB) || - (event.event == NfcWorkerEventReadUidNfcF) || - (event.event == NfcWorkerEventReadUidNfcV)) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadUidNfcA) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadNfcV) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfUltralight) { - notification_message(nfc->notifications, &sequence_success); - // Set unlock password input to 0xFFFFFFFF only on fresh read - memset(nfc->byte_input_store, 0xFF, sizeof(nfc->byte_input_store)); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfClassicDone) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfDesfire) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { - if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); - } - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - nfc_scene_read_set_state(nfc, NfcSceneReadStateReading); - nfc_blink_detect_start(nfc); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - nfc_scene_read_set_state(nfc, NfcSceneReadStateDetecting); - nfc_blink_read_start(nfc); - consumed = true; - } - } - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneRead, context, event); } void nfc_scene_read_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneRead, NfcSceneReadStateIdle); - - nfc_blink_stop(nfc); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneRead, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_success.c b/applications/main/nfc/scenes/nfc_scene_read_card_success.c deleted file mode 100644 index ee80ee7688..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_read_card_success.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_read_card_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_read_card_success_on_enter(void* context) { - Nfc* nfc = context; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - // Setup view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - furi_string_set(temp_str, nfc_get_dev_type(data->type)); - widget_add_string_element( - widget, 64, 12, AlignCenter, AlignBottom, FontPrimary, furi_string_get_cstr(temp_str)); - furi_string_set(temp_str, "UID:"); - for(uint8_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", data->uid[i]); - } - widget_add_string_element( - widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_read_card_success_widget_callback, nfc); - - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_read_card_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); - } - return consumed; -} - -void nfc_scene_read_card_success_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_type.c b/applications/main/nfc/scenes/nfc_scene_read_card_type.c deleted file mode 100644 index 8023026c3d..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_read_card_type.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "../nfc_i.h" -#include "nfc_worker_i.h" - -enum SubmenuIndex { - SubmenuIndexReadMifareClassic, - SubmenuIndexReadMifareDesfire, - SubmenuIndexReadMfUltralight, - SubmenuIndexReadNFCA, -}; - -void nfc_scene_read_card_type_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_read_card_type_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, - "Read Mifare Classic", - SubmenuIndexReadMifareClassic, - nfc_scene_read_card_type_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Read Mifare DESFire", - SubmenuIndexReadMifareDesfire, - nfc_scene_read_card_type_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Read NTAG/Ultralight", - SubmenuIndexReadMfUltralight, - nfc_scene_read_card_type_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Read NFC-A data", - SubmenuIndexReadNFCA, - nfc_scene_read_card_type_submenu_callback, - nfc); - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadCardType); - submenu_set_selected_item(submenu, state); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_read_card_type_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexReadMifareClassic) { - nfc->dev->dev_data.read_mode = NfcReadModeMfClassic; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - if(event.event == SubmenuIndexReadMifareDesfire) { - nfc->dev->dev_data.read_mode = NfcReadModeMfDesfire; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - if(event.event == SubmenuIndexReadMfUltralight) { - nfc->dev->dev_data.read_mode = NfcReadModeMfUltralight; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - if(event.event == SubmenuIndexReadNFCA) { - nfc->dev->dev_data.read_mode = NfcReadModeNFCA; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, event.event); - } - return consumed; -} - -void nfc_scene_read_card_type_on_exit(void* context) { - Nfc* nfc = context; - - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read_menu.c b/applications/main/nfc/scenes/nfc_scene_read_menu.c new file mode 100644 index 0000000000..cba07a485a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_read_menu.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_read_menu_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneReadMenu, context); +} + +bool nfc_scene_read_menu_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneReadMenu, context, event); +} + +void nfc_scene_read_menu_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneReadMenu, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_read_success.c b/applications/main/nfc/scenes/nfc_scene_read_success.c new file mode 100644 index 0000000000..5ceada48b5 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_read_success.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_read_success_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneReadSuccess, context); +} + +bool nfc_scene_read_success_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneReadSuccess, context, event); +} + +void nfc_scene_read_success_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneReadSuccess, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original.c b/applications/main/nfc/scenes/nfc_scene_restore_original.c index 3ecf5c048e..612e6041e6 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original.c @@ -1,12 +1,12 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_restore_original_popup_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_restore_original_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view Popup* popup = nfc->popup; @@ -20,17 +20,17 @@ void nfc_scene_restore_original_on_enter(void* context) { } bool nfc_scene_restore_original_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { + if(nfc_load_file(nfc, nfc->file_path, false)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSavedMenu); } else { consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); + nfc->scene_manager, NfcSceneFileSelect); } } } @@ -38,7 +38,7 @@ bool nfc_scene_restore_original_on_event(void* context, SceneManagerEvent event) } void nfc_scene_restore_original_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view popup_reset(nfc->popup); diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c index 16b0953f80..6e260da2a2 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_restore_original_confirm_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_restore_original_confirm_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_header(dialog_ex, "Restore Card Data?", 64, 0, AlignCenter, AlignTop); @@ -23,16 +23,16 @@ void nfc_scene_restore_original_confirm_on_enter(void* context) { } bool nfc_scene_restore_original_confirm_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { - if(!nfc_device_restore(nfc->dev, true)) { + if(nfc_delete_shadow_file(nfc)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginal); + } else { scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginal); } consumed = true; } else if(event.event == DialogExResultLeft) { @@ -46,7 +46,7 @@ bool nfc_scene_restore_original_confirm_on_event(void* context, SceneManagerEven } void nfc_scene_restore_original_confirm_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clean view dialog_ex_reset(nfc->dialog_ex); diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index 5f4f7985e7..b80f1bdcc1 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_retry_confirm_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_retry_confirm_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Retry"); @@ -22,15 +22,20 @@ void nfc_scene_retry_confirm_on_enter(void* context) { } bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneRead); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneDetect); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneRead)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneRead); + } } } else if(event.type == SceneManagerEventTypeBack) { consumed = true; @@ -40,7 +45,7 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_retry_confirm_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clean view dialog_ex_reset(nfc->dialog_ex); diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index d06ee75646..e12e84605b 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -1,93 +1,13 @@ -#include "../nfc_i.h" +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_rpc_on_enter(void* context) { - Nfc* nfc = context; - Popup* popup = nfc->popup; - - popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); - - popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - - notification_message(nfc->notifications, &sequence_display_backlight_on); -} - -static bool nfc_scene_rpc_emulate_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - Nfc* nfc = context; - - nfc->rpc_state = NfcRpcStateEmulated; - return true; + nfc_protocol_support_on_enter(NfcProtocolSupportSceneRpc, context); } bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - Popup* popup = nfc->popup; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == NfcCustomEventViewExit) { - rpc_system_app_confirm(nfc->rpc_ctx, RpcAppEventAppExit, true); - scene_manager_stop(nfc->scene_manager); - view_dispatcher_stop(nfc->view_dispatcher); - } else if(event.event == NfcCustomEventRpcSessionClose) { - scene_manager_stop(nfc->scene_manager); - view_dispatcher_stop(nfc->view_dispatcher); - } else if(event.event == NfcCustomEventRpcLoad) { - bool result = false; - const char* arg = rpc_system_app_get_data(nfc->rpc_ctx); - if((arg) && (nfc->rpc_state == NfcRpcStateIdle)) { - if(nfc_device_load(nfc->dev, arg, false)) { - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfUltralightEmulate, - &nfc->dev->dev_data, - nfc_scene_rpc_emulate_callback, - nfc); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicEmulate, - &nfc->dev->dev_data, - nfc_scene_rpc_emulate_callback, - nfc); - } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVEmulate, - &nfc->dev->dev_data, - nfc_scene_rpc_emulate_callback, - nfc); - } else { - nfc_worker_start( - nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc); - } - nfc->rpc_state = NfcRpcStateEmulating; - result = true; - - nfc_blink_emulate_start(nfc); - nfc_text_store_set(nfc, "emulating\n%s", nfc->dev->dev_name); - popup_set_text(popup, nfc->text_store, 89, 44, AlignCenter, AlignTop); - } - } - - rpc_system_app_confirm(nfc->rpc_ctx, RpcAppEventLoadFile, result); - } - } - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneRpc, context, event); } void nfc_scene_rpc_on_exit(void* context) { - Nfc* nfc = context; - Popup* popup = nfc->popup; - - nfc_blink_stop(nfc); - - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneRpc, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index b18e176333..c23f097e14 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -1,93 +1,13 @@ -#include "../nfc_i.h" -#include -#include -#include -#include - -void nfc_scene_save_name_text_input_callback(void* context) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone); -} +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_save_name_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - TextInput* text_input = nfc->text_input; - bool dev_name_empty = false; - if(!strcmp(nfc->dev->dev_name, "")) { - name_generator_make_auto(nfc->text_store, NFC_DEV_NAME_MAX_LEN, NFC_APP_FILENAME_PREFIX); - dev_name_empty = true; - } else { - nfc_text_store_set(nfc, nfc->dev->dev_name); - } - text_input_set_header_text(text_input, "Name the card"); - text_input_set_result_callback( - text_input, - nfc_scene_save_name_text_input_callback, - nfc, - nfc->text_store, - NFC_DEV_NAME_MAX_LEN, - dev_name_empty); - - FuriString* folder_path; - folder_path = furi_string_alloc(); - - if(furi_string_end_with(nfc->dev->load_path, NFC_APP_FILENAME_EXTENSION)) { - path_extract_dirname(furi_string_get_cstr(nfc->dev->load_path), folder_path); - } else { - furi_string_set(folder_path, NFC_APP_FOLDER); - } - - ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - furi_string_get_cstr(folder_path), NFC_APP_FILENAME_EXTENSION, nfc->dev->dev_name); - text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput); - - furi_string_free(folder_path); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneSaveName, context); } bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventTextInputDone) { - if(strcmp(nfc->dev->dev_name, "") != 0) { - nfc_device_delete(nfc->dev, true); - } - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { - nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; - } - strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1); - if(nfc_save_file(nfc)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); - if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { - // Nothing, do not count editing as saving - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddSave); - } else { - dolphin_deed(DolphinDeedNfcSave); - } - consumed = true; - } else { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); - } - } - } - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneSaveName, context, event); } void nfc_scene_save_name_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - void* validator_context = text_input_get_validator_callback_context(nfc->text_input); - text_input_set_validator(nfc->text_input, NULL, NULL); - validator_is_file_free(validator_context); - - text_input_reset(nfc->text_input); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneSaveName, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 34919cbd86..0cb26c0d45 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -1,12 +1,12 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_save_success_popup_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_save_success_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view Popup* popup = nfc->popup; @@ -20,7 +20,7 @@ void nfc_scene_save_success_on_enter(void* context) { } bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -28,9 +28,6 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneSavedMenu); } else { consumed = scene_manager_search_and_switch_to_another_scene( nfc->scene_manager, NfcSceneFileSelect); @@ -41,7 +38,7 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_save_success_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view popup_reset(nfc->popup); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index b3205554a4..d367e8ab86 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -1,186 +1,13 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexEmulate, - SubmenuIndexEditUid, - SubmenuIndexDetectReader, - SubmenuIndexWrite, - SubmenuIndexUpdate, - SubmenuIndexRename, - SubmenuIndexDelete, - SubmenuIndexInfo, - SubmenuIndexRestoreOriginal, - SubmenuIndexMfUlUnlockByReader, - SubmenuIndexMfUlUnlockByPassword, -}; - -void nfc_scene_saved_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_saved_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - if(nfc->dev->format == NfcDeviceSaveFormatUid || - nfc->dev->format == NfcDeviceSaveFormatMifareDesfire) { - submenu_add_item( - submenu, - "Emulate UID", - SubmenuIndexEmulate, - nfc_scene_saved_menu_submenu_callback, - nfc); - if(nfc->dev->dev_data.protocol == NfcDeviceProtocolUnknown) { - submenu_add_item( - submenu, - "Edit UID", - SubmenuIndexEditUid, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - } else if( - (nfc->dev->format == NfcDeviceSaveFormatMifareUl && - mf_ul_emulation_supported(&nfc->dev->dev_data.mf_ul_data)) || - nfc->dev->format == NfcDeviceSaveFormatNfcV || - nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - submenu_add_item( - submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); - } - if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { - submenu_add_item( - submenu, - "Detect Reader", - SubmenuIndexDetectReader, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, - "Write to Initial Card", - SubmenuIndexWrite, - nfc_scene_saved_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Update from Initial Card", - SubmenuIndexUpdate, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl && - nfc->dev->dev_data.mf_ul_data.type != MfUltralightTypeULC && - !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { - submenu_add_item( - submenu, - "Unlock with Reader", - SubmenuIndexMfUlUnlockByReader, - nfc_scene_saved_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Unlock with Password", - SubmenuIndexMfUlUnlockByPassword, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - if(nfc->dev->shadow_file_exist) { - submenu_add_item( - submenu, - "Restore to original", - SubmenuIndexRestoreOriginal, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Rename", SubmenuIndexRename, nfc_scene_saved_menu_submenu_callback, nfc); - submenu_add_item( - submenu, "Delete", SubmenuIndexDelete, nfc_scene_saved_menu_submenu_callback, nfc); - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSavedMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneSavedMenu, context); } bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, event.event); - if(event.event == SubmenuIndexEmulate) { - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - } - dolphin_deed(DolphinDeedNfcEmulate); - consumed = true; - } else if(event.event == SubmenuIndexDetectReader) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - dolphin_deed(DolphinDeedNfcDetectReader); - consumed = true; - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); - consumed = true; - } else if(event.event == SubmenuIndexUpdate) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdate); - consumed = true; - } else if(event.event == SubmenuIndexRename) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEditUid) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetUid); - consumed = true; - } else if(event.event == SubmenuIndexDelete) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDelete); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - bool application_info_present = false; - if(dev_data->protocol == NfcDeviceProtocolEMV) { - application_info_present = true; - } else if( - dev_data->protocol == NfcDeviceProtocolMifareClassic || - dev_data->protocol == NfcDeviceProtocolMifareDesfire || - dev_data->protocol == NfcDeviceProtocolMifareUl) { - application_info_present = nfc_supported_card_verify_and_parse(dev_data); - } - - FURI_LOG_I("nfc", "application_info_present: %d", application_info_present); - - if(application_info_present) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - } - consumed = true; - } else if(event.event == SubmenuIndexRestoreOriginal) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginalConfirm); - consumed = true; - } else if(event.event == SubmenuIndexMfUlUnlockByReader) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); - consumed = true; - } else if(event.event == SubmenuIndexMfUlUnlockByPassword) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); - consumed = true; - } - } - - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneSavedMenu, context, event); } void nfc_scene_saved_menu_on_exit(void* context) { - Nfc* nfc = context; - - submenu_reset(nfc->submenu); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneSavedMenu, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_select_protocol.c b/applications/main/nfc/scenes/nfc_scene_select_protocol.c new file mode 100644 index 0000000000..86b9982fc6 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_select_protocol.c @@ -0,0 +1,67 @@ +#include "../nfc_app_i.h" + +void nfc_scene_select_protocol_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_select_protocol_on_enter(void* context) { + NfcApp* instance = context; + Submenu* submenu = instance->submenu; + + FuriString* temp_str = furi_string_alloc(); + const char* prefix; + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneExtraActions)) { + prefix = "Read"; + instance->protocols_detected_num = NfcProtocolNum; + for(uint32_t i = 0; i < NfcProtocolNum; i++) { + instance->protocols_detected[i] = i; + } + } 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++) { + furi_string_printf( + temp_str, + "%s %s", + prefix, + nfc_device_get_protocol_name(instance->protocols_detected[i])); + submenu_add_item( + submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_select_protocol_submenu_callback, + instance); + } + furi_string_free(temp_str); + + const uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneSelectProtocol); + submenu_set_selected_item(submenu, state); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_select_protocol_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + instance->protocols_detected_selected_idx = event.event; + scene_manager_next_scene(instance->scene_manager, NfcSceneRead); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneSelectProtocol, event.event); + consumed = true; + } + return consumed; +} + +void nfc_scene_select_protocol_on_exit(void* context) { + NfcApp* nfc = context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_set_atqa.c b/applications/main/nfc/scenes/nfc_scene_set_atqa.c index d079b38047..17a07b8b53 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_atqa.c +++ b/applications/main/nfc/scenes/nfc_scene_set_atqa.c @@ -1,34 +1,39 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -void nfc_scene_set_atqa_byte_input_callback(void* context) { - Nfc* nfc = context; +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +static void nfc_scene_set_atqa_byte_input_changed_callback(void* context) { + NfcApp* instance = context; + iso14443_3a_set_atqa(instance->iso14443_3a_edit_data, instance->byte_input_store); } void nfc_scene_set_atqa_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + iso14443_3a_get_atqa(instance->iso14443_3a_edit_data, instance->byte_input_store); // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter ATQA in hex"); byte_input_set_result_callback( byte_input, - nfc_scene_set_atqa_byte_input_callback, - NULL, - nfc, - nfc->dev->dev_data.nfc_data.atqa, + nfc_protocol_support_common_byte_input_done_callback, + nfc_scene_set_atqa_byte_input_changed_callback, + instance, + instance->byte_input_store, 2); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_set_atqa_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetUid); + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3a, instance->iso14443_3a_edit_data); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; } } @@ -36,9 +41,9 @@ bool nfc_scene_set_atqa_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_set_atqa_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_set_sak.c b/applications/main/nfc/scenes/nfc_scene_set_sak.c index 60a1e1494b..c55cee1c21 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_sak.c +++ b/applications/main/nfc/scenes/nfc_scene_set_sak.c @@ -1,44 +1,48 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -void nfc_scene_set_sak_byte_input_callback(void* context) { - Nfc* nfc = context; +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +static void nfc_scene_set_sak_byte_input_changed_callback(void* context) { + NfcApp* instance = context; + iso14443_3a_set_sak(instance->iso14443_3a_edit_data, instance->byte_input_store[0]); } void nfc_scene_set_sak_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + instance->byte_input_store[0] = iso14443_3a_get_sak(instance->iso14443_3a_edit_data); // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter SAK in hex"); byte_input_set_result_callback( byte_input, - nfc_scene_set_sak_byte_input_callback, - NULL, - nfc, - &nfc->dev->dev_data.nfc_data.sak, + nfc_protocol_support_common_byte_input_done_callback, + nfc_scene_set_sak_byte_input_changed_callback, + instance, + instance->byte_input_store, 1); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_set_sak_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqa); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetAtqa); consumed = true; } } + return consumed; } void nfc_scene_set_sak_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index cadf2eb69e..e336600807 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -1,68 +1,72 @@ -#include "../nfc_i.h" -#include "lib/nfc/helpers/nfc_generators.h" +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" enum SubmenuIndex { - SubmenuIndexNFCA4, - SubmenuIndexNFCA7, SubmenuIndexGeneratorsStart, + SubmenuIndexNFCA4 = NfcDataGeneratorTypeNum, + SubmenuIndexNFCA7, }; -void nfc_scene_set_type_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +static void nfc_scene_set_type_init_edit_data(Iso14443_3aData* data, size_t uid_len) { + // Easiest way to create a zero'd buffer of given length + uint8_t* uid = malloc(uid_len); + iso14443_3a_set_uid(data, uid, uid_len); + free(uid); } void nfc_scene_set_type_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - furi_string_set(nfc->dev->load_path, ""); + NfcApp* instance = context; + + Submenu* submenu = instance->submenu; submenu_add_item( - submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); + submenu, + "NFC-A 7-bytes UID", + SubmenuIndexNFCA7, + nfc_protocol_support_common_submenu_callback, + instance); submenu_add_item( - submenu, "NFC-A 4-bytes UID", SubmenuIndexNFCA4, nfc_scene_set_type_submenu_callback, nfc); + submenu, + "NFC-A 4-bytes UID", + SubmenuIndexNFCA4, + nfc_protocol_support_common_submenu_callback, + instance); - // Generators - int i = SubmenuIndexGeneratorsStart; - for(const NfcGenerator* const* generator = nfc_generators; *generator != NULL; - ++generator, ++i) { - submenu_add_item(submenu, (*generator)->name, i, nfc_scene_set_type_submenu_callback, nfc); + for(size_t i = 0; i < NfcDataGeneratorTypeNum; i++) { + const char* name = nfc_data_generator_get_name(i); + submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance); } - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexNFCA7) { - nfc->dev->dev_data.nfc_data.uid_len = 7; - nfc->dev->format = NfcDeviceSaveFormatUid; - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak); + nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 7); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; } else if(event.event == SubmenuIndexNFCA4) { - nfc->dev->dev_data.nfc_data.uid_len = 4; - nfc->dev->format = NfcDeviceSaveFormatUid; - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak); + nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; } else { - nfc_device_clear(nfc->dev); - nfc->generator = nfc_generators[event.event - SubmenuIndexGeneratorsStart]; - nfc->generator->generator_func(&nfc->dev->dev_data); - - scene_manager_next_scene(nfc->scene_manager, NfcSceneGenerateInfo); + nfc_data_generator_fill_data(event.event, instance->nfc_device); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneGenerateInfo, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneGenerateInfo); consumed = true; } } + return consumed; } void nfc_scene_set_type_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - submenu_reset(nfc->submenu); + submenu_reset(instance->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 54606b68ee..df8a4dc72c 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -1,42 +1,51 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -void nfc_scene_set_uid_byte_input_callback(void* context) { - Nfc* nfc = context; +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +static void nfc_scene_set_uid_byte_input_changed_callback(void* context) { + NfcApp* instance = context; + // Retrieve previously saved UID length + const size_t uid_len = scene_manager_get_scene_state(instance->scene_manager, NfcSceneSetUid); + nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len); } void nfc_scene_set_uid_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + + memcpy(instance->byte_input_store, uid, uid_len); + // Save UID length for use in callback + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSetUid, uid_len); // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter UID in hex"); - nfc->dev_edit_data = nfc->dev->dev_data.nfc_data; byte_input_set_result_callback( byte_input, - nfc_scene_set_uid_byte_input_callback, - NULL, - nfc, - nfc->dev_edit_data.uid, - nfc->dev_edit_data.uid_len); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + nfc_protocol_support_common_byte_input_done_callback, + nfc_scene_set_uid_byte_input_changed_callback, + instance, + instance->byte_input_store, + uid_len); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = (Nfc*)context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { - nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; - if(nfc_save_file(nfc)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu)) { + if(nfc_save(instance)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); consumed = true; } } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; } } @@ -46,9 +55,9 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_set_uid_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index c9e8bf78cf..c923226fc7 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -1,5 +1,4 @@ -#include "../nfc_i.h" -#include "nfc_worker_i.h" +#include "../nfc_app_i.h" #include enum SubmenuIndex { @@ -12,15 +11,20 @@ enum SubmenuIndex { }; void nfc_scene_start_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } void nfc_scene_start_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; Submenu* submenu = nfc->submenu; + // Clear file name and device contents + furi_string_reset(nfc->file_name); + nfc_device_clear(nfc->nfc_device); + iso14443_3a_reset(nfc->iso14443_3a_edit_data); + submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc); submenu_add_item( submenu, "Detect Reader", SubmenuIndexDetectReader, nfc_scene_start_submenu_callback, nfc); @@ -38,32 +42,23 @@ void nfc_scene_start_on_enter(void* context) { submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneStart)); - nfc_device_clear(nfc->dev); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); - nfc->dev->dev_data.read_mode = NfcReadModeAuto; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + scene_manager_next_scene(nfc->scene_manager, NfcSceneDetect); dolphin_deed(DolphinDeedNfcRead); consumed = true; } else if(event.event == SubmenuIndexDetectReader) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneStart, SubmenuIndexDetectReader); - bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; - if(sd_exist) { - nfc_device_data_clear(&nfc->dev->dev_data); - scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - dolphin_deed(DolphinDeedNfcDetectReader); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); - } + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); consumed = true; } else if(event.event == SubmenuIndexSaved) { // Save the scene state explicitly in each branch, so that @@ -92,7 +87,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_start_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_supported_card.c b/applications/main/nfc/scenes/nfc_scene_supported_card.c new file mode 100644 index 0000000000..cea55b783b --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_supported_card.c @@ -0,0 +1,50 @@ +#include "nfc/nfc_app_i.h" + +#include "nfc/helpers/nfc_supported_cards.h" +#include "nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h" + +void nfc_scene_supported_card_on_enter(void* context) { + NfcApp* instance = context; + + FuriString* temp_str = furi_string_alloc(); + + if(nfc_supported_cards_parse(instance->nfc_device, temp_str)) { + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSupportedCard, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + + } else { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSupportedCard, false); + scene_manager_next_scene(instance->scene_manager, NfcSceneInfo); + } + + furi_string_free(temp_str); +} + +bool nfc_scene_supported_card_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneInfo); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_supported_card_on_exit(void* context) { + NfcApp* instance = context; + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index e5951beb26..ebcda7caf1 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -163,7 +163,10 @@ void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t no with_view_model( detect_reader->view, DetectReaderViewModel * model, - { model->nonces = nonces_collected; }, + { + model->nonces = nonces_collected; + model->state = DetectReaderStateReaderDetected; + }, false); } diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 8f4bd063e8..b656c2dc5b 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -2,42 +2,36 @@ #include -typedef enum { - DictAttackStateRead, - DictAttackStateCardRemoved, -} DictAttackState; +#define NFC_CLASSIC_KEYS_PER_SECTOR 2 struct DictAttack { View* view; DictAttackCallback callback; void* context; - bool card_present; }; typedef struct { - DictAttackState state; - MfClassicType type; FuriString* header; + bool card_detected; uint8_t sectors_total; uint8_t sectors_read; - uint8_t sector_current; - uint8_t keys_total; + uint8_t current_sector; uint8_t keys_found; - uint16_t dict_keys_total; - uint16_t dict_keys_current; + size_t dict_keys_total; + size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { DictAttackViewModel* m = model; - if(m->state == DictAttackStateCardRemoved) { + if(!m->card_detected) { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!"); canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned( canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); - } else if(m->state == DictAttackStateRead) { + } else { char draw_str[32] = {}; canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned( @@ -49,28 +43,33 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { "Reuse key check for sector: %d", m->key_attack_current_sector); } else { - snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->sector_current); + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); } canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); float dict_progress = m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); float progress = m->sectors_total == 0 ? 0 : - ((float)(m->sector_current) + dict_progress) / + ((float)(m->current_sector) + dict_progress) / (float)(m->sectors_total); - if(progress > 1.0) { - progress = 1.0; + if(progress > 1.0f) { + progress = 1.0f; } if(m->dict_keys_current == 0) { // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%d", 1, m->dict_keys_total); + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); } else { snprintf( - draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); } elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); - snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); snprintf( draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); @@ -80,55 +79,56 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { } static bool dict_attack_input_callback(InputEvent* event, void* context) { - DictAttack* dict_attack = context; + DictAttack* instance = context; bool consumed = false; + if(event->type == InputTypeShort && event->key == InputKeyOk) { - if(dict_attack->callback) { - dict_attack->callback(dict_attack->context); + if(instance->callback) { + instance->callback(DictAttackEventSkipPressed, instance->context); } consumed = true; } + return consumed; } DictAttack* dict_attack_alloc() { - DictAttack* dict_attack = malloc(sizeof(DictAttack)); - dict_attack->view = view_alloc(); - view_allocate_model(dict_attack->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); - view_set_draw_callback(dict_attack->view, dict_attack_draw_callback); - view_set_input_callback(dict_attack->view, dict_attack_input_callback); - view_set_context(dict_attack->view, dict_attack); + DictAttack* instance = malloc(sizeof(DictAttack)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); + view_set_draw_callback(instance->view, dict_attack_draw_callback); + view_set_input_callback(instance->view, dict_attack_input_callback); + view_set_context(instance->view, instance); with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { model->header = furi_string_alloc(); }, false); - return dict_attack; + + return instance; } -void dict_attack_free(DictAttack* dict_attack) { - furi_assert(dict_attack); +void dict_attack_free(DictAttack* instance) { + furi_assert(instance); + with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { furi_string_free(model->header); }, - false); - view_free(dict_attack->view); - free(dict_attack); + instance->view, DictAttackViewModel * model, { furi_string_free(model->header); }, false); + + view_free(instance->view); + free(instance); } -void dict_attack_reset(DictAttack* dict_attack) { - furi_assert(dict_attack); +void dict_attack_reset(DictAttack* instance) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { - model->state = DictAttackStateRead; - model->type = MfClassicType1k; + model->card_detected = false; model->sectors_total = 0; model->sectors_read = 0; - model->sector_current = 0; - model->keys_total = 0; + model->current_sector = 0; model->keys_found = 0; model->dict_keys_total = 0; model->dict_keys_current = 0; @@ -138,152 +138,108 @@ void dict_attack_reset(DictAttack* dict_attack) { false); } -View* dict_attack_get_view(DictAttack* dict_attack) { - furi_assert(dict_attack); - return dict_attack->view; +View* dict_attack_get_view(DictAttack* instance) { + furi_assert(instance); + + return instance->view; } -void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context) { - furi_assert(dict_attack); +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context) { + furi_assert(instance); furi_assert(callback); - dict_attack->callback = callback; - dict_attack->context = context; + + instance->callback = callback; + instance->context = context; } -void dict_attack_set_header(DictAttack* dict_attack, const char* header) { - furi_assert(dict_attack); +void dict_attack_set_header(DictAttack* instance, const char* header) { + furi_assert(instance); furi_assert(header); with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { furi_string_set(model->header, header); }, true); } -void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) { - furi_assert(dict_attack); - dict_attack->card_present = true; +void dict_attack_set_card_state(DictAttack* instance, bool detected) { + furi_assert(instance); + with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->state = DictAttackStateRead; - model->sectors_total = mf_classic_get_total_sectors_num(type); - model->keys_total = model->sectors_total * 2; - }, - true); + instance->view, DictAttackViewModel * model, { model->card_detected = detected; }, true); } -void dict_attack_set_card_removed(DictAttack* dict_attack) { - furi_assert(dict_attack); - dict_attack->card_present = false; +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, - { model->state = DictAttackStateCardRemoved; }, + { model->sectors_total = sectors_total; }, true); } -bool dict_attack_get_card_state(DictAttack* dict_attack) { - furi_assert(dict_attack); - return dict_attack->card_present; -} +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read) { + furi_assert(instance); -void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { - furi_assert(dict_attack); with_view_model( - dict_attack->view, DictAttackViewModel * model, { model->sectors_read = sec_read; }, true); + instance->view, DictAttackViewModel * model, { model->sectors_read = sectors_read; }, true); } -void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); -} +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found) { + furi_assert(instance); -void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec) { - furi_assert(dict_attack); with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->sector_current = curr_sec; - model->dict_keys_current = 0; - }, - true); + instance->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); } -void dict_attack_inc_current_sector(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->sector_current < model->sectors_total) { - model->sector_current++; - model->dict_keys_current = 0; - } - }, - true); -} +void dict_attack_set_current_sector(DictAttack* instance, uint8_t current_sector) { + furi_assert(instance); -void dict_attack_inc_keys_found(DictAttack* dict_attack) { - furi_assert(dict_attack); with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, - { - if(model->keys_found < model->keys_total) { - model->keys_found++; - } - }, + { model->current_sector = current_sector; }, true); } -void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total) { - furi_assert(dict_attack); +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { model->dict_keys_total = dict_keys_total; }, true); } -void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) { - furi_assert(dict_attack); +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, - { - if(model->dict_keys_current + keys_tried < model->dict_keys_total) { - model->dict_keys_current += keys_tried; - } - }, + { model->dict_keys_current = cur_key_num; }, true); } -void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector) { - furi_assert(dict_attack); +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { - model->is_key_attack = is_key_attack; + model->is_key_attack = true; model->key_attack_current_sector = sector; }, true); } -void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack) { - furi_assert(dict_attack); +void dict_attack_reset_key_attack(DictAttack* instance) { + furi_assert(instance); + with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->key_attack_current_sector < model->sectors_total) { - model->key_attack_current_sector++; - } - }, - true); + instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); } diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 73b98a1b82..54a0220fe5 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -1,46 +1,50 @@ #pragma once + #include #include -#include -#include +#ifdef __cplusplus +extern "C" { +#endif typedef struct DictAttack DictAttack; -typedef void (*DictAttackCallback)(void* context); - -DictAttack* dict_attack_alloc(); +typedef enum { + DictAttackEventSkipPressed, +} DictAttackEvent; -void dict_attack_free(DictAttack* dict_attack); +typedef void (*DictAttackCallback)(DictAttackEvent event, void* context); -void dict_attack_reset(DictAttack* dict_attack); +DictAttack* dict_attack_alloc(); -View* dict_attack_get_view(DictAttack* dict_attack); +void dict_attack_free(DictAttack* instance); -void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context); +void dict_attack_reset(DictAttack* instance); -void dict_attack_set_header(DictAttack* dict_attack, const char* header); +View* dict_attack_get_view(DictAttack* instance); -void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type); +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context); -void dict_attack_set_card_removed(DictAttack* dict_attack); +void dict_attack_set_header(DictAttack* instance, const char* header); -bool dict_attack_get_card_state(DictAttack* dict_attack); +void dict_attack_set_card_state(DictAttack* instance, bool detected); -void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read); +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total); -void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found); +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read); -void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec); +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found); -void dict_attack_inc_current_sector(DictAttack* dict_attack); +void dict_attack_set_current_sector(DictAttack* instance, uint8_t curr_sec); -void dict_attack_inc_keys_found(DictAttack* dict_attack); +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total); -void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num); -void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); -void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); +void dict_attack_reset_key_attack(DictAttack* instance); -void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack); \ No newline at end of file +#ifdef __cplusplus +} +#endif diff --git a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c index ebe825e6a9..2f009e7d2a 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include "../desktop_i.h" diff --git a/assets/unit_tests/nfc/Ntag213_locked.nfc b/assets/unit_tests/nfc/Ntag213_locked.nfc new file mode 100644 index 0000000000..32f7771165 --- /dev/null +++ b/assets/unit_tests/nfc/Ntag213_locked.nfc @@ -0,0 +1,66 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: NTAG213 +# UID, ATQA and SAK are common for all formats +UID: 04 AC 6B 72 BA 6C 80 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 2D AE BC AF 84 B8 85 87 C2 FB FE 76 13 58 86 72 8E 1D 3C B5 DA 24 23 44 E5 63 4D 4C 82 FB D7 18 +Mifare version: 00 04 04 02 01 00 0F 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 0 +Tearing 2: 00 +Pages total: 45 +Pages read: 45 +Page 0: 04 AC 6B 4B +Page 1: 72 BA 6C 80 +Page 2: 24 48 00 00 +Page 3: E1 10 12 00 +Page 4: 00 00 41 50 +Page 5: 00 00 31 31 +Page 6: 00 20 09 28 +Page 7: 00 03 31 59 +Page 8: 91 DF D3 00 +Page 9: 00 00 00 00 +Page 10: 00 00 00 00 +Page 11: 00 00 00 00 +Page 12: 00 00 00 00 +Page 13: 00 00 00 00 +Page 14: 00 00 00 00 +Page 15: 00 00 00 00 +Page 16: 00 00 00 00 +Page 17: 00 00 00 00 +Page 18: 00 00 00 00 +Page 19: 00 00 00 00 +Page 20: 00 00 00 00 +Page 21: 00 00 00 00 +Page 22: 00 00 00 00 +Page 23: 00 00 00 00 +Page 24: 00 00 00 00 +Page 25: 00 00 00 00 +Page 26: 00 00 00 00 +Page 27: 00 00 00 00 +Page 28: 00 00 00 00 +Page 29: 00 00 00 00 +Page 30: 00 00 00 00 +Page 31: 00 00 00 00 +Page 32: 00 00 00 00 +Page 33: 00 00 00 00 +Page 34: 00 00 00 00 +Page 35: 00 00 00 00 +Page 36: 00 00 00 00 +Page 37: 00 00 00 00 +Page 38: 00 00 00 00 +Page 39: 00 00 00 00 +Page 40: 00 00 00 BD +Page 41: 04 00 00 04 +Page 42: C0 05 00 00 +Page 43: 95 3F 52 FF +Page 44: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ntag215.nfc b/assets/unit_tests/nfc/Ntag215.nfc new file mode 100644 index 0000000000..420af08a66 --- /dev/null +++ b/assets/unit_tests/nfc/Ntag215.nfc @@ -0,0 +1,156 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: NTAG215 +# UID, ATQA and SAK are common for all formats +UID: 04 51 5C FA 6F 73 81 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 42 21 E4 6C 79 6A 81 5E EA 0D 93 6D 85 EE 4B 0C 2A 00 D5 77 F1 C5 67 F3 63 75 F8 EB 86 48 5E 6B +Mifare version: 00 04 04 02 01 00 11 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 00 +Tearing 2: 00 +Pages total: 135 +Pages read: 135 +Page 0: 04 51 5C 81 +Page 1: FA 6F 73 81 +Page 2: 67 48 0F E0 +Page 3: F1 10 FF EE +Page 4: A5 00 00 00 +Page 5: 90 42 74 71 +Page 6: FD 8F 50 61 +Page 7: C5 65 1B 54 +Page 8: EF 68 D0 8E +Page 9: 3D 35 DB 83 +Page 10: D3 00 29 F6 +Page 11: 42 2A A5 5C +Page 12: F1 69 0A FC +Page 13: B6 44 E9 6B +Page 14: 77 41 88 81 +Page 15: 86 31 CB AD +Page 16: B1 DE F1 AB +Page 17: DF 96 C2 C5 +Page 18: C1 26 99 96 +Page 19: 85 AF 9F 0E +Page 20: 58 FE ED DC +Page 21: 0A 0A 00 01 +Page 22: 03 C1 05 02 +Page 23: 38 39 34 33 +Page 24: 49 2D 4E 5C +Page 25: 5B 21 0F 44 +Page 26: 3F 3F 76 69 +Page 27: B4 72 D8 38 +Page 28: A0 35 53 51 +Page 29: 53 EB A6 7C +Page 30: 3E 8B 97 C0 +Page 31: 00 7A 45 13 +Page 32: 3A 8B D4 0F +Page 33: 31 C2 32 CC +Page 34: B4 24 A6 1B +Page 35: D3 F5 4A 1F +Page 36: CD 8F 1D 64 +Page 37: 01 F4 DF C2 +Page 38: 11 16 C2 C5 +Page 39: 30 6D 49 AF +Page 40: 10 D4 7C 3C +Page 41: 6E 36 4E 08 +Page 42: 95 76 BC 84 +Page 43: 35 50 DD F0 +Page 44: 21 0F EE D9 +Page 45: 85 19 54 5F +Page 46: 3E A9 04 20 +Page 47: 1B 97 E4 39 +Page 48: FF 0A 45 F6 +Page 49: 13 D4 3E DD +Page 50: 97 42 FC 67 +Page 51: 6A AC 78 96 +Page 52: D1 DA 25 23 +Page 53: BF 4D B3 76 +Page 54: F1 21 ED 15 +Page 55: BD 55 11 C4 +Page 56: 4E 8C E9 23 +Page 57: C0 C4 6D 5A +Page 58: 58 25 FF 95 +Page 59: 3C 2B 7A 57 +Page 60: 66 BE A0 61 +Page 61: BC FC 4A 31 +Page 62: 4D AC EE 81 +Page 63: BE 1A 86 04 +Page 64: F6 D7 5E B3 +Page 65: E7 A8 A2 86 +Page 66: E9 40 AB 47 +Page 67: C8 36 E4 3E +Page 68: A7 4D D3 EA +Page 69: 83 9A 64 F7 +Page 70: 96 6B 5D BF +Page 71: 4E A2 A6 0F +Page 72: BD 3D BE 7C +Page 73: 22 0C 68 51 +Page 74: 0F 9A B8 AE +Page 75: 38 2C C4 CD +Page 76: 53 D8 DD 18 +Page 77: A6 5D 35 87 +Page 78: C9 6D 99 59 +Page 79: 61 9F B6 DC +Page 80: E6 22 0F 99 +Page 81: 39 82 79 60 +Page 82: 58 2E BE F7 +Page 83: EF F7 95 62 +Page 84: D5 06 1B 58 +Page 85: 65 05 A9 08 +Page 86: 75 ED 5D 90 +Page 87: 5A E1 7E C9 +Page 88: 35 D6 29 BB +Page 89: D0 67 6C F9 +Page 90: A0 FF 0B 93 +Page 91: 22 EA A3 3F +Page 92: E2 BD BD 58 +Page 93: BE 93 D9 94 +Page 94: 41 CC 7E 40 +Page 95: E6 8C 5A 43 +Page 96: 65 C1 24 94 +Page 97: B9 97 61 13 +Page 98: AD 74 FF 21 +Page 99: 0F EC F6 03 +Page 100: 89 5D 89 E5 +Page 101: 8D 11 F8 D7 +Page 102: 33 43 79 2E +Page 103: 23 E5 29 B5 +Page 104: 53 98 13 FF +Page 105: E8 79 8B 33 +Page 106: 45 6C 34 38 +Page 107: 3B 69 28 D7 +Page 108: D2 80 B0 2F +Page 109: D0 18 D5 DD +Page 110: 6C 2D D9 97 +Page 111: CA 78 B4 A2 +Page 112: B7 3E B8 79 +Page 113: A2 BE 54 E4 +Page 114: C8 28 0C 4A +Page 115: 81 E7 EC 1C +Page 116: 39 93 6F 70 +Page 117: 75 77 5C FC +Page 118: 66 58 0C 1C +Page 119: 9F 70 2E C8 +Page 120: 52 4A 52 BD +Page 121: 56 D5 6A 15 +Page 122: 54 1B 33 90 +Page 123: 44 11 C1 07 +Page 124: 11 5C BA 80 +Page 125: 10 14 20 9A +Page 126: 4A D8 E6 36 +Page 127: DA B8 59 E5 +Page 128: 5E 48 95 DA +Page 129: 96 6A 26 85 +Page 130: 01 00 0F BD +Page 131: 00 00 00 04 +Page 132: 5F 00 00 00 +Page 133: 00 00 00 00 +Page 134: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ntag216.nfc b/assets/unit_tests/nfc/Ntag216.nfc new file mode 100644 index 0000000000..debe43858f --- /dev/null +++ b/assets/unit_tests/nfc/Ntag216.nfc @@ -0,0 +1,252 @@ +Filetype: Flipper NFC device +Version: 2 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card +Device type: NTAG216 +# UID, ATQA and SAK are common for all formats +UID: 04 D9 65 0A 32 5E 80 +ATQA: 44 00 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 48 2A F2 01 0F F2 F5 A7 9A D5 79 6E CB 14 54 48 98 D1 57 5D 8A 23 A9 B0 E8 20 02 3E CD C8 16 DB +Mifare version: 00 04 04 02 01 00 13 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 0 +Tearing 2: 00 +Pages total: 231 +Pages read: 231 +Page 0: 04 D9 65 30 +Page 1: 0A 32 5E 80 +Page 2: E6 48 00 00 +Page 3: E1 10 6D 00 +Page 4: 03 37 D1 01 +Page 5: 33 55 04 6D +Page 6: 2E 79 6F 75 +Page 7: 74 75 62 65 +Page 8: 2E 63 6F 6D +Page 9: 2F 77 61 74 +Page 10: 63 68 3F 76 +Page 11: 3D 62 78 71 +Page 12: 4C 73 72 6C +Page 13: 61 6B 4B 38 +Page 14: 26 66 65 61 +Page 15: 74 75 72 65 +Page 16: 3D 79 6F 75 +Page 17: 74 75 2E 62 +Page 18: 65 FE 00 00 +Page 19: 00 00 00 00 +Page 20: 00 00 00 00 +Page 21: 00 00 00 00 +Page 22: 00 00 00 00 +Page 23: 00 00 00 00 +Page 24: 00 00 00 00 +Page 25: 00 00 00 00 +Page 26: 00 00 00 00 +Page 27: 00 00 00 00 +Page 28: 00 00 00 00 +Page 29: 00 00 00 00 +Page 30: 00 00 00 00 +Page 31: 00 00 00 00 +Page 32: 00 00 00 00 +Page 33: 00 00 00 00 +Page 34: 00 00 00 00 +Page 35: 00 00 00 00 +Page 36: 00 00 00 00 +Page 37: 00 00 00 00 +Page 38: 00 00 00 00 +Page 39: 00 00 00 00 +Page 40: 00 00 00 00 +Page 41: 00 00 00 00 +Page 42: 00 00 00 00 +Page 43: 00 00 00 00 +Page 44: 00 00 00 00 +Page 45: 00 00 00 00 +Page 46: 00 00 00 00 +Page 47: 00 00 00 00 +Page 48: 00 00 00 00 +Page 49: 00 00 00 00 +Page 50: 00 00 00 00 +Page 51: 00 00 00 00 +Page 52: 00 00 00 00 +Page 53: 00 00 00 00 +Page 54: 00 00 00 00 +Page 55: 00 00 00 00 +Page 56: 00 00 00 00 +Page 57: 00 00 00 00 +Page 58: 00 00 00 00 +Page 59: 00 00 00 00 +Page 60: 00 00 00 00 +Page 61: 00 00 00 00 +Page 62: 00 00 00 00 +Page 63: 00 00 00 00 +Page 64: 00 00 00 00 +Page 65: 00 00 00 00 +Page 66: 00 00 00 00 +Page 67: 00 00 00 00 +Page 68: 00 00 00 00 +Page 69: 00 00 00 00 +Page 70: 00 00 00 00 +Page 71: 00 00 00 00 +Page 72: 00 00 00 00 +Page 73: 00 00 00 00 +Page 74: 00 00 00 00 +Page 75: 00 00 00 00 +Page 76: 00 00 00 00 +Page 77: 00 00 00 00 +Page 78: 00 00 00 00 +Page 79: 00 00 00 00 +Page 80: 00 00 00 00 +Page 81: 00 00 00 00 +Page 82: 00 00 00 00 +Page 83: 00 00 00 00 +Page 84: 00 00 00 00 +Page 85: 00 00 00 00 +Page 86: 00 00 00 00 +Page 87: 00 00 00 00 +Page 88: 00 00 00 00 +Page 89: 00 00 00 00 +Page 90: 00 00 00 00 +Page 91: 00 00 00 00 +Page 92: 00 00 00 00 +Page 93: 00 00 00 00 +Page 94: 00 00 00 00 +Page 95: 00 00 00 00 +Page 96: 00 00 00 00 +Page 97: 00 00 00 00 +Page 98: 00 00 00 00 +Page 99: 00 00 00 00 +Page 100: 00 00 00 00 +Page 101: 00 00 00 00 +Page 102: 00 00 00 00 +Page 103: 00 00 00 00 +Page 104: 00 00 00 00 +Page 105: 00 00 00 00 +Page 106: 00 00 00 00 +Page 107: 00 00 00 00 +Page 108: 00 00 00 00 +Page 109: 00 00 00 00 +Page 110: 00 00 00 00 +Page 111: 00 00 00 00 +Page 112: 00 00 00 00 +Page 113: 00 00 00 00 +Page 114: 00 00 00 00 +Page 115: 00 00 00 00 +Page 116: 00 00 00 00 +Page 117: 00 00 00 00 +Page 118: 00 00 00 00 +Page 119: 00 00 00 00 +Page 120: 00 00 00 00 +Page 121: 00 00 00 00 +Page 122: 00 00 00 00 +Page 123: 00 00 00 00 +Page 124: 00 00 00 00 +Page 125: 00 00 00 00 +Page 126: 00 00 00 00 +Page 127: 00 00 00 00 +Page 128: 00 00 00 00 +Page 129: 00 00 00 00 +Page 130: 00 00 00 00 +Page 131: 00 00 00 00 +Page 132: 00 00 00 00 +Page 133: 00 00 00 00 +Page 134: 00 00 00 00 +Page 135: 00 00 00 00 +Page 136: 00 00 00 00 +Page 137: 00 00 00 00 +Page 138: 00 00 00 00 +Page 139: 00 00 00 00 +Page 140: 00 00 00 00 +Page 141: 00 00 00 00 +Page 142: 00 00 00 00 +Page 143: 00 00 00 00 +Page 144: 00 00 00 00 +Page 145: 00 00 00 00 +Page 146: 00 00 00 00 +Page 147: 00 00 00 00 +Page 148: 00 00 00 00 +Page 149: 00 00 00 00 +Page 150: 00 00 00 00 +Page 151: 00 00 00 00 +Page 152: 00 00 00 00 +Page 153: 00 00 00 00 +Page 154: 00 00 00 00 +Page 155: 00 00 00 00 +Page 156: 00 00 00 00 +Page 157: 00 00 00 00 +Page 158: 00 00 00 00 +Page 159: 00 00 00 00 +Page 160: 00 00 00 00 +Page 161: 00 00 00 00 +Page 162: 00 00 00 00 +Page 163: 00 00 00 00 +Page 164: 00 00 00 00 +Page 165: 00 00 00 00 +Page 166: 00 00 00 00 +Page 167: 00 00 00 00 +Page 168: 00 00 00 00 +Page 169: 00 00 00 00 +Page 170: 00 00 00 00 +Page 171: 00 00 00 00 +Page 172: 00 00 00 00 +Page 173: 00 00 00 00 +Page 174: 00 00 00 00 +Page 175: 00 00 00 00 +Page 176: 00 00 00 00 +Page 177: 00 00 00 00 +Page 178: 00 00 00 00 +Page 179: 00 00 00 00 +Page 180: 00 00 00 00 +Page 181: 00 00 00 00 +Page 182: 00 00 00 00 +Page 183: 00 00 00 00 +Page 184: 00 00 00 00 +Page 185: 00 00 00 00 +Page 186: 00 00 00 00 +Page 187: 00 00 00 00 +Page 188: 00 00 00 00 +Page 189: 00 00 00 00 +Page 190: 00 00 00 00 +Page 191: 00 00 00 00 +Page 192: 00 00 00 00 +Page 193: 00 00 00 00 +Page 194: 00 00 00 00 +Page 195: 00 00 00 00 +Page 196: 00 00 00 00 +Page 197: 00 00 00 00 +Page 198: 00 00 00 00 +Page 199: 00 00 00 00 +Page 200: 00 00 00 00 +Page 201: 00 00 00 00 +Page 202: 00 00 00 00 +Page 203: 00 00 00 00 +Page 204: 00 00 00 00 +Page 205: 00 00 00 00 +Page 206: 00 00 00 00 +Page 207: 00 00 00 00 +Page 208: 00 00 00 00 +Page 209: 00 00 00 00 +Page 210: 00 00 00 00 +Page 211: 00 00 00 00 +Page 212: 00 00 00 00 +Page 213: 00 00 00 00 +Page 214: 00 00 00 00 +Page 215: 00 00 00 00 +Page 216: 00 00 00 00 +Page 217: 00 00 00 00 +Page 218: 00 00 00 00 +Page 219: 00 00 00 00 +Page 220: 00 00 00 00 +Page 221: 00 00 00 00 +Page 222: 00 00 00 00 +Page 223: 00 00 00 00 +Page 224: 00 00 00 00 +Page 225: 00 00 00 00 +Page 226: 00 00 00 BD +Page 227: 04 00 00 FF +Page 228: 00 05 00 00 +Page 229: 00 00 00 00 +Page 230: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ultralight_11.nfc b/assets/unit_tests/nfc/Ultralight_11.nfc new file mode 100644 index 0000000000..22441289dd --- /dev/null +++ b/assets/unit_tests/nfc/Ultralight_11.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: Mifare Ultralight 11 +# UID, ATQA and SAK are common for all formats +UID: 04 15 74 F2 B0 5E 81 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: A4 37 7D E5 8C 2F 88 D8 04 60 41 6E 3A C8 CD DB 19 94 26 12 C5 D0 12 B0 EB 88 05 72 89 F2 A5 61 +Mifare version: 00 04 03 01 01 00 0B 03 +Counter 0: 0 +Tearing 0: BD +Counter 1: 0 +Tearing 1: BD +Counter 2: 0 +Tearing 2: BD +Pages total: 20 +Pages read: 20 +Page 0: 04 15 74 ED +Page 1: F2 B0 5E 81 +Page 2: 9D 48 F8 FF +Page 3: C1 31 3E 3F +Page 4: B0 00 F0 02 +Page 5: 2F B3 45 A0 +Page 6: D4 9C 02 F2 +Page 7: 4A B1 ED FF +Page 8: C8 01 00 02 +Page 9: 4F B3 46 70 +Page 10: EE F6 60 B0 +Page 11: B6 C6 12 1B +Page 12: B9 1E 49 C3 +Page 13: 49 DF 7A 57 +Page 14: 08 52 2A 11 +Page 15: 28 0A 28 59 +Page 16: 00 00 00 FF +Page 17: 00 05 00 00 +Page 18: FF FF FF FF +Page 19: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ultralight_21.nfc b/assets/unit_tests/nfc/Ultralight_21.nfc new file mode 100644 index 0000000000..dc01e93a60 --- /dev/null +++ b/assets/unit_tests/nfc/Ultralight_21.nfc @@ -0,0 +1,62 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: Mifare Ultralight 21 +# UID, ATQA and SAK are common for all formats +UID: 34 BF AB B1 AE 73 D6 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Mifare version: 00 34 21 01 01 00 0E 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 0 +Tearing 2: 00 +Pages total: 41 +Pages read: 41 +Page 0: 34 BF AB A8 +Page 1: B1 AE 73 D6 +Page 2: BA 00 70 08 +Page 3: FF FF FF FC +Page 4: 45 D9 BB A0 +Page 5: 5D 9D FA 00 +Page 6: 80 70 38 40 +Page 7: 12 30 02 00 +Page 8: 00 00 00 00 +Page 9: 00 00 00 00 +Page 10: AC A1 0D E4 +Page 11: 80 70 38 40 +Page 12: 00 57 A0 01 +Page 13: 00 08 C1 40 +Page 14: 00 00 00 00 +Page 15: AC A1 0D E4 +Page 16: 00 00 00 00 +Page 17: 00 00 00 00 +Page 18: 00 00 00 00 +Page 19: 00 00 00 00 +Page 20: 00 00 00 00 +Page 21: 00 00 00 00 +Page 22: 00 00 00 00 +Page 23: 00 00 00 00 +Page 24: 00 00 00 00 +Page 25: 00 00 00 00 +Page 26: 00 00 00 00 +Page 27: 00 00 00 00 +Page 28: 00 00 00 00 +Page 29: 00 00 00 00 +Page 30: 00 00 00 00 +Page 31: 00 00 00 00 +Page 32: 00 00 00 00 +Page 33: 00 00 00 00 +Page 34: 00 00 00 00 +Page 35: 00 00 00 00 +Page 36: 00 00 00 BD +Page 37: 00 00 00 FF +Page 38: 00 05 00 00 +Page 39: FF FF FF FF +Page 40: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index 78c6420ee0..f752cdb901 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -1,44 +1,109 @@ # NFC Flipper File Formats -## NFC-A (UID) + Header +## UID + Header (General format) ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card - Device type: UID - # UID, ATQA and SAK are common for all formats - UID: 04 85 92 8A A0 61 81 - ATQA: 00 44 - SAK: 00 + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-4A + # UID is common for all formats + UID: 04 48 6A 32 33 58 80 + ------------------------- + (Device-specific data) ### Description -This file format is used to store the UID, SAK and ATQA of a NFC-A device. It does not store any internal data, so it can be used for multiple different card types. Also used as a header for other formats. +This file format is used to store the device type and the UID of an NFC device. It does not store any internal data, so it is only used as a header for other formats. Version differences: 1. Initial version, deprecated 2. LSB ATQA (e.g. 4400 instead of 0044) 3. MSB ATQA (current version) +4. Replace UID device type with ISO14443-3A -UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. +## ISO14443-3A -## Mifare Ultralight/NTAG + Filetype: Flipper NFC device + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-3A + # UID is common for all formats + UID: 34 19 6D 41 14 56 E6 + # ISO14443-3A specific data + ATQA: 00 44 + SAK: 00 + +### Description + +This file format is used to store the UID, SAK and ATQA of a ISO14443-3A device. +UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. + +Version differences: +None, there are no versions yet. + +## ISO14443-3B + + Filetype: Flipper NFC device + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-3B + # UID is common for all formats + UID: 30 1D B3 28 + # ISO14443-3B specific data + Application data: 00 12 34 FF + Protocol info: 11 81 E1 + +### Description + +This file format is used to store the UID, Application data and Protocol info of a ISO14443-3B device. +UID must be 4 bytes long. Application data is 4 bytes long. Protocol info is 3 bytes long. + +Version differences: +None, there are no versions yet. + +## ISO14443-4A + +### Example + + Filetype: Flipper NFC device + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-4A + # UID is common for all formats + UID: 04 48 6A 32 33 58 80 + # ISO14443-3A specific data + ATQA: 03 44 + SAK: 20 + # ISO14443-4A specific data + ATS: 06 75 77 81 02 80 + +### Description + +This file format is used to store the UID, SAK and ATQA of a ISO14443-4A device. It also stores the Answer to Select (ATS) data of the card. +ATS must be no less than 5 bytes long. + +Version differences: +None, there are no versions yet. + +## NTAG/Ultralight ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic - Device type: NTAG216 - # UID, ATQA and SAK are common for all formats + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: NTAG/Ultralight + # UID is common for all formats UID: 04 85 90 54 12 98 23 + # ISO14443-3A specific data ATQA: 00 44 SAK: 00 - # Mifare Ultralight specific data - Data format version: 1 + # NTAG/Ultralight specific data + Data format version: 2 + NTAG/Ultralight type: NTAG216 Signature: 1B 84 EB 70 BD 4C BD 1B 1D E4 98 0B 18 58 BD 7C 72 85 B4 E4 7B 38 8E 96 CF 88 6B EE A3 43 AD 90 Mifare version: 00 04 04 02 01 00 13 03 Counter 0: 0 @@ -66,6 +131,8 @@ UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/NTAG device. It also stores the internal data of the card, the signature, the version, and the counters. The data is stored in pages, just like on the card itself. +The "NTAG/Ultralight type" field contains the concrete device type. It must be one of: Mifare Ultralight, Mifare Ultralight 11, Mifare Ultralight 21, NTAG203, NTAG213, NTAG215, NTAG216, NTAG I2C 1K, NTAG I2C 2K, NTAG I2C Plus 1K, NTAG I2C Plus 2K. + The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: (page 31) The "Mifare version" field is not related to the file format version but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: (page 21) @@ -74,18 +141,20 @@ Other fields are the direct representation of the card's internal state. Learn m Version differences: -1. Current version +1. Mifare Ultralight type is stored directly in Device type field +2. Current version, Mifare Ultralight type is stored in the same-named field ## Mifare Classic ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire Device type: Mifare Classic - # UID, ATQA and SAK are common for all formats + # UID is common for all formats UID: BA E2 7C 9D + # ISO14443-3A specific data ATQA: 00 02 SAK: 18 # Mifare Classic specific data @@ -145,13 +214,16 @@ Example: ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire Device type: Mifare DESFire - # UID, ATQA and SAK are common for all formats + # UID is common for all formats UID: 04 2F 19 0A CD 66 80 + # ISO14443-3A specific data ATQA: 03 44 SAK: 20 + # ISO14443-4A specific data + ATS: 06 75 77 81 02 80 # Mifare DESFire specific data PICC Version: 04 01 01 12 00 1A 05 04 01 01 02 01 1A 05 04 2F 19 0A CD 66 80 CE ED D4 51 80 31 19 PICC Free Memory: 7520 diff --git a/firmware.scons b/firmware.scons index 82f775d719..d8e96ad7d0 100644 --- a/firmware.scons +++ b/firmware.scons @@ -71,6 +71,15 @@ env = ENV.Clone( "FURI_DEBUG" if ENV["DEBUG"] else "FURI_NDEBUG", ], }, + "nfc": { + "CCFLAGS": [ + "-Og", + ], + "CPPDEFINES": [ + "NDEBUG", + "FURI_DEBUG", + ], + }, }, FW_API_TABLE=None, _APP_ICONS=None, diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index a3eb2743d8..8e15030a94 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.2,, +Version,+,40.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -81,6 +81,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, @@ -132,11 +133,15 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/music_worker/music_worker.h,, +Header,+,lib/nanopb/pb.h,, +Header,+,lib/nanopb/pb_decode.h,, +Header,+,lib/nanopb/pb_encode.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, +Header,+,lib/signal_reader/signal_reader.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -166,6 +171,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/bit_buffer.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -180,6 +186,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/sha256.h,, +Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -522,6 +529,36 @@ Function,-,atoll,long long,const char* Function,-,basename,char*,const char* Function,-,bcmp,int,"const void*, const void*, size_t" Function,-,bcopy,void,"const void*, void*, size_t" +Function,+,bit_buffer_alloc,BitBuffer*,size_t +Function,+,bit_buffer_append,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_append_bit,void,"BitBuffer*, _Bool" +Function,+,bit_buffer_append_byte,void,"BitBuffer*, uint8_t" +Function,+,bit_buffer_append_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_append_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_copy_bits,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes_with_parity,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_left,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_free,void,BitBuffer* +Function,+,bit_buffer_get_byte,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_byte_from_bit,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_capacity_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_get_data,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_parity,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_size,size_t,const BitBuffer* +Function,+,bit_buffer_get_size_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_has_partial_byte,_Bool,const BitBuffer* +Function,+,bit_buffer_reset,void,BitBuffer* +Function,+,bit_buffer_set_byte,void,"BitBuffer*, size_t, uint8_t" +Function,+,bit_buffer_set_byte_with_parity,void,"BitBuffer*, size_t, uint8_t, _Bool" +Function,+,bit_buffer_set_size,void,"BitBuffer*, size_t" +Function,+,bit_buffer_set_size_bytes,void,"BitBuffer*, size_t" +Function,+,bit_buffer_starts_with_byte,_Bool,"const BitBuffer*, uint8_t" +Function,+,bit_buffer_write_bytes,void,"const BitBuffer*, void*, size_t" +Function,+,bit_buffer_write_bytes_mid,void,"const BitBuffer*, void*, size_t, size_t" +Function,+,bit_buffer_write_bytes_with_parity,void,"const BitBuffer*, void*, size_t, size_t*" Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,ble_app_init,_Bool, Function,+,ble_app_thread_stop,void, @@ -679,24 +716,19 @@ Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, u Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" Function,-,difftime,double,"time_t, time_t" -Function,-,digital_sequence_add,void,"DigitalSequence*, uint8_t" +Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* Function,-,digital_sequence_free,void,DigitalSequence* -Function,-,digital_sequence_send,_Bool,DigitalSequence* -Function,-,digital_sequence_set_sendtime,void,"DigitalSequence*, uint32_t" -Function,-,digital_sequence_set_signal,void,"DigitalSequence*, uint8_t, DigitalSignal*" -Function,-,digital_sequence_timebase_correction,void,"DigitalSequence*, float" -Function,-,digital_signal_add,void,"DigitalSignal*, uint32_t" -Function,-,digital_signal_add_pulse,void,"DigitalSignal*, uint32_t, _Bool" +Function,+,digital_sequence_register_signal,void,"DigitalSequence*, uint8_t, const DigitalSignal*" +Function,+,digital_sequence_transmit,void,DigitalSequence* +Function,+,digital_signal_add_period,void,"DigitalSignal*, uint32_t" +Function,+,digital_signal_add_period_with_level,void,"DigitalSignal*, uint32_t, _Bool" Function,-,digital_signal_alloc,DigitalSignal*,uint32_t -Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" Function,-,digital_signal_free,void,DigitalSignal* -Function,-,digital_signal_get_edge,uint32_t,"DigitalSignal*, uint32_t" -Function,-,digital_signal_get_edges_cnt,uint32_t,DigitalSignal* -Function,-,digital_signal_get_start_level,_Bool,DigitalSignal* -Function,-,digital_signal_prepare_arr,void,DigitalSignal* -Function,-,digital_signal_send,void,"DigitalSignal*, const GpioPin*" +Function,+,digital_signal_get_size,uint32_t,const DigitalSignal* +Function,+,digital_signal_get_start_level,_Bool,const DigitalSignal* +Function,+,digital_signal_set_start_level,void,"DigitalSignal*, _Bool" Function,-,diprintf,int,"int, const char*, ..." Function,+,dir_walk_alloc,DirWalk*,Storage* Function,+,dir_walk_close,void,DirWalk* @@ -1777,6 +1809,35 @@ Function,+,path_extract_dirname,void,"const char*, FuriString*" Function,+,path_extract_extension,void,"FuriString*, char*, size_t" Function,+,path_extract_filename,void,"FuriString*, FuriString*, _Bool" Function,+,path_extract_filename_no_ext,void,"const char*, FuriString*" +Function,+,pb_close_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_decode,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*" +Function,+,pb_decode_bool,_Bool,"pb_istream_t*, _Bool*" +Function,+,pb_decode_ex,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*, unsigned int" +Function,+,pb_decode_fixed32,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_fixed64,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_svarint,_Bool,"pb_istream_t*, int64_t*" +Function,+,pb_decode_tag,_Bool,"pb_istream_t*, pb_wire_type_t*, uint32_t*, _Bool*" +Function,+,pb_decode_varint,_Bool,"pb_istream_t*, uint64_t*" +Function,+,pb_decode_varint32,_Bool,"pb_istream_t*, uint32_t*" +Function,+,pb_default_field_callback,_Bool,"pb_istream_t*, pb_ostream_t*, const pb_field_t*" +Function,+,pb_encode,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_ex,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*, unsigned int" +Function,+,pb_encode_fixed32,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_fixed64,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_string,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" +Function,+,pb_encode_submessage,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_svarint,_Bool,"pb_ostream_t*, int64_t" +Function,+,pb_encode_tag,_Bool,"pb_ostream_t*, pb_wire_type_t, uint32_t" +Function,+,pb_encode_tag_for_field,_Bool,"pb_ostream_t*, const pb_field_iter_t*" +Function,+,pb_encode_varint,_Bool,"pb_ostream_t*, uint64_t" +Function,+,pb_get_encoded_size,_Bool,"size_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_istream_from_buffer,pb_istream_t,"const pb_byte_t*, size_t" +Function,+,pb_make_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_ostream_from_buffer,pb_ostream_t,"pb_byte_t*, size_t" +Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" +Function,+,pb_release,void,"const pb_msgdesc_t*, void*" +Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" +Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pcTaskGetName,char*,TaskHandle_t Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* @@ -1955,6 +2016,25 @@ Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" Function,+,sha256_process,void,sha256_context* Function,+,sha256_start,void,sha256_context* Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" +Function,+,signal_reader_free,void,SignalReader* +Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" +Function,+,signal_reader_set_pull,void,"SignalReader*, GpioPull" +Function,+,signal_reader_set_sample_rate,void,"SignalReader*, SignalReaderTimeUnit, uint32_t" +Function,+,signal_reader_set_trigger,void,"SignalReader*, SignalReaderTrigger" +Function,+,signal_reader_start,void,"SignalReader*, SignalReaderCallback, void*" +Function,+,signal_reader_stop,void,SignalReader* +Function,+,simple_array_alloc,SimpleArray*,const SimpleArrayConfig* +Function,+,simple_array_cget,const SimpleArrayElement*,"const SimpleArray*, uint32_t" +Function,+,simple_array_cget_data,const SimpleArrayData*,const SimpleArray* +Function,+,simple_array_copy,void,"SimpleArray*, const SimpleArray*" +Function,+,simple_array_free,void,SimpleArray* +Function,+,simple_array_get,SimpleArrayElement*,"SimpleArray*, uint32_t" +Function,+,simple_array_get_count,uint32_t,const SimpleArray* +Function,+,simple_array_get_data,SimpleArrayData*,SimpleArray* +Function,+,simple_array_init,void,"SimpleArray*, uint32_t" +Function,+,simple_array_is_equal,_Bool,"const SimpleArray*, const SimpleArray*" +Function,+,simple_array_reset,void,SimpleArray* Function,-,sin,double,double Function,-,sincos,void,"double, double*, double*" Function,-,sincosf,void,"float, float*, float*" @@ -2696,6 +2776,7 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,simple_array_config_uint8_t,const SimpleArrayConfig, Variable,-,suboptarg,char*, Variable,+,usb_ccid,FuriHalUsbInterface, Variable,+,usb_cdc_dual,FuriHalUsbInterface, diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json index 2d14813f6d..19de9dd615 100644 --- a/firmware/targets/f18/target.json +++ b/firmware/targets/f18/target.json @@ -20,6 +20,8 @@ "littlefs", "flipperformat", "toolbox", + "digital_signal", + "signal_reader", "microtar", "usb_stm32", "appframe", @@ -31,10 +33,18 @@ "flipperformat", "toolbox", "flipper18" + ], "excluded_sources": [ "furi_hal_infrared.c", "furi_hal_nfc.c", + "furi_hal_nfc_timer.c", + "furi_hal_nfc_irq.c", + "furi_hal_nfc_event.c", + "furi_hal_nfc_iso15693.c", + "furi_hal_nfc_iso14443a.c", + "furi_hal_nfc_iso14443b.c", + "furi_hal_nfc_felica.c", "furi_hal_rfid.c", "furi_hal_subghz.c" ], @@ -51,7 +61,6 @@ "lfrfid", "subghz", "ibutton", - "infrared", - "st25rfal002" + "infrared" ] -} \ No newline at end of file +} diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index eb997e64b1..3ae099b5f1 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.2,, +Version,+,40.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -48,7 +48,6 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_ibutton.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_nfc.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, @@ -74,6 +73,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_infrared.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_nfc.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, @@ -87,6 +87,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, @@ -150,18 +151,49 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/music_worker/music_worker.h,, -Header,+,lib/nfc/helpers/mfkey32.h,, -Header,+,lib/nfc/helpers/nfc_generators.h,, +Header,+,lib/nanopb/pb.h,, +Header,+,lib/nanopb/pb_decode.h,, +Header,+,lib/nanopb/pb_encode.h,, +Header,+,lib/nfc/helpers/iso13239_crc.h,, +Header,+,lib/nfc/helpers/iso14443_crc.h,, +Header,+,lib/nfc/helpers/nfc_data_generator.h,, +Header,+,lib/nfc/helpers/nfc_dict.h,, +Header,+,lib/nfc/helpers/nfc_util.h,, +Header,+,lib/nfc/nfc.h,, Header,+,lib/nfc/nfc_device.h,, -Header,+,lib/nfc/nfc_types.h,, -Header,+,lib/nfc/nfc_worker.h,, -Header,+,lib/nfc/parsers/nfc_supported_card.h,, -Header,+,lib/nfc/protocols/nfc_util.h,, +Header,+,lib/nfc/nfc_listener.h,, +Header,+,lib/nfc/nfc_poller.h,, +Header,+,lib/nfc/nfc_scanner.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h,, +Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b.h,, +Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h,, +Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a.h,, +Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h,, +Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h,, +Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b.h,, +Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h,, +Header,+,lib/nfc/protocols/mf_desfire/mf_desfire.h,, +Header,+,lib/nfc/protocols/mf_desfire/mf_desfire_poller.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h,, +Header,+,lib/nfc/protocols/slix/slix.h,, +Header,+,lib/nfc/protocols/st25tb/st25tb.h,, +Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, +Header,+,lib/signal_reader/signal_reader.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -207,6 +239,7 @@ Header,+,lib/subghz/subghz_worker.h,, Header,+,lib/subghz/transmitter.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/bit_buffer.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -221,6 +254,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/sha256.h,, +Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -563,6 +597,36 @@ Function,-,atoll,long long,const char* Function,-,basename,char*,const char* Function,-,bcmp,int,"const void*, const void*, size_t" Function,-,bcopy,void,"const void*, void*, size_t" +Function,+,bit_buffer_alloc,BitBuffer*,size_t +Function,+,bit_buffer_append,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_append_bit,void,"BitBuffer*, _Bool" +Function,+,bit_buffer_append_byte,void,"BitBuffer*, uint8_t" +Function,+,bit_buffer_append_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_append_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_copy_bits,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes_with_parity,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_left,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_free,void,BitBuffer* +Function,+,bit_buffer_get_byte,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_byte_from_bit,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_capacity_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_get_data,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_parity,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_size,size_t,const BitBuffer* +Function,+,bit_buffer_get_size_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_has_partial_byte,_Bool,const BitBuffer* +Function,+,bit_buffer_reset,void,BitBuffer* +Function,+,bit_buffer_set_byte,void,"BitBuffer*, size_t, uint8_t" +Function,+,bit_buffer_set_byte_with_parity,void,"BitBuffer*, size_t, uint8_t, _Bool" +Function,+,bit_buffer_set_size,void,"BitBuffer*, size_t" +Function,+,bit_buffer_set_size_bytes,void,"BitBuffer*, size_t" +Function,+,bit_buffer_starts_with_byte,_Bool,"const BitBuffer*, uint8_t" +Function,+,bit_buffer_write_bytes,void,"const BitBuffer*, void*, size_t" +Function,+,bit_buffer_write_bytes_mid,void,"const BitBuffer*, void*, size_t, size_t" +Function,+,bit_buffer_write_bytes_with_parity,void,"const BitBuffer*, void*, size_t, size_t*" Function,+,bit_lib_add_parity,size_t,"const uint8_t*, size_t, uint8_t*, size_t, uint8_t, uint8_t, BitLibParity" Function,+,bit_lib_copy_bits,void,"uint8_t*, size_t, size_t, const uint8_t*, size_t" Function,+,bit_lib_crc16,uint16_t,"const uint8_t*, size_t, uint16_t, uint16_t, _Bool, _Bool, uint16_t" @@ -711,14 +775,6 @@ Function,-,coshl,long double,long double Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" -Function,-,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" -Function,-,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" -Function,-,crypto1_decrypt,void,"Crypto1*, uint8_t*, uint16_t, uint8_t*" -Function,-,crypto1_encrypt,void,"Crypto1*, uint8_t*, uint8_t*, uint16_t, uint8_t*, uint8_t*" -Function,-,crypto1_filter,uint32_t,uint32_t -Function,-,crypto1_init,void,"Crypto1*, uint64_t" -Function,-,crypto1_reset,void,Crypto1* -Function,-,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* Function,-,ctime,char*,const time_t* Function,-,ctime_r,char*,"const time_t*, char*" @@ -748,24 +804,19 @@ Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, u Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" Function,-,difftime,double,"time_t, time_t" -Function,-,digital_sequence_add,void,"DigitalSequence*, uint8_t" +Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* Function,-,digital_sequence_free,void,DigitalSequence* -Function,-,digital_sequence_send,_Bool,DigitalSequence* -Function,-,digital_sequence_set_sendtime,void,"DigitalSequence*, uint32_t" -Function,-,digital_sequence_set_signal,void,"DigitalSequence*, uint8_t, DigitalSignal*" -Function,-,digital_sequence_timebase_correction,void,"DigitalSequence*, float" -Function,-,digital_signal_add,void,"DigitalSignal*, uint32_t" -Function,-,digital_signal_add_pulse,void,"DigitalSignal*, uint32_t, _Bool" +Function,+,digital_sequence_register_signal,void,"DigitalSequence*, uint8_t, const DigitalSignal*" +Function,+,digital_sequence_transmit,void,DigitalSequence* +Function,+,digital_signal_add_period,void,"DigitalSignal*, uint32_t" +Function,+,digital_signal_add_period_with_level,void,"DigitalSignal*, uint32_t, _Bool" Function,-,digital_signal_alloc,DigitalSignal*,uint32_t -Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" Function,-,digital_signal_free,void,DigitalSignal* -Function,-,digital_signal_get_edge,uint32_t,"DigitalSignal*, uint32_t" -Function,-,digital_signal_get_edges_cnt,uint32_t,DigitalSignal* -Function,-,digital_signal_get_start_level,_Bool,DigitalSignal* -Function,-,digital_signal_prepare_arr,void,DigitalSignal* -Function,-,digital_signal_send,void,"DigitalSignal*, const GpioPin*" +Function,+,digital_signal_get_size,uint32_t,const DigitalSignal* +Function,+,digital_signal_get_start_level,_Bool,const DigitalSignal* +Function,+,digital_signal_set_start_level,void,"DigitalSignal*, _Bool" Function,-,diprintf,int,"int, const char*, ..." Function,+,dir_walk_alloc,DirWalk*,Storage* Function,+,dir_walk_close,void,DirWalk* @@ -814,8 +865,6 @@ Function,+,elf_symbolname_hash,uint32_t,const char* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* -Function,-,emv_card_emulation,_Bool,FuriHalNfcTxRxContext* -Function,-,emv_read_bank_card,_Bool,"FuriHalNfcTxRxContext*, EmvApplication*" Function,-,erand48,double,unsigned short[3] Function,-,erf,double,double Function,-,erfc,double,double @@ -1234,38 +1283,44 @@ Function,-,furi_hal_mpu_init,void, Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" -Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" -Function,-,furi_hal_nfc_deinit,void, -Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" -Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" -Function,+,furi_hal_nfc_exit_sleep,void, -Function,+,furi_hal_nfc_field_detect_start,void, +Function,+,furi_hal_nfc_abort,FuriHalNfcError, +Function,+,furi_hal_nfc_acquire,FuriHalNfcError, +Function,+,furi_hal_nfc_event_start,FuriHalNfcError, +Function,+,furi_hal_nfc_event_stop,FuriHalNfcError, +Function,+,furi_hal_nfc_field_detect_start,FuriHalNfcError, +Function,+,furi_hal_nfc_field_detect_stop,FuriHalNfcError, Function,+,furi_hal_nfc_field_is_present,_Bool, -Function,+,furi_hal_nfc_field_off,void, -Function,+,furi_hal_nfc_field_on,void, -Function,-,furi_hal_nfc_init,void, -Function,+,furi_hal_nfc_is_busy,_Bool, -Function,+,furi_hal_nfc_is_init,_Bool, -Function,+,furi_hal_nfc_listen,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, _Bool, uint32_t" -Function,+,furi_hal_nfc_listen_rx,_Bool,"FuriHalNfcTxRxContext*, uint32_t" -Function,+,furi_hal_nfc_listen_sleep,void, -Function,+,furi_hal_nfc_listen_start,void,FuriHalNfcDevData* -Function,+,furi_hal_nfc_ll_poll,void, -Function,+,furi_hal_nfc_ll_set_error_handling,void,FuriHalNfcErrorHandling -Function,+,furi_hal_nfc_ll_set_fdt_listen,void,uint32_t -Function,+,furi_hal_nfc_ll_set_fdt_poll,void,uint32_t -Function,+,furi_hal_nfc_ll_set_guard_time,void,uint32_t -Function,+,furi_hal_nfc_ll_set_mode,FuriHalNfcReturn,"FuriHalNfcMode, FuriHalNfcBitrate, FuriHalNfcBitrate" -Function,+,furi_hal_nfc_ll_txrx,FuriHalNfcReturn,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,+,furi_hal_nfc_ll_txrx_bits,FuriHalNfcReturn,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,+,furi_hal_nfc_ll_txrx_off,void, -Function,+,furi_hal_nfc_ll_txrx_on,void, -Function,+,furi_hal_nfc_sleep,void, -Function,+,furi_hal_nfc_start_sleep,void, -Function,+,furi_hal_nfc_stop,void, -Function,+,furi_hal_nfc_stop_cmd,void, -Function,+,furi_hal_nfc_tx_rx,_Bool,"FuriHalNfcTxRxContext*, uint16_t" -Function,+,furi_hal_nfc_tx_rx_full,_Bool,FuriHalNfcTxRxContext* +Function,+,furi_hal_nfc_init,FuriHalNfcError, +Function,+,furi_hal_nfc_is_hal_ready,FuriHalNfcError, +Function,+,furi_hal_nfc_iso14443a_listener_set_col_res_data,FuriHalNfcError,"uint8_t*, uint8_t, uint8_t*, uint8_t" +Function,+,furi_hal_nfc_iso14443a_listener_tx_custom_parity,FuriHalNfcError,"const uint8_t*, const uint8_t*, size_t" +Function,+,furi_hal_nfc_iso14443a_poller_trx_short_frame,FuriHalNfcError,FuriHalNfcaShortFrame +Function,+,furi_hal_nfc_iso14443a_poller_tx_custom_parity,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_iso14443a_rx_sdd_frame,FuriHalNfcError,"uint8_t*, size_t, size_t*" +Function,+,furi_hal_nfc_iso14443a_tx_sdd_frame,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_iso15693_listener_tx_sof,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_enable_rx,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_idle,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_rx,FuriHalNfcError,"uint8_t*, size_t, size_t*" +Function,+,furi_hal_nfc_listener_sleep,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_tx,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_listener_wait_event,FuriHalNfcEvent,uint32_t +Function,+,furi_hal_nfc_low_power_mode_start,FuriHalNfcError, +Function,+,furi_hal_nfc_low_power_mode_stop,FuriHalNfcError, +Function,+,furi_hal_nfc_poller_field_on,FuriHalNfcError, +Function,+,furi_hal_nfc_poller_rx,FuriHalNfcError,"uint8_t*, size_t, size_t*" +Function,+,furi_hal_nfc_poller_tx,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_poller_wait_event,FuriHalNfcEvent,uint32_t +Function,+,furi_hal_nfc_release,FuriHalNfcError, +Function,+,furi_hal_nfc_reset_mode,FuriHalNfcError, +Function,+,furi_hal_nfc_set_mode,FuriHalNfcError,"FuriHalNfcMode, FuriHalNfcTech" +Function,+,furi_hal_nfc_timer_block_tx_is_running,_Bool, +Function,+,furi_hal_nfc_timer_block_tx_start,void,uint32_t +Function,+,furi_hal_nfc_timer_block_tx_start_us,void,uint32_t +Function,+,furi_hal_nfc_timer_block_tx_stop,void, +Function,+,furi_hal_nfc_timer_fwt_start,void,uint32_t +Function,+,furi_hal_nfc_timer_fwt_stop,void, +Function,+,furi_hal_nfc_trx_reset,FuriHalNfcError, Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, Function,+,furi_hal_power_check_otg_fault,_Bool, @@ -1791,6 +1846,97 @@ Function,-,islower,int,int Function,-,islower_l,int,"int, locale_t" Function,-,isnan,int,double Function,-,isnanf,int,float +Function,+,iso13239_crc_append,void,"Iso13239CrcType, BitBuffer*" +Function,+,iso13239_crc_check,_Bool,"Iso13239CrcType, const BitBuffer*" +Function,+,iso13239_crc_trim,void,BitBuffer* +Function,+,iso14443_3a_alloc,Iso14443_3aData*, +Function,+,iso14443_3a_copy,void,"Iso14443_3aData*, const Iso14443_3aData*" +Function,+,iso14443_3a_free,void,Iso14443_3aData* +Function,+,iso14443_3a_get_atqa,void,"const Iso14443_3aData*, uint8_t[2]" +Function,+,iso14443_3a_get_base_data,Iso14443_3aData*,const Iso14443_3aData* +Function,+,iso14443_3a_get_cuid,uint32_t,const Iso14443_3aData* +Function,+,iso14443_3a_get_device_name,const char*,"const Iso14443_3aData*, NfcDeviceNameType" +Function,+,iso14443_3a_get_sak,uint8_t,const Iso14443_3aData* +Function,+,iso14443_3a_get_uid,const uint8_t*,"const Iso14443_3aData*, size_t*" +Function,+,iso14443_3a_is_equal,_Bool,"const Iso14443_3aData*, const Iso14443_3aData*" +Function,+,iso14443_3a_load,_Bool,"Iso14443_3aData*, FlipperFormat*, uint32_t" +Function,+,iso14443_3a_poller_read,Iso14443_3aError,"Nfc*, Iso14443_3aData*" +Function,+,iso14443_3a_poller_send_standard_frame,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_poller_txrx,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_reset,void,Iso14443_3aData* +Function,+,iso14443_3a_save,_Bool,"const Iso14443_3aData*, FlipperFormat*" +Function,+,iso14443_3a_set_atqa,void,"Iso14443_3aData*, const uint8_t[2]" +Function,+,iso14443_3a_set_sak,void,"Iso14443_3aData*, uint8_t" +Function,+,iso14443_3a_set_uid,_Bool,"Iso14443_3aData*, const uint8_t*, size_t" +Function,+,iso14443_3a_supports_iso14443_4,_Bool,const Iso14443_3aData* +Function,+,iso14443_3a_verify,_Bool,"Iso14443_3aData*, const FuriString*" +Function,+,iso14443_3b_alloc,Iso14443_3bData*, +Function,+,iso14443_3b_copy,void,"Iso14443_3bData*, const Iso14443_3bData*" +Function,+,iso14443_3b_free,void,Iso14443_3bData* +Function,+,iso14443_3b_get_application_data,const uint8_t*,"const Iso14443_3bData*, size_t*" +Function,+,iso14443_3b_get_base_data,Iso14443_3bData*,const Iso14443_3bData* +Function,+,iso14443_3b_get_device_name,const char*,"const Iso14443_3bData*, NfcDeviceNameType" +Function,+,iso14443_3b_get_frame_size_max,uint16_t,const Iso14443_3bData* +Function,+,iso14443_3b_get_fwt_fc_max,uint32_t,const Iso14443_3bData* +Function,+,iso14443_3b_get_uid,const uint8_t*,"const Iso14443_3bData*, size_t*" +Function,+,iso14443_3b_is_equal,_Bool,"const Iso14443_3bData*, const Iso14443_3bData*" +Function,+,iso14443_3b_load,_Bool,"Iso14443_3bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_3b_reset,void,Iso14443_3bData* +Function,+,iso14443_3b_save,_Bool,"const Iso14443_3bData*, FlipperFormat*" +Function,+,iso14443_3b_set_uid,_Bool,"Iso14443_3bData*, const uint8_t*, size_t" +Function,+,iso14443_3b_supports_bit_rate,_Bool,"const Iso14443_3bData*, Iso14443_3bBitRate" +Function,+,iso14443_3b_supports_frame_option,_Bool,"const Iso14443_3bData*, Iso14443_3bFrameOption" +Function,+,iso14443_3b_supports_iso14443_4,_Bool,const Iso14443_3bData* +Function,+,iso14443_3b_verify,_Bool,"Iso14443_3bData*, const FuriString*" +Function,+,iso14443_4a_alloc,Iso14443_4aData*, +Function,+,iso14443_4a_copy,void,"Iso14443_4aData*, const Iso14443_4aData*" +Function,+,iso14443_4a_free,void,Iso14443_4aData* +Function,+,iso14443_4a_get_base_data,Iso14443_3aData*,const Iso14443_4aData* +Function,+,iso14443_4a_get_device_name,const char*,"const Iso14443_4aData*, NfcDeviceNameType" +Function,+,iso14443_4a_get_frame_size_max,uint16_t,const Iso14443_4aData* +Function,+,iso14443_4a_get_fwt_fc_max,uint32_t,const Iso14443_4aData* +Function,+,iso14443_4a_get_historical_bytes,const uint8_t*,"const Iso14443_4aData*, uint32_t*" +Function,+,iso14443_4a_get_uid,const uint8_t*,"const Iso14443_4aData*, size_t*" +Function,+,iso14443_4a_is_equal,_Bool,"const Iso14443_4aData*, const Iso14443_4aData*" +Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4a_reset,void,Iso14443_4aData* +Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" +Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" +Function,+,iso14443_4a_supports_bit_rate,_Bool,"const Iso14443_4aData*, Iso14443_4aBitRate" +Function,+,iso14443_4a_supports_frame_option,_Bool,"const Iso14443_4aData*, Iso14443_4aFrameOption" +Function,+,iso14443_4a_verify,_Bool,"Iso14443_4aData*, const FuriString*" +Function,+,iso14443_4b_alloc,Iso14443_4bData*, +Function,+,iso14443_4b_copy,void,"Iso14443_4bData*, const Iso14443_4bData*" +Function,+,iso14443_4b_free,void,Iso14443_4bData* +Function,+,iso14443_4b_get_base_data,Iso14443_3bData*,const Iso14443_4bData* +Function,+,iso14443_4b_get_device_name,const char*,"const Iso14443_4bData*, NfcDeviceNameType" +Function,+,iso14443_4b_get_uid,const uint8_t*,"const Iso14443_4bData*, size_t*" +Function,+,iso14443_4b_is_equal,_Bool,"const Iso14443_4bData*, const Iso14443_4bData*" +Function,+,iso14443_4b_load,_Bool,"Iso14443_4bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4b_reset,void,Iso14443_4bData* +Function,+,iso14443_4b_save,_Bool,"const Iso14443_4bData*, FlipperFormat*" +Function,+,iso14443_4b_set_uid,_Bool,"Iso14443_4bData*, const uint8_t*, size_t" +Function,+,iso14443_4b_verify,_Bool,"Iso14443_4bData*, const FuriString*" +Function,+,iso14443_crc_append,void,"Iso14443CrcType, BitBuffer*" +Function,+,iso14443_crc_check,_Bool,"Iso14443CrcType, const BitBuffer*" +Function,+,iso14443_crc_trim,void,BitBuffer* +Function,+,iso15693_3_alloc,Iso15693_3Data*, +Function,+,iso15693_3_copy,void,"Iso15693_3Data*, const Iso15693_3Data*" +Function,+,iso15693_3_free,void,Iso15693_3Data* +Function,+,iso15693_3_get_base_data,Iso15693_3Data*,const Iso15693_3Data* +Function,+,iso15693_3_get_block_count,uint16_t,const Iso15693_3Data* +Function,+,iso15693_3_get_block_data,const uint8_t*,"const Iso15693_3Data*, uint8_t" +Function,+,iso15693_3_get_block_size,uint8_t,const Iso15693_3Data* +Function,+,iso15693_3_get_device_name,const char*,"const Iso15693_3Data*, NfcDeviceNameType" +Function,+,iso15693_3_get_manufacturer_id,uint8_t,const Iso15693_3Data* +Function,+,iso15693_3_get_uid,const uint8_t*,"const Iso15693_3Data*, size_t*" +Function,+,iso15693_3_is_block_locked,_Bool,"const Iso15693_3Data*, uint8_t" +Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Data*" +Function,+,iso15693_3_load,_Bool,"Iso15693_3Data*, FlipperFormat*, uint32_t" +Function,+,iso15693_3_reset,void,Iso15693_3Data* +Function,+,iso15693_3_save,_Bool,"const Iso15693_3Data*, FlipperFormat*" +Function,+,iso15693_3_set_uid,_Bool,"Iso15693_3Data*, const uint8_t*, size_t" +Function,+,iso15693_3_verify,_Bool,"Iso15693_3Data*, const FuriString*" Function,-,isprint,int,int Function,-,isprint_l,int,"int, locale_t" Function,-,ispunct,int,int @@ -1926,7 +2072,7 @@ Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" Function,-,mbedtls_platform_zeroize,void,"void*, size_t" -Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" +Function,+,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* @@ -1968,120 +2114,93 @@ Function,+,menu_free,void,Menu* Function,+,menu_get_view,View*,Menu* Function,+,menu_reset,void,Menu* Function,+,menu_set_selected_item,void,"Menu*, uint32_t" -Function,-,mf_classic_auth_attempt,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, MfClassicAuthContext*, uint64_t" -Function,-,mf_classic_auth_init_context,void,"MfClassicAuthContext*, uint8_t" -Function,-,mf_classic_auth_write_block,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t" -Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey" -Function,-,mf_classic_authenticate_skip_activate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey, _Bool, uint32_t" -Function,-,mf_classic_block_to_value,_Bool,"const uint8_t*, int32_t*, uint8_t*" -Function,-,mf_classic_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,+,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" -Function,-,mf_classic_dict_add_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,+,mf_classic_dict_alloc,MfClassicDict*,MfClassicDictType -Function,+,mf_classic_dict_check_presence,_Bool,MfClassicDictType -Function,+,mf_classic_dict_delete_index,_Bool,"MfClassicDict*, uint32_t" -Function,-,mf_classic_dict_find_index,_Bool,"MfClassicDict*, uint8_t*, uint32_t*" -Function,-,mf_classic_dict_find_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t*" -Function,+,mf_classic_dict_free,void,MfClassicDict* -Function,-,mf_classic_dict_get_key_at_index,_Bool,"MfClassicDict*, uint64_t*, uint32_t" -Function,+,mf_classic_dict_get_key_at_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t" -Function,-,mf_classic_dict_get_next_key,_Bool,"MfClassicDict*, uint64_t*" -Function,+,mf_classic_dict_get_next_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,+,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* -Function,+,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" -Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*" -Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict* -Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*, _Bool" -Function,-,mf_classic_get_classic_type,MfClassicType,"uint8_t, uint8_t, uint8_t" -Function,+,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" +Function,+,mf_classic_alloc,MfClassicData*, +Function,+,mf_classic_block_to_value,_Bool,"const MfClassicBlock*, int32_t*, uint8_t*" +Function,+,mf_classic_copy,void,"MfClassicData*, const MfClassicData*" +Function,+,mf_classic_free,void,MfClassicData* +Function,+,mf_classic_get_base_data,Iso14443_3aData*,const MfClassicData* +Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t +Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType" +Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t +Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*" Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t -Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t -Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" -Function,-,mf_classic_get_total_block_num,uint16_t,MfClassicType +Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t" +Function,+,mf_classic_get_sector_trailer_num_by_block,uint8_t,uint8_t +Function,+,mf_classic_get_sector_trailer_num_by_sector,uint8_t,uint8_t +Function,+,mf_classic_get_total_block_num,uint16_t,MfClassicType Function,+,mf_classic_get_total_sectors_num,uint8_t,MfClassicType -Function,-,mf_classic_get_type_str,const char*,MfClassicType -Function,-,mf_classic_halt,void,"FuriHalNfcTxRxContext*, Crypto1*" -Function,-,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" -Function,-,mf_classic_is_allowed_access_sector_trailer,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" -Function,+,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" -Function,+,mf_classic_is_card_read,_Bool,MfClassicData* -Function,+,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" -Function,-,mf_classic_is_sector_data_read,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_is_sector_read,_Bool,"MfClassicData*, uint8_t" +Function,+,mf_classic_get_uid,const uint8_t*,"const MfClassicData*, size_t*" +Function,+,mf_classic_is_allowed_access,_Bool,"MfClassicData*, uint8_t, MfClassicKeyType, MfClassicAction" +Function,+,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicSectorTrailer*, uint8_t, MfClassicKeyType, MfClassicAction" +Function,+,mf_classic_is_block_read,_Bool,"const MfClassicData*, uint8_t" +Function,+,mf_classic_is_card_read,_Bool,const MfClassicData* +Function,+,mf_classic_is_equal,_Bool,"const MfClassicData*, const MfClassicData*" +Function,+,mf_classic_is_key_found,_Bool,"const MfClassicData*, uint8_t, MfClassicKeyType" +Function,+,mf_classic_is_sector_read,_Bool,"const MfClassicData*, uint8_t" Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t -Function,-,mf_classic_is_value_block,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_read_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" -Function,-,mf_classic_read_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicReader*, MfClassicData*" -Function,-,mf_classic_read_sector,void,"FuriHalNfcTxRxContext*, MfClassicData*, uint8_t" -Function,-,mf_classic_reader_add_sector,void,"MfClassicReader*, uint8_t, uint64_t, uint64_t" -Function,-,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlock*" -Function,-,mf_classic_set_key_found,void,"MfClassicData*, uint8_t, MfClassicKey, uint64_t" -Function,-,mf_classic_set_key_not_found,void,"MfClassicData*, uint8_t, MfClassicKey" -Function,-,mf_classic_set_sector_data_not_read,void,MfClassicData* -Function,-,mf_classic_transfer,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t" -Function,-,mf_classic_update_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicData*" -Function,-,mf_classic_value_cmd,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, uint8_t, int32_t" -Function,-,mf_classic_value_cmd_full,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t, int32_t" -Function,-,mf_classic_value_to_block,void,"int32_t, uint8_t, uint8_t*" -Function,-,mf_classic_write_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" -Function,-,mf_classic_write_sector,_Bool,"FuriHalNfcTxRxContext*, MfClassicData*, MfClassicData*, uint8_t" -Function,-,mf_df_cat_application,void,"MifareDesfireApplication*, FuriString*" -Function,+,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" -Function,+,mf_df_cat_card_info,void,"MifareDesfireData*, FuriString*" -Function,-,mf_df_cat_data,void,"MifareDesfireData*, FuriString*" -Function,+,mf_df_cat_file,void,"MifareDesfireFile*, FuriString*" -Function,-,mf_df_cat_free_mem,void,"MifareDesfireFreeMemory*, FuriString*" -Function,-,mf_df_cat_key_settings,void,"MifareDesfireKeySettings*, FuriString*" -Function,-,mf_df_cat_version,void,"MifareDesfireVersion*, FuriString*" -Function,-,mf_df_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,-,mf_df_clear,void,MifareDesfireData* -Function,-,mf_df_get_application,MifareDesfireApplication*,"MifareDesfireData*, const uint8_t[3]*" -Function,-,mf_df_get_file,MifareDesfireFile*,"MifareDesfireApplication*, uint8_t" -Function,-,mf_df_parse_get_application_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireApplication**" -Function,-,mf_df_parse_get_file_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile**" -Function,-,mf_df_parse_get_file_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*" -Function,-,mf_df_parse_get_free_memory_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFreeMemory*" -Function,-,mf_df_parse_get_key_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireKeySettings*" -Function,-,mf_df_parse_get_key_version_response,_Bool,"uint8_t*, uint16_t, MifareDesfireKeyVersion*" -Function,-,mf_df_parse_get_version_response,_Bool,"uint8_t*, uint16_t, MifareDesfireVersion*" -Function,-,mf_df_parse_read_data_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*" -Function,-,mf_df_parse_select_application_response,_Bool,"uint8_t*, uint16_t" -Function,-,mf_df_prepare_get_application_ids,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_file_ids,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_file_settings,uint16_t,"uint8_t*, uint8_t" -Function,-,mf_df_prepare_get_free_memory,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_key_settings,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_key_version,uint16_t,"uint8_t*, uint8_t" -Function,-,mf_df_prepare_get_value,uint16_t,"uint8_t*, uint8_t" -Function,-,mf_df_prepare_get_version,uint16_t,uint8_t* -Function,-,mf_df_prepare_read_data,uint16_t,"uint8_t*, uint8_t, uint32_t, uint32_t" -Function,-,mf_df_prepare_read_records,uint16_t,"uint8_t*, uint8_t, uint32_t, uint32_t" -Function,-,mf_df_prepare_select_application,uint16_t,"uint8_t*, uint8_t[3]" -Function,-,mf_df_read_card,_Bool,"FuriHalNfcTxRxContext*, MifareDesfireData*" -Function,-,mf_ul_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,+,mf_ul_emulation_supported,_Bool,MfUltralightData* -Function,+,mf_ul_is_full_capture,_Bool,MfUltralightData* -Function,-,mf_ul_prepare_emulation,void,"MfUltralightEmulator*, MfUltralightData*" -Function,-,mf_ul_prepare_emulation_response,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*, uint32_t*, void*" -Function,-,mf_ul_pwdgen_amiibo,uint32_t,FuriHalNfcDevData* -Function,-,mf_ul_pwdgen_xiaomi,uint32_t,FuriHalNfcDevData* -Function,-,mf_ul_read_card,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mf_ul_reset,void,MfUltralightData* -Function,-,mf_ul_reset_emulation,void,"MfUltralightEmulator*, _Bool" -Function,-,mf_ultralight_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint32_t, uint16_t*" -Function,-,mf_ultralight_fast_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,+,mf_ultralight_get_config_pages,MfUltralightConfigPages*,MfUltralightData* -Function,-,mf_ultralight_read_counters,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" -Function,-,mf_ultralight_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mf_ultralight_read_pages_direct,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint8_t*" -Function,-,mf_ultralight_read_signature,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" -Function,-,mf_ultralight_read_tearing_flags,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" -Function,-,mf_ultralight_read_version,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mfkey32_alloc,Mfkey32*,uint32_t -Function,-,mfkey32_free,void,Mfkey32* -Function,+,mfkey32_get_auth_sectors,uint16_t,FuriString* -Function,-,mfkey32_process_data,void,"Mfkey32*, uint8_t*, uint16_t, _Bool, _Bool" -Function,-,mfkey32_set_callback,void,"Mfkey32*, Mfkey32ParseDataCallback, void*" +Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" +Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" +Function,+,mf_classic_poller_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" +Function,+,mf_classic_poller_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_detect_type,MfClassicError,"Nfc*, MfClassicType*" +Function,+,mf_classic_poller_read,MfClassicError,"Nfc*, const MfClassicDeviceKeys*, MfClassicData*" +Function,+,mf_classic_poller_read_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_read_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t*" +Function,+,mf_classic_poller_write_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_reset,void,MfClassicData* +Function,+,mf_classic_save,_Bool,"const MfClassicData*, FlipperFormat*" +Function,+,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_set_key_found,void,"MfClassicData*, uint8_t, MfClassicKeyType, uint64_t" +Function,+,mf_classic_set_key_not_found,void,"MfClassicData*, uint8_t, MfClassicKeyType" +Function,+,mf_classic_set_uid,_Bool,"MfClassicData*, const uint8_t*, size_t" +Function,+,mf_classic_value_to_block,void,"int32_t, uint8_t, MfClassicBlock*" +Function,+,mf_classic_verify,_Bool,"MfClassicData*, const FuriString*" +Function,+,mf_desfire_alloc,MfDesfireData*, +Function,+,mf_desfire_copy,void,"MfDesfireData*, const MfDesfireData*" +Function,+,mf_desfire_free,void,MfDesfireData* +Function,+,mf_desfire_get_application,const MfDesfireApplication*,"const MfDesfireData*, const MfDesfireApplicationId*" +Function,+,mf_desfire_get_base_data,Iso14443_4aData*,const MfDesfireData* +Function,+,mf_desfire_get_device_name,const char*,"const MfDesfireData*, NfcDeviceNameType" +Function,+,mf_desfire_get_file_data,const MfDesfireFileData*,"const MfDesfireApplication*, const MfDesfireFileId*" +Function,+,mf_desfire_get_file_settings,const MfDesfireFileSettings*,"const MfDesfireApplication*, const MfDesfireFileId*" +Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*" +Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*" +Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t" +Function,+,mf_desfire_reset,void,MfDesfireData* +Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" +Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" +Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" +Function,+,mf_ultralight_alloc,MfUltralightData*, +Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*" +Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData* +Function,+,mf_ultralight_free,void,MfUltralightData* +Function,+,mf_ultralight_get_base_data,Iso14443_3aData*,const MfUltralightData* +Function,+,mf_ultralight_get_config_page,_Bool,"const MfUltralightData*, MfUltralightConfigPages**" +Function,+,mf_ultralight_get_config_page_num,uint16_t,MfUltralightType +Function,+,mf_ultralight_get_device_name,const char*,"const MfUltralightData*, NfcDeviceNameType" +Function,+,mf_ultralight_get_feature_support_set,uint32_t,MfUltralightType +Function,+,mf_ultralight_get_pages_total,uint16_t,MfUltralightType +Function,+,mf_ultralight_get_pwd_page_num,uint8_t,MfUltralightType +Function,+,mf_ultralight_get_type_by_version,MfUltralightType,MfUltralightVersion* +Function,+,mf_ultralight_get_uid,const uint8_t*,"const MfUltralightData*, size_t*" +Function,+,mf_ultralight_is_all_data_read,_Bool,const MfUltralightData* +Function,+,mf_ultralight_is_counter_configured,_Bool,const MfUltralightData* +Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltralightData*" +Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" +Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" +Function,+,mf_ultralight_poller_read_card,MfUltralightError,"Nfc*, MfUltralightData*" +Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" +Function,+,mf_ultralight_poller_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" +Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"Nfc*, uint8_t, MfUltralightTearingFlag*" +Function,+,mf_ultralight_poller_read_version,MfUltralightError,"Nfc*, MfUltralightVersion*" +Function,+,mf_ultralight_poller_write_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_reset,void,MfUltralightData* +Function,+,mf_ultralight_save,_Bool,"const MfUltralightData*, FlipperFormat*" +Function,+,mf_ultralight_set_uid,_Bool,"MfUltralightData*, const uint8_t*, size_t" +Function,+,mf_ultralight_support_feature,_Bool,"const uint32_t, const uint32_t" +Function,+,mf_ultralight_verify,_Bool,"MfUltralightData*, const FuriString*" Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" @@ -2120,52 +2239,76 @@ Function,-,nextafterl,long double,"long double, long double" Function,-,nexttoward,double,"double, long double" Function,-,nexttowardf,float,"float, long double" Function,-,nexttowardl,long double,"long double, long double" +Function,+,nfc_alloc,Nfc*, +Function,+,nfc_config,void,"Nfc*, NfcMode, NfcTech" +Function,+,nfc_data_generator_fill_data,void,"NfcDataGeneratorType, NfcDevice*" +Function,+,nfc_data_generator_get_name,const char*,NfcDataGeneratorType Function,+,nfc_device_alloc,NfcDevice*, Function,+,nfc_device_clear,void,NfcDevice* -Function,+,nfc_device_data_clear,void,NfcDeviceData* -Function,+,nfc_device_delete,_Bool,"NfcDevice*, _Bool" +Function,+,nfc_device_copy_data,void,"const NfcDevice*, NfcProtocol, NfcDeviceData*" Function,+,nfc_device_free,void,NfcDevice* -Function,+,nfc_device_load,_Bool,"NfcDevice*, const char*, _Bool" -Function,+,nfc_device_load_key_cache,_Bool,NfcDevice* -Function,+,nfc_device_restore,_Bool,"NfcDevice*, _Bool" +Function,+,nfc_device_get_data,const NfcDeviceData*,"const NfcDevice*, NfcProtocol" +Function,+,nfc_device_get_name,const char*,"const NfcDevice*, NfcDeviceNameType" +Function,+,nfc_device_get_protocol,NfcProtocol,const NfcDevice* +Function,+,nfc_device_get_protocol_name,const char*,NfcProtocol +Function,+,nfc_device_get_uid,const uint8_t*,"const NfcDevice*, size_t*" +Function,+,nfc_device_is_equal,_Bool,"const NfcDevice*, const NfcDevice*" +Function,+,nfc_device_is_equal_data,_Bool,"const NfcDevice*, NfcProtocol, const NfcDeviceData*" +Function,+,nfc_device_load,_Bool,"NfcDevice*, const char*" +Function,+,nfc_device_reset,void,NfcDevice* Function,+,nfc_device_save,_Bool,"NfcDevice*, const char*" -Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" +Function,+,nfc_device_set_data,void,"NfcDevice*, NfcProtocol, const NfcDeviceData*" Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" -Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" -Function,+,nfc_file_select,_Bool,NfcDevice* -Function,-,nfc_generate_mf_classic,void,"NfcDeviceData*, uint8_t, MfClassicType" -Function,+,nfc_get_dev_type,const char*,FuriHalNfcType -Function,-,nfc_guess_protocol,const char*,NfcProtocol -Function,+,nfc_mf_classic_type,const char*,MfClassicType -Function,+,nfc_mf_ul_type,const char*,"MfUltralightType, _Bool" -Function,+,nfc_supported_card_verify_and_parse,_Bool,NfcDeviceData* +Function,+,nfc_device_set_uid,_Bool,"NfcDevice*, const uint8_t*, size_t" +Function,+,nfc_dict_add_key,_Bool,"NfcDict*, const uint8_t*, size_t" +Function,+,nfc_dict_alloc,NfcDict*,"const char*, NfcDictMode, size_t" +Function,+,nfc_dict_check_presence,_Bool,const char* +Function,+,nfc_dict_delete_key,_Bool,"NfcDict*, const uint8_t*, size_t" +Function,+,nfc_dict_free,void,NfcDict* +Function,+,nfc_dict_get_next_key,_Bool,"NfcDict*, uint8_t*, size_t" +Function,+,nfc_dict_get_total_keys,uint32_t,NfcDict* +Function,+,nfc_dict_is_key_present,_Bool,"NfcDict*, const uint8_t*, size_t" +Function,+,nfc_dict_rewind,_Bool,NfcDict* +Function,+,nfc_free,void,Nfc* +Function,+,nfc_iso14443a_listener_set_col_res_data,NfcError,"Nfc*, uint8_t*, uint8_t, uint8_t*, uint8_t" +Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuffer*" +Function,+,nfc_iso14443a_poller_trx_custom_parity,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,nfc_iso14443a_poller_trx_sdd_frame,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,nfc_iso14443a_poller_trx_short_frame,NfcError,"Nfc*, NfcIso14443aShortFrame, BitBuffer*, uint32_t" +Function,+,nfc_iso15693_listener_tx_sof,NfcError,Nfc* +Function,+,nfc_listener_alloc,NfcListener*,"Nfc*, NfcProtocol, const NfcDeviceData*" +Function,+,nfc_listener_free,void,NfcListener* +Function,+,nfc_listener_get_data,const NfcDeviceData*,"const NfcListener*, NfcProtocol" +Function,+,nfc_listener_get_protocol,NfcProtocol,const NfcListener* +Function,+,nfc_listener_start,void,"NfcListener*, NfcGenericCallback, void*" +Function,+,nfc_listener_stop,void,NfcListener* +Function,+,nfc_listener_tx,NfcError,"Nfc*, const BitBuffer*" +Function,+,nfc_poller_alloc,NfcPoller*,"Nfc*, NfcProtocol" +Function,+,nfc_poller_detect,_Bool,NfcPoller* +Function,+,nfc_poller_free,void,NfcPoller* +Function,+,nfc_poller_get_data,const NfcDeviceData*,const NfcPoller* +Function,+,nfc_poller_get_protocol,NfcProtocol,const NfcPoller* +Function,+,nfc_poller_start,void,"NfcPoller*, NfcGenericCallback, void*" +Function,+,nfc_poller_stop,void,NfcPoller* +Function,+,nfc_poller_trx,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,nfc_protocol_get_parent,NfcProtocol,NfcProtocol +Function,+,nfc_protocol_has_parent,_Bool,"NfcProtocol, NfcProtocol" +Function,+,nfc_scanner_alloc,NfcScanner*,Nfc* +Function,+,nfc_scanner_free,void,NfcScanner* +Function,+,nfc_scanner_start,void,"NfcScanner*, NfcScannerCallback, void*" +Function,+,nfc_scanner_stop,void,NfcScanner* +Function,+,nfc_set_fdt_listen_fc,void,"Nfc*, uint32_t" +Function,+,nfc_set_fdt_poll_fc,void,"Nfc*, uint32_t" +Function,+,nfc_set_fdt_poll_poll_us,void,"Nfc*, uint32_t" +Function,+,nfc_set_guard_time_us,void,"Nfc*, uint32_t" +Function,+,nfc_set_mask_receive_time_fc,void,"Nfc*, uint32_t" +Function,+,nfc_start,void,"Nfc*, NfcEventCallback, void*" +Function,+,nfc_stop,void,Nfc* Function,+,nfc_util_bytes2num,uint64_t,"const uint8_t*, uint8_t" Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_num2bytes,void,"uint64_t, uint8_t, uint8_t*" Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t -Function,+,nfc_worker_alloc,NfcWorker*, -Function,+,nfc_worker_free,void,NfcWorker* -Function,+,nfc_worker_get_state,NfcWorkerState,NfcWorker* -Function,-,nfc_worker_nfcv_emulate,void,NfcWorker* -Function,-,nfc_worker_nfcv_sniff,void,NfcWorker* -Function,-,nfc_worker_nfcv_unlock,void,NfcWorker* -Function,+,nfc_worker_start,void,"NfcWorker*, NfcWorkerState, NfcDeviceData*, NfcWorkerCallback, void*" -Function,+,nfc_worker_stop,void,NfcWorker* -Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" -Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" -Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" -Function,-,nfca_signal_alloc,NfcaSignal*, -Function,-,nfca_signal_encode,void,"NfcaSignal*, uint8_t*, uint16_t, uint8_t*" -Function,-,nfca_signal_free,void,NfcaSignal* -Function,+,nfcv_emu_deinit,void,NfcVData* -Function,+,nfcv_emu_init,void,"FuriHalNfcDevData*, NfcVData*" -Function,+,nfcv_emu_loop,_Bool,"FuriHalNfcTxRxContext*, FuriHalNfcDevData*, NfcVData*, uint32_t" -Function,+,nfcv_emu_send,void,"FuriHalNfcTxRxContext*, NfcVData*, uint8_t*, uint8_t, NfcVSendFlags, uint32_t" -Function,-,nfcv_inventory,ReturnCode,uint8_t* -Function,-,nfcv_read_blocks,ReturnCode,"NfcVReader*, NfcVData*" -Function,-,nfcv_read_card,_Bool,"NfcVReader*, FuriHalNfcDevData*, NfcVData*" -Function,-,nfcv_read_sysinfo,ReturnCode,"FuriHalNfcDevData*, NfcVData*" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -2208,16 +2351,39 @@ Function,+,path_extract_dirname,void,"const char*, FuriString*" Function,+,path_extract_extension,void,"FuriString*, char*, size_t" Function,+,path_extract_filename,void,"FuriString*, FuriString*, _Bool" Function,+,path_extract_filename_no_ext,void,"const char*, FuriString*" +Function,+,pb_close_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_decode,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*" +Function,+,pb_decode_bool,_Bool,"pb_istream_t*, _Bool*" +Function,+,pb_decode_ex,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*, unsigned int" +Function,+,pb_decode_fixed32,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_fixed64,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_svarint,_Bool,"pb_istream_t*, int64_t*" +Function,+,pb_decode_tag,_Bool,"pb_istream_t*, pb_wire_type_t*, uint32_t*, _Bool*" +Function,+,pb_decode_varint,_Bool,"pb_istream_t*, uint64_t*" +Function,+,pb_decode_varint32,_Bool,"pb_istream_t*, uint32_t*" +Function,+,pb_default_field_callback,_Bool,"pb_istream_t*, pb_ostream_t*, const pb_field_t*" +Function,+,pb_encode,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_ex,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*, unsigned int" +Function,+,pb_encode_fixed32,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_fixed64,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_string,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" +Function,+,pb_encode_submessage,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_svarint,_Bool,"pb_ostream_t*, int64_t" +Function,+,pb_encode_tag,_Bool,"pb_ostream_t*, pb_wire_type_t, uint32_t" +Function,+,pb_encode_tag_for_field,_Bool,"pb_ostream_t*, const pb_field_iter_t*" +Function,+,pb_encode_varint,_Bool,"pb_ostream_t*, uint64_t" +Function,+,pb_get_encoded_size,_Bool,"size_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_istream_from_buffer,pb_istream_t,"const pb_byte_t*, size_t" +Function,+,pb_make_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_ostream_from_buffer,pb_ostream_t,"pb_byte_t*, size_t" +Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" +Function,+,pb_release,void,"const pb_msgdesc_t*, void*" +Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" +Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pcTaskGetName,char*,TaskHandle_t Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* -Function,-,platformDisableIrqCallback,void, -Function,-,platformEnableIrqCallback,void, -Function,-,platformProtectST25RComm,void, -Function,-,platformSetIrqCallback,void,PlatformIrqCallback -Function,-,platformSpiTxRx,_Bool,"const uint8_t*, uint8_t*, uint16_t" -Function,-,platformUnprotectST25RComm,void, Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" @@ -2252,7 +2418,6 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." -Function,-,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" @@ -2318,178 +2483,6 @@ Function,-,remquol,long double,"long double, long double, int*" Function,-,rename,int,"const char*, const char*" Function,-,renameat,int,"int, const char*, int, const char*" Function,-,rewind,void,FILE* -Function,-,rfalAdjustRegulators,ReturnCode,uint16_t* -Function,-,rfalCalibrate,ReturnCode, -Function,-,rfalDeinitialize,ReturnCode, -Function,-,rfalDisableObsvMode,void, -Function,-,rfalFeliCaPoll,ReturnCode,"rfalFeliCaPollSlots, uint16_t, uint8_t, rfalFeliCaPollRes*, uint8_t, uint8_t*, uint8_t*" -Function,-,rfalFieldOff,ReturnCode, -Function,+,rfalFieldOnAndStartGT,ReturnCode, -Function,-,rfalGetBitRate,ReturnCode,"rfalBitRate*, rfalBitRate*" -Function,-,rfalGetErrorHandling,rfalEHandling, -Function,-,rfalGetFDTListen,uint32_t, -Function,-,rfalGetFDTPoll,uint32_t, -Function,-,rfalGetGT,uint32_t, -Function,-,rfalGetMode,rfalMode, -Function,-,rfalGetObsvMode,void,"uint8_t*, uint8_t*" -Function,-,rfalGetTransceiveRSSI,ReturnCode,uint16_t* -Function,-,rfalGetTransceiveState,rfalTransceiveState, -Function,-,rfalGetTransceiveStatus,ReturnCode, -Function,-,rfalISO14443ATransceiveAnticollisionFrame,ReturnCode,"uint8_t*, uint8_t*, uint8_t*, uint16_t*, uint32_t" -Function,-,rfalISO14443ATransceiveShortFrame,ReturnCode,"rfal14443AShortFrameCmd, uint8_t*, uint8_t, uint16_t*, uint32_t" -Function,-,rfalISO15693TransceiveAnticollisionFrame,ReturnCode,"uint8_t*, uint8_t, uint8_t*, uint8_t, uint16_t*" -Function,-,rfalISO15693TransceiveEOF,ReturnCode,"uint8_t*, uint8_t, uint16_t*" -Function,-,rfalISO15693TransceiveEOFAnticollision,ReturnCode,"uint8_t*, uint8_t, uint16_t*" -Function,-,rfalInitialize,ReturnCode, -Function,-,rfalIsExtFieldOn,_Bool, -Function,-,rfalIsGTExpired,_Bool, -Function,-,rfalIsTransceiveInRx,_Bool, -Function,-,rfalIsTransceiveInTx,_Bool, -Function,-,rfalIsoDepATTRIB,ReturnCode,"const uint8_t*, uint8_t, rfalBitRate, rfalBitRate, rfalIsoDepFSxI, uint8_t, uint8_t, const uint8_t*, uint8_t, uint32_t, rfalIsoDepAttribRes*, uint8_t*" -Function,-,rfalIsoDepDeselect,ReturnCode, -Function,-,rfalIsoDepFSxI2FSx,uint16_t,uint8_t -Function,-,rfalIsoDepFWI2FWT,uint32_t,uint8_t -Function,-,rfalIsoDepGetApduTransceiveStatus,ReturnCode, -Function,-,rfalIsoDepGetMaxInfLen,uint16_t, -Function,-,rfalIsoDepGetTransceiveStatus,ReturnCode, -Function,-,rfalIsoDepInitialize,void, -Function,-,rfalIsoDepInitializeWithParams,void,"rfalComplianceMode, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" -Function,-,rfalIsoDepIsAttrib,_Bool,"const uint8_t*, uint8_t" -Function,-,rfalIsoDepIsRats,_Bool,"const uint8_t*, uint8_t" -Function,-,rfalIsoDepListenGetActivationStatus,ReturnCode, -Function,-,rfalIsoDepListenStartActivation,ReturnCode,"rfalIsoDepAtsParam*, const rfalIsoDepAttribResParam*, const uint8_t*, uint16_t, rfalIsoDepListenActvParam" -Function,-,rfalIsoDepPPS,ReturnCode,"uint8_t, rfalBitRate, rfalBitRate, rfalIsoDepPpsRes*" -Function,-,rfalIsoDepPollAGetActivationStatus,ReturnCode, -Function,-,rfalIsoDepPollAHandleActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollAStartActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollBGetActivationStatus,ReturnCode, -Function,-,rfalIsoDepPollBHandleActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, uint8_t, const rfalNfcbListenDevice*, const uint8_t*, uint8_t, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollBStartActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, uint8_t, const rfalNfcbListenDevice*, const uint8_t*, uint8_t, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollHandleSParameters,ReturnCode,"rfalIsoDepDevice*, rfalBitRate, rfalBitRate" -Function,-,rfalIsoDepRATS,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalIsoDepAts*, uint8_t*" -Function,-,rfalIsoDepStartApduTransceive,ReturnCode,rfalIsoDepApduTxRxParam -Function,-,rfalIsoDepStartTransceive,ReturnCode,rfalIsoDepTxRxParam -Function,-,rfalListenGetState,rfalLmState,"_Bool*, rfalBitRate*" -Function,-,rfalListenSetState,ReturnCode,rfalLmState -Function,-,rfalListenSleepStart,ReturnCode,"rfalLmState, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalListenStart,ReturnCode,"uint32_t, const rfalLmConfPA*, const rfalLmConfPB*, const rfalLmConfPF*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalListenStop,ReturnCode, -Function,+,rfalLowPowerModeStart,ReturnCode, -Function,+,rfalLowPowerModeStop,ReturnCode, -Function,-,rfalNfcDataExchangeCustomStart,ReturnCode,"uint8_t*, uint16_t, uint8_t**, uint16_t**, uint32_t, uint32_t" -Function,-,rfalNfcDataExchangeGetStatus,ReturnCode, -Function,-,rfalNfcDataExchangeStart,ReturnCode,"uint8_t*, uint16_t, uint8_t**, uint16_t**, uint32_t, uint32_t" -Function,-,rfalNfcDeactivate,ReturnCode,_Bool -Function,-,rfalNfcDepATR,ReturnCode,"const rfalNfcDepAtrParam*, rfalNfcDepAtrRes*, uint8_t*" -Function,-,rfalNfcDepCalculateRWT,uint32_t,uint8_t -Function,-,rfalNfcDepDSL,ReturnCode, -Function,-,rfalNfcDepGetPduTransceiveStatus,ReturnCode, -Function,-,rfalNfcDepGetTransceiveStatus,ReturnCode, -Function,-,rfalNfcDepInitialize,void, -Function,-,rfalNfcDepInitiatorHandleActivation,ReturnCode,"rfalNfcDepAtrParam*, rfalBitRate, rfalNfcDepDevice*" -Function,-,rfalNfcDepIsAtrReq,_Bool,"const uint8_t*, uint16_t, uint8_t*" -Function,-,rfalNfcDepListenGetActivationStatus,ReturnCode, -Function,-,rfalNfcDepListenStartActivation,ReturnCode,"const rfalNfcDepTargetParam*, const uint8_t*, uint16_t, rfalNfcDepListenActvParam" -Function,-,rfalNfcDepPSL,ReturnCode,"uint8_t, uint8_t" -Function,-,rfalNfcDepRLS,ReturnCode, -Function,-,rfalNfcDepSetDeactivatingCallback,void,rfalNfcDepDeactCallback -Function,-,rfalNfcDepStartPduTransceive,ReturnCode,rfalNfcDepPduTxRxParam -Function,-,rfalNfcDepStartTransceive,ReturnCode,const rfalNfcDepTxRxParam* -Function,-,rfalNfcDepTargetRcvdATR,_Bool, -Function,-,rfalNfcDiscover,ReturnCode,const rfalNfcDiscoverParam* -Function,-,rfalNfcGetActiveDevice,ReturnCode,rfalNfcDevice** -Function,-,rfalNfcGetDevicesFound,ReturnCode,"rfalNfcDevice**, uint8_t*" -Function,-,rfalNfcGetState,rfalNfcState, -Function,-,rfalNfcInitialize,ReturnCode, -Function,-,rfalNfcSelect,ReturnCode,uint8_t -Function,-,rfalNfcWorker,void, -Function,-,rfalNfcaListenerIsSleepReq,_Bool,"const uint8_t*, uint16_t" -Function,-,rfalNfcaPollerCheckPresence,ReturnCode,"rfal14443AShortFrameCmd, rfalNfcaSensRes*" -Function,-,rfalNfcaPollerFullCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcaListenDevice*, uint8_t*" -Function,-,rfalNfcaPollerGetFullCollisionResolutionStatus,ReturnCode, -Function,-,rfalNfcaPollerInitialize,ReturnCode, -Function,-,rfalNfcaPollerSelect,ReturnCode,"const uint8_t*, uint8_t, rfalNfcaSelRes*" -Function,-,rfalNfcaPollerSingleCollisionResolution,ReturnCode,"uint8_t, _Bool*, rfalNfcaSelRes*, uint8_t*, uint8_t*" -Function,-,rfalNfcaPollerSleep,ReturnCode, -Function,-,rfalNfcaPollerSleepFullCollisionResolution,ReturnCode,"uint8_t, rfalNfcaListenDevice*, uint8_t*" -Function,-,rfalNfcaPollerStartFullCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcaListenDevice*, uint8_t*" -Function,-,rfalNfcaPollerTechnologyDetection,ReturnCode,"rfalComplianceMode, rfalNfcaSensRes*" -Function,-,rfalNfcbPollerCheckPresence,ReturnCode,"rfalNfcbSensCmd, rfalNfcbSlots, rfalNfcbSensbRes*, uint8_t*" -Function,-,rfalNfcbPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcbListenDevice*, uint8_t*" -Function,-,rfalNfcbPollerInitialize,ReturnCode, -Function,-,rfalNfcbPollerInitializeWithParams,ReturnCode,"uint8_t, uint8_t" -Function,-,rfalNfcbPollerSleep,ReturnCode,const uint8_t* -Function,-,rfalNfcbPollerSlotMarker,ReturnCode,"uint8_t, rfalNfcbSensbRes*, uint8_t*" -Function,-,rfalNfcbPollerSlottedCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcbSlots, rfalNfcbSlots, rfalNfcbListenDevice*, uint8_t*, _Bool*" -Function,-,rfalNfcbPollerTechnologyDetection,ReturnCode,"rfalComplianceMode, rfalNfcbSensbRes*, uint8_t*" -Function,-,rfalNfcbTR2ToFDT,uint32_t,uint8_t -Function,-,rfalNfcfListenerIsT3TReq,_Bool,"const uint8_t*, uint16_t, uint8_t*" -Function,-,rfalNfcfPollerCheck,ReturnCode,"const uint8_t*, const rfalNfcfServBlockListParam*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcfPollerCheckPresence,ReturnCode, -Function,-,rfalNfcfPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcfListenDevice*, uint8_t*" -Function,-,rfalNfcfPollerInitialize,ReturnCode,rfalBitRate -Function,-,rfalNfcfPollerPoll,ReturnCode,"rfalFeliCaPollSlots, uint16_t, uint8_t, rfalFeliCaPollRes*, uint8_t*, uint8_t*" -Function,-,rfalNfcfPollerUpdate,ReturnCode,"const uint8_t*, const rfalNfcfServBlockListParam*, uint8_t*, uint16_t, const uint8_t*, uint8_t*, uint16_t" -Function,-,rfalNfcvPollerCheckPresence,ReturnCode,rfalNfcvInventoryRes* -Function,-,rfalNfcvPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcvListenDevice*, uint8_t*" -Function,-,rfalNfcvPollerExtendedGetSystemInformation,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerExtendedLockSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t" -Function,-,rfalNfcvPollerExtendedReadMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint16_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerExtendedReadSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerExtendedWriteMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint16_t, uint8_t*, uint16_t, uint8_t, const uint8_t*, uint16_t" -Function,-,rfalNfcvPollerExtendedWriteSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t, const uint8_t*, uint8_t" -Function,-,rfalNfcvPollerGetSystemInformation,ReturnCode,"uint8_t, const uint8_t*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerInitialize,ReturnCode, -Function,-,rfalNfcvPollerInventory,ReturnCode,"rfalNfcvNumSlots, uint8_t, const uint8_t*, rfalNfcvInventoryRes*, uint16_t*" -Function,-,rfalNfcvPollerLockBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t" -Function,-,rfalNfcvPollerReadMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerReadSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerSelect,ReturnCode,"uint8_t, const uint8_t*" -Function,-,rfalNfcvPollerSleep,ReturnCode,"uint8_t, const uint8_t*" -Function,-,rfalNfcvPollerSleepCollisionResolution,ReturnCode,"uint8_t, rfalNfcvListenDevice*, uint8_t*" -Function,-,rfalNfcvPollerTransceiveReq,ReturnCode,"uint8_t, uint8_t, uint8_t, const uint8_t*, const uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerWriteMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t, uint8_t*, uint16_t, uint8_t, const uint8_t*, uint16_t" -Function,-,rfalNfcvPollerWriteSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t, const uint8_t*, uint8_t" -Function,-,rfalSetBitRate,ReturnCode,"rfalBitRate, rfalBitRate" -Function,-,rfalSetErrorHandling,void,rfalEHandling -Function,-,rfalSetFDTListen,void,uint32_t -Function,-,rfalSetFDTPoll,void,uint32_t -Function,-,rfalSetGT,void,uint32_t -Function,-,rfalSetMode,ReturnCode,"rfalMode, rfalBitRate, rfalBitRate" -Function,-,rfalSetObsvMode,void,"uint8_t, uint8_t" -Function,-,rfalSetPostTxRxCallback,void,rfalPostTxRxCallback -Function,-,rfalSetPreTxRxCallback,void,rfalPreTxRxCallback -Function,-,rfalSetUpperLayerCallback,void,rfalUpperLayerCallback -Function,-,rfalSt25tbPollerCheckPresence,ReturnCode,uint8_t* -Function,-,rfalSt25tbPollerCollisionResolution,ReturnCode,"uint8_t, rfalSt25tbListenDevice*, uint8_t*" -Function,-,rfalSt25tbPollerCompletion,ReturnCode, -Function,-,rfalSt25tbPollerGetUID,ReturnCode,rfalSt25tbUID* -Function,-,rfalSt25tbPollerInitialize,ReturnCode, -Function,-,rfalSt25tbPollerInitiate,ReturnCode,uint8_t* -Function,-,rfalSt25tbPollerPcall,ReturnCode,uint8_t* -Function,-,rfalSt25tbPollerReadBlock,ReturnCode,"uint8_t, rfalSt25tbBlock*" -Function,-,rfalSt25tbPollerResetToInventory,ReturnCode, -Function,-,rfalSt25tbPollerSelect,ReturnCode,uint8_t -Function,-,rfalSt25tbPollerSlotMarker,ReturnCode,"uint8_t, uint8_t*" -Function,-,rfalSt25tbPollerWriteBlock,ReturnCode,"uint8_t, const rfalSt25tbBlock*" -Function,-,rfalStartTransceive,ReturnCode,const rfalTransceiveContext* -Function,-,rfalT1TPollerInitialize,ReturnCode, -Function,-,rfalT1TPollerRall,ReturnCode,"const uint8_t*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalT1TPollerRid,ReturnCode,rfalT1TRidRes* -Function,-,rfalT1TPollerWrite,ReturnCode,"const uint8_t*, uint8_t, uint8_t" -Function,-,rfalTransceiveBitsBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalTransceiveBitsBlockingTxRx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalTransceiveBlockingRx,ReturnCode, -Function,-,rfalTransceiveBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalTransceiveBlockingTxRx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalWakeUpModeHasWoke,_Bool, -Function,-,rfalWakeUpModeStart,ReturnCode,const rfalWakeUpConfig* -Function,-,rfalWakeUpModeStop,ReturnCode, -Function,+,rfalWorker,void, -Function,-,rfal_platform_spi_acquire,void, -Function,-,rfal_platform_spi_release,void, -Function,-,rfal_set_callback_context,void,void* -Function,-,rfal_set_state_changed_callback,void,RfalStateChangedCallback Function,-,rindex,char*,"const char*, int" Function,-,rint,double,double Function,-,rintf,float,float @@ -2565,6 +2558,25 @@ Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" Function,+,sha256_process,void,sha256_context* Function,+,sha256_start,void,sha256_context* Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" +Function,+,signal_reader_free,void,SignalReader* +Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" +Function,+,signal_reader_set_pull,void,"SignalReader*, GpioPull" +Function,+,signal_reader_set_sample_rate,void,"SignalReader*, SignalReaderTimeUnit, uint32_t" +Function,+,signal_reader_set_trigger,void,"SignalReader*, SignalReaderTrigger" +Function,+,signal_reader_start,void,"SignalReader*, SignalReaderCallback, void*" +Function,+,signal_reader_stop,void,SignalReader* +Function,+,simple_array_alloc,SimpleArray*,const SimpleArrayConfig* +Function,+,simple_array_cget,const SimpleArrayElement*,"const SimpleArray*, uint32_t" +Function,+,simple_array_cget_data,const SimpleArrayData*,const SimpleArray* +Function,+,simple_array_copy,void,"SimpleArray*, const SimpleArray*" +Function,+,simple_array_free,void,SimpleArray* +Function,+,simple_array_get,SimpleArrayElement*,"SimpleArray*, uint32_t" +Function,+,simple_array_get_count,uint32_t,const SimpleArray* +Function,+,simple_array_get_data,SimpleArrayData*,SimpleArray* +Function,+,simple_array_init,void,"SimpleArray*, uint32_t" +Function,+,simple_array_is_equal,_Bool,"const SimpleArray*, const SimpleArray*" +Function,+,simple_array_reset,void,SimpleArray* Function,-,sin,double,double Function,-,sincos,void,"double, double*, double*" Function,-,sincosf,void,"float, float*, float*" @@ -2575,6 +2587,26 @@ Function,-,sinhl,long double,long double Function,-,sinl,long double,long double Function,-,siprintf,int,"char*, const char*, ..." Function,-,siscanf,int,"const char*, const char*, ..." +Function,+,slix_alloc,SlixData*, +Function,+,slix_copy,void,"SlixData*, const SlixData*" +Function,+,slix_free,void,SlixData* +Function,+,slix_get_base_data,const Iso15693_3Data*,const SlixData* +Function,+,slix_get_counter,uint16_t,const SlixData* +Function,+,slix_get_device_name,const char*,"const SlixData*, NfcDeviceNameType" +Function,+,slix_get_password,SlixPassword,"const SlixData*, SlixPasswordType" +Function,+,slix_get_type,SlixType,const SlixData* +Function,+,slix_get_uid,const uint8_t*,"const SlixData*, size_t*" +Function,+,slix_is_block_protected,_Bool,"const SlixData*, SlixPasswordType, uint8_t" +Function,+,slix_is_counter_increment_protected,_Bool,const SlixData* +Function,+,slix_is_equal,_Bool,"const SlixData*, const SlixData*" +Function,+,slix_is_privacy_mode,_Bool,const SlixData* +Function,+,slix_load,_Bool,"SlixData*, FlipperFormat*, uint32_t" +Function,+,slix_reset,void,SlixData* +Function,+,slix_save,_Bool,"const SlixData*, FlipperFormat*" +Function,+,slix_set_uid,_Bool,"SlixData*, const uint8_t*, size_t" +Function,+,slix_type_has_features,_Bool,"SlixType, SlixTypeFeatures" +Function,+,slix_type_supports_password,_Bool,"SlixType, SlixPasswordType" +Function,+,slix_verify,_Bool,"SlixData*, const FuriString*" Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." Function,-,sprintf,int,"char*, const char*, ..." @@ -2585,6 +2617,19 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,st25tb_alloc,St25tbData*, +Function,+,st25tb_copy,void,"St25tbData*, const St25tbData*" +Function,+,st25tb_free,void,St25tbData* +Function,+,st25tb_get_base_data,St25tbData*,const St25tbData* +Function,+,st25tb_get_block_count,uint8_t,St25tbType +Function,+,st25tb_get_device_name,const char*,"const St25tbData*, NfcDeviceNameType" +Function,+,st25tb_get_uid,const uint8_t*,"const St25tbData*, size_t*" +Function,+,st25tb_is_equal,_Bool,"const St25tbData*, const St25tbData*" +Function,+,st25tb_load,_Bool,"St25tbData*, FlipperFormat*, uint32_t" +Function,+,st25tb_reset,void,St25tbData* +Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" +Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" +Function,+,st25tb_verify,_Bool,"St25tbData*, const FuriString*" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" @@ -2728,7 +2773,6 @@ Function,-,strupr,char*,char* Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" -Function,-,stub_parser_verify_read,_Bool,"NfcWorker*, FuriHalNfcTxRxContext*" Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*" Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" @@ -2934,11 +2978,6 @@ Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double Function,-,time,time_t,time_t* -Function,+,timerCalculateTimer,uint32_t,uint16_t -Function,-,timerDelay,void,uint16_t -Function,+,timerIsExpired,_Bool,uint32_t -Function,-,timerStopwatchMeasure,uint32_t, -Function,-,timerStopwatchStart,void, Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" Function,-,tmpfile,FILE*, @@ -3421,8 +3460,10 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, -Variable,+,nfc_generators,const NfcGenerator*[], -Variable,-,nfc_supported_card,NfcSupportedCard[NfcSupportedCardTypeEnd], +Variable,-,nfc_device_mf_classic,const NfcDeviceBase, +Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, +Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, +Variable,-,nfc_device_st25tb,const NfcDeviceBase, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, @@ -3472,6 +3513,7 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,simple_array_config_uint8_t,const SimpleArrayConfig, Variable,-,subghz_device_cc1101_int,const SubGhzDevice, Variable,+,subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs,const uint8_t[], Variable,+,subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs,const uint8_t[], diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index baffde1ebf..e8a1033d51 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -1,837 +1,621 @@ -#include -#include -#include -#include -#include -#include +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include -#include +#include #include -#include -#include -#include #define TAG "FuriHalNfc" -static const uint32_t clocks_in_ms = 64 * 1000; - -FuriEventFlag* event = NULL; -#define EVENT_FLAG_INTERRUPT (1UL << 0) -#define EVENT_FLAG_STATE_CHANGED (1UL << 1) -#define EVENT_FLAG_STOP (1UL << 2) -#define EVENT_FLAG_ALL (EVENT_FLAG_INTERRUPT | EVENT_FLAG_STATE_CHANGED | EVENT_FLAG_STOP) - -#define FURI_HAL_NFC_UID_INCOMPLETE (0x04) - -void furi_hal_nfc_init() { - furi_assert(!event); - event = furi_event_flag_alloc(); - - ReturnCode ret = rfalNfcInitialize(); - if(ret == ERR_NONE) { - furi_hal_nfc_start_sleep(); - FURI_LOG_I(TAG, "Init OK"); - } else { - FURI_LOG_W(TAG, "Init Failed, RFAL returned: %d", ret); - } -} - -void furi_hal_nfc_deinit() { - ReturnCode ret = rfalDeinitialize(); - if(ret == ERR_NONE) { - FURI_LOG_I(TAG, "Deinit OK"); - } else { - FURI_LOG_W(TAG, "Deinit Failed, RFAL returned: %d", ret); +const FuriHalNfcTechBase* furi_hal_nfc_tech[FuriHalNfcTechNum] = { + [FuriHalNfcTechIso14443a] = &furi_hal_nfc_iso14443a, + [FuriHalNfcTechIso14443b] = &furi_hal_nfc_iso14443b, + [FuriHalNfcTechIso15693] = &furi_hal_nfc_iso15693, + [FuriHalNfcTechFelica] = &furi_hal_nfc_felica, + // Add new technologies here +}; + +FuriHalNfc furi_hal_nfc; + +static FuriHalNfcError furi_hal_nfc_turn_on_osc(FuriHalSpiBusHandle* handle) { + FuriHalNfcError error = FuriHalNfcErrorNone; + furi_hal_nfc_event_start(); + + if(!st25r3916_check_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en, + ST25R3916_REG_OP_CONTROL_en)) { + st25r3916_mask_irq(handle, ~ST25R3916_IRQ_MASK_OSC); + st25r3916_set_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en); + furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_OSC, 10); + } + // Disable IRQs + st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL); + + bool osc_on = st25r3916_check_reg( + handle, + ST25R3916_REG_AUX_DISPLAY, + ST25R3916_REG_AUX_DISPLAY_osc_ok, + ST25R3916_REG_AUX_DISPLAY_osc_ok); + if(!osc_on) { + error = FuriHalNfcErrorOscillator; } - if(event) { - furi_event_flag_free(event); - event = NULL; - } + return error; } -bool furi_hal_nfc_is_busy() { - return rfalNfcGetState() != RFAL_NFC_STATE_IDLE; -} +FuriHalNfcError furi_hal_nfc_is_hal_ready() { + FuriHalNfcError error = FuriHalNfcErrorNone; -bool furi_hal_nfc_is_init() { - return rfalNfcGetState() != RFAL_NFC_STATE_NOTINIT; -} + do { + error = furi_hal_nfc_acquire(); + if(error != FuriHalNfcErrorNone) break; -void furi_hal_nfc_field_on() { - furi_hal_nfc_exit_sleep(); - st25r3916TxRxOn(); -} + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + uint8_t chip_id = 0; + st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id); + if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != + ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) { + FURI_LOG_E(TAG, "Wrong chip id"); + error = FuriHalNfcErrorCommunication; + } -void furi_hal_nfc_field_off() { - st25r3916TxRxOff(); - furi_hal_nfc_start_sleep(); -} + furi_hal_nfc_release(); + } while(false); -void furi_hal_nfc_start_sleep() { - rfalLowPowerModeStart(); + return error; } -void furi_hal_nfc_exit_sleep() { - rfalLowPowerModeStop(); -} +FuriHalNfcError furi_hal_nfc_init() { + furi_assert(furi_hal_nfc.mutex == NULL); -bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { - furi_assert(nfc_data); + furi_hal_nfc.mutex = furi_mutex_alloc(FuriMutexTypeNormal); + FuriHalNfcError error = FuriHalNfcErrorNone; - rfalNfcDevice* dev_list = NULL; - uint8_t dev_cnt = 0; - bool detected = false; + furi_hal_nfc_event_init(); + furi_hal_nfc_event_start(); - rfalLowPowerModeStop(); - rfalNfcState state = rfalNfcGetState(); - rfalNfcState state_old = 0; - if(state == RFAL_NFC_STATE_NOTINIT) { - rfalNfcInitialize(); - } - rfalNfcDiscoverParam params; - params.compMode = RFAL_COMPLIANCE_MODE_EMV; - params.techs2Find = RFAL_NFC_POLL_TECH_A | RFAL_NFC_POLL_TECH_B | RFAL_NFC_POLL_TECH_F | - RFAL_NFC_POLL_TECH_V | RFAL_NFC_POLL_TECH_AP2P | RFAL_NFC_POLL_TECH_ST25TB; - params.totalDuration = 1000; - params.devLimit = 3; - params.wakeupEnabled = false; - params.wakeupConfigDefault = true; - params.nfcfBR = RFAL_BR_212; - params.ap2pBR = RFAL_BR_424; - params.maxBR = RFAL_BR_KEEP; - params.GBLen = RFAL_NFCDEP_GB_MAX_LEN; - params.notifyCb = NULL; - - uint32_t start = DWT->CYCCNT; - rfalNfcDiscover(¶ms); - while(true) { - rfalNfcWorker(); - state = rfalNfcGetState(); - if(state != state_old) { - FURI_LOG_T(TAG, "State change %d -> %d", state_old, state); + do { + error = furi_hal_nfc_acquire(); + if(error != FuriHalNfcErrorNone) { + furi_hal_nfc_low_power_mode_start(); } - state_old = state; - if(state == RFAL_NFC_STATE_ACTIVATED) { - detected = true; + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + // Set default state + st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT); + // Increase IO driver strength of MISO and IRQ + st25r3916_write_reg(handle, ST25R3916_REG_IO_CONF2, ST25R3916_REG_IO_CONF2_io_drv_lvl); + // Check chip ID + uint8_t chip_id = 0; + st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id); + if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != + ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) { + FURI_LOG_E(TAG, "Wrong chip id"); + error = FuriHalNfcErrorCommunication; + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); break; } - if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { - start = DWT->CYCCNT; - continue; - } - if(state == RFAL_NFC_STATE_POLL_SELECT) { - rfalNfcSelect(0); - } - if(DWT->CYCCNT - start > timeout * clocks_in_ms) { - rfalNfcDeactivate(true); - FURI_LOG_T(TAG, "Timeout"); + // Clear interrupts + st25r3916_get_irq(handle); + // Mask all interrupts + st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL); + // Enable interrupts + furi_hal_nfc_init_gpio_isr(); + // Disable internal overheat protection + st25r3916_change_test_reg_bits(handle, 0x04, 0x10, 0x10); + + error = furi_hal_nfc_turn_on_osc(handle); + if(error != FuriHalNfcErrorNone) { + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); break; } - furi_delay_tick(1); - } - rfalNfcGetDevicesFound(&dev_list, &dev_cnt); - if(detected) { - if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCA) { - nfc_data->type = FuriHalNfcTypeA; - nfc_data->atqa[0] = dev_list[0].dev.nfca.sensRes.anticollisionInfo; - nfc_data->atqa[1] = dev_list[0].dev.nfca.sensRes.platformInfo; - nfc_data->sak = dev_list[0].dev.nfca.selRes.sak; - uint8_t* cuid_start = dev_list[0].nfcid; - if(dev_list[0].nfcidLen == 7) { - cuid_start = &dev_list[0].nfcid[3]; - } - nfc_data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | - (cuid_start[3]); - } else if( - dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCB || - dev_list[0].type == RFAL_NFC_LISTEN_TYPE_ST25TB) { - nfc_data->type = FuriHalNfcTypeB; - } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCF) { - nfc_data->type = FuriHalNfcTypeF; - } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCV) { - nfc_data->type = FuriHalNfcTypeV; - } - if(dev_list[0].rfInterface == RFAL_NFC_INTERFACE_RF) { - nfc_data->interface = FuriHalNfcInterfaceRf; - } else if(dev_list[0].rfInterface == RFAL_NFC_INTERFACE_ISODEP) { - nfc_data->interface = FuriHalNfcInterfaceIsoDep; - } else if(dev_list[0].rfInterface == RFAL_NFC_INTERFACE_NFCDEP) { - nfc_data->interface = FuriHalNfcInterfaceNfcDep; + + // Measure voltage + // Set measure power supply voltage source + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_REGULATOR_CONTROL, + ST25R3916_REG_REGULATOR_CONTROL_mpsv_mask, + ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd); + // Enable timer and interrupt register + st25r3916_mask_irq(handle, ~ST25R3916_IRQ_MASK_DCT); + st25r3916_direct_cmd(handle, ST25R3916_CMD_MEASURE_VDD); + furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_DCT, 100); + st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL); + uint8_t ad_res = 0; + st25r3916_read_reg(handle, ST25R3916_REG_AD_RESULT, &ad_res); + uint16_t mV = ((uint16_t)ad_res) * 23U; + mV += (((((uint16_t)ad_res) * 4U) + 5U) / 10U); + + if(mV < 3600) { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF2, + ST25R3916_REG_IO_CONF2_sup3V, + ST25R3916_REG_IO_CONF2_sup3V_3V); + } else { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF2, + ST25R3916_REG_IO_CONF2_sup3V, + ST25R3916_REG_IO_CONF2_sup3V_5V); } - nfc_data->uid_len = dev_list[0].nfcidLen; - memcpy(nfc_data->uid, dev_list[0].nfcid, nfc_data->uid_len); - } - return detected; -} + // Disable MCU CLK + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF1, + ST25R3916_REG_IO_CONF1_out_cl_mask | ST25R3916_REG_IO_CONF1_lf_clk_off, + 0x07); + // Disable MISO pull-down + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF2, + ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2, + 0x00); + // Set tx driver resistance to 1 Om + st25r3916_change_reg_bits( + handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_d_res_mask, 0x00); + // Use minimum non-overlap + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_RES_AM_MOD, + ST25R3916_REG_RES_AM_MOD_fa3_f, + ST25R3916_REG_RES_AM_MOD_fa3_f); + + // Set activation threashold + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_ACTV, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_105mV); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_ACTV, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_105mV); + // Set deactivation threashold + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_mask, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_75mV); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_mask, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_75mV); + // Enable external load modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_lm_ext, + ST25R3916_REG_AUX_MOD_lm_ext); + // Enable internal load modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_lm_dri, + ST25R3916_REG_AUX_MOD_lm_dri); + // Adjust FDT + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_fdel_mask, + (5U << ST25R3916_REG_PASSIVE_TARGET_fdel_shift)); + // Reduce RFO resistance in Modulated state + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_PT_MOD, + ST25R3916_REG_PT_MOD_ptm_res_mask | ST25R3916_REG_PT_MOD_pt_res_mask, + 0x0f); + // Enable RX start on first 4 bits + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_EMD_SUP_CONF, + ST25R3916_REG_EMD_SUP_CONF_rx_start_emv, + ST25R3916_REG_EMD_SUP_CONF_rx_start_emv_on); + // Set antena tunning + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_A, 0xff, 0x82); + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_B, 0xff, 0x82); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en_fd_mask, + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); -bool furi_hal_nfc_activate_nfca(uint32_t timeout, uint32_t* cuid) { - rfalNfcDevice* dev_list; - uint8_t dev_cnt = 0; - rfalLowPowerModeStop(); - rfalNfcState state = rfalNfcGetState(); - if(state == RFAL_NFC_STATE_NOTINIT) { - rfalNfcInitialize(); - } - rfalNfcDiscoverParam params = { - .compMode = RFAL_COMPLIANCE_MODE_NFC, - .techs2Find = RFAL_NFC_POLL_TECH_A, - .totalDuration = 1000, - .devLimit = 3, - .wakeupEnabled = false, - .wakeupConfigDefault = true, - .nfcfBR = RFAL_BR_212, - .ap2pBR = RFAL_BR_424, - .maxBR = RFAL_BR_KEEP, - .GBLen = RFAL_NFCDEP_GB_MAX_LEN, - .notifyCb = NULL, - }; - uint32_t start = DWT->CYCCNT; - rfalNfcDiscover(¶ms); - while(state != RFAL_NFC_STATE_ACTIVATED) { - rfalNfcWorker(); - state = rfalNfcGetState(); - FURI_LOG_T(TAG, "Current state %d", state); - if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { - start = DWT->CYCCNT; - continue; - } - if(state == RFAL_NFC_STATE_POLL_SELECT) { - rfalNfcSelect(0); - } - if(DWT->CYCCNT - start > timeout * clocks_in_ms) { - rfalNfcDeactivate(true); - FURI_LOG_T(TAG, "Timeout"); - return false; + // Perform calibration + if(st25r3916_check_reg( + handle, + ST25R3916_REG_REGULATOR_CONTROL, + ST25R3916_REG_REGULATOR_CONTROL_reg_s, + 0x00)) { + FURI_LOG_I(TAG, "Adjusting regulators"); + // Reset logic + st25r3916_set_reg_bits( + handle, ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); + st25r3916_direct_cmd(handle, ST25R3916_CMD_ADJUST_REGULATORS); + furi_delay_ms(6); } - furi_thread_yield(); - } - rfalNfcGetDevicesFound(&dev_list, &dev_cnt); - // Take first device and set cuid - if(cuid) { - uint8_t* cuid_start = dev_list[0].nfcid; - if(dev_list[0].nfcidLen == 7) { - cuid_start = &dev_list[0].nfcid[3]; - } - *cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | - (cuid_start[3]); - FURI_LOG_T(TAG, "Activated tag with cuid: %lX", *cuid); - } - return true; -} -bool furi_hal_nfc_listen( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - bool activate_after_sak, - uint32_t timeout) { - rfalNfcState state = rfalNfcGetState(); - if(state == RFAL_NFC_STATE_NOTINIT) { - rfalNfcInitialize(); - } else if(state >= RFAL_NFC_STATE_ACTIVATED) { - rfalNfcDeactivate(false); - } - rfalLowPowerModeStop(); - rfalNfcDiscoverParam params = { - .techs2Find = RFAL_NFC_LISTEN_TECH_A, - .totalDuration = 1000, - .devLimit = 1, - .wakeupEnabled = false, - .wakeupConfigDefault = true, - .nfcfBR = RFAL_BR_212, - .ap2pBR = RFAL_BR_424, - .maxBR = RFAL_BR_KEEP, - .GBLen = RFAL_NFCDEP_GB_MAX_LEN, - .notifyCb = NULL, - .activate_after_sak = activate_after_sak, - }; - if(FURI_BIT(sak, 5)) { - params.compMode = RFAL_COMPLIANCE_MODE_EMV; - } else { - params.compMode = RFAL_COMPLIANCE_MODE_NFC; - } - params.lmConfigPA.nfcidLen = uid_len; - memcpy(params.lmConfigPA.nfcid, uid, uid_len); - params.lmConfigPA.SENS_RES[0] = atqa[0]; - params.lmConfigPA.SENS_RES[1] = atqa[1]; - params.lmConfigPA.SEL_RES = sak; - rfalNfcDiscover(¶ms); - - // Disable EMD suppression. - st25r3916ModifyRegister(ST25R3916_REG_EMD_SUP_CONF, ST25R3916_REG_EMD_SUP_CONF_emd_emv, 0); - - uint32_t start = DWT->CYCCNT; - while(state != RFAL_NFC_STATE_ACTIVATED) { - rfalNfcWorker(); - state = rfalNfcGetState(); - if(DWT->CYCCNT - start > timeout * clocks_in_ms) { - rfalNfcDeactivate(true); - return false; - } - furi_delay_tick(1); - } - return true; -} + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); + } while(false); -static void furi_hal_nfc_read_fifo(uint8_t* data, uint16_t* bits) { - uint8_t fifo_status[2]; - uint8_t rx_buff[64]; - - st25r3916ReadMultipleRegisters( - ST25R3916_REG_FIFO_STATUS1, fifo_status, ST25R3916_FIFO_STATUS_LEN); - uint16_t rx_bytes = - ((((uint16_t)fifo_status[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) - << 8); - rx_bytes |= (((uint16_t)fifo_status[0]) & 0x00FFU); - st25r3916ReadFifo(rx_buff, rx_bytes); - - memcpy(data, rx_buff, rx_bytes); - *bits = rx_bytes * 8; + return error; } -void furi_hal_nfc_listen_sleep() { - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); +static bool furi_hal_nfc_is_mine() { + return (furi_mutex_get_owner(furi_hal_nfc.mutex) == furi_thread_get_current_id()); } -void furi_hal_nfc_stop_cmd() { - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); -} +FuriHalNfcError furi_hal_nfc_acquire() { + furi_check(furi_hal_nfc.mutex); -bool furi_hal_nfc_listen_rx(FuriHalNfcTxRxContext* tx_rx, uint32_t timeout_ms) { - furi_assert(tx_rx); - - // Wait for interrupts - uint32_t start = furi_get_tick(); - bool data_received = false; - while(true) { - if(furi_hal_gpio_read(&gpio_nfc_irq_rfid_pull) == true) { - st25r3916CheckForReceivedInterrupts(); - if(st25r3916GetInterrupt(ST25R3916_IRQ_MASK_RXE)) { - furi_hal_nfc_read_fifo(tx_rx->rx_data, &tx_rx->rx_bits); - data_received = true; - if(tx_rx->sniff_rx) { - tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, false, tx_rx->sniff_context); - } - break; - } - continue; - } - if(furi_get_tick() - start > timeout_ms) { - FURI_LOG_T(TAG, "Interrupt waiting timeout"); - furi_delay_tick(1); - break; - } + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); + + FuriHalNfcError error = FuriHalNfcErrorNone; + if(furi_mutex_acquire(furi_hal_nfc.mutex, 100) != FuriStatusOk) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); + error = FuriHalNfcErrorBusy; } - return data_received; + return error; } -void furi_hal_nfc_listen_start(FuriHalNfcDevData* nfc_data) { - furi_assert(nfc_data); +FuriHalNfcError furi_hal_nfc_release() { + furi_check(furi_hal_nfc.mutex); + furi_check(furi_hal_nfc_is_mine()); + furi_check(furi_mutex_release(furi_hal_nfc.mutex) == FuriStatusOk); - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); - // Clear interrupts - st25r3916ClearInterrupts(); - // Mask all interrupts - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - // RESET - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - // Setup registers - st25r3916WriteRegister( + furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_low_power_mode_start() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_wu | ST25R3916_REG_OP_CONTROL_tx_en | + ST25R3916_REG_OP_CONTROL_en_fd_mask)); + furi_hal_nfc_deinit_gpio_isr(); + furi_hal_nfc_timers_deinit(); + furi_hal_nfc_event_stop(); + + return error; +} + +FuriHalNfcError furi_hal_nfc_low_power_mode_stop() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + do { + furi_hal_nfc_init_gpio_isr(); + furi_hal_nfc_timers_init(); + error = furi_hal_nfc_turn_on_osc(handle); + if(error != FuriHalNfcErrorNone) break; + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en_fd_mask, ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - st25r3916WriteRegister( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om0); - st25r3916WriteRegister( - ST25R3916_REG_PASSIVE_TARGET, - ST25R3916_REG_PASSIVE_TARGET_fdel_2 | ST25R3916_REG_PASSIVE_TARGET_fdel_0 | - ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r); - st25r3916WriteRegister(ST25R3916_REG_MASK_RX_TIMER, 0x02); - - // Mask interrupts - uint32_t clear_irq_mask = - (ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_A_X | - ST25R3916_IRQ_MASK_WU_A); - st25r3916EnableInterrupts(clear_irq_mask); - - // Set 4 or 7 bytes UID - if(nfc_data->uid_len == 4) { - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_4bytes); - } else { - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_7bytes); - } - // Write PT Memory - uint8_t pt_memory[15] = {}; - memcpy(pt_memory, nfc_data->uid, nfc_data->uid_len); - pt_memory[10] = nfc_data->atqa[0]; - pt_memory[11] = nfc_data->atqa[1]; - if(nfc_data->uid_len == 4) { - pt_memory[12] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; - } else { - pt_memory[12] = FURI_HAL_NFC_UID_INCOMPLETE; - } - pt_memory[13] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; - pt_memory[14] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; - st25r3916WritePTMem(pt_memory, sizeof(pt_memory)); - // Go to sense - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); -} + } while(false); -void rfal_interrupt_callback_handler() { - furi_event_flag_set(event, EVENT_FLAG_INTERRUPT); + return error; } -void rfal_state_changed_callback(void* context) { - UNUSED(context); - furi_event_flag_set(event, EVENT_FLAG_STATE_CHANGED); +static FuriHalNfcError furi_hal_nfc_poller_init_common(FuriHalSpiBusHandle* handle) { + // Disable wake up + st25r3916_clear_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); + // Enable correlator + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX, + ST25R3916_REG_AUX_dis_corr, + ST25R3916_REG_AUX_dis_corr_correlator); + + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_A, 0xff, 0x82); + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_B, 0xFF, 0x82); + + st25r3916_write_reg(handle, ST25R3916_REG_OVERSHOOT_CONF1, 0x00); + st25r3916_write_reg(handle, ST25R3916_REG_OVERSHOOT_CONF2, 0x00); + st25r3916_write_reg(handle, ST25R3916_REG_UNDERSHOOT_CONF1, 0x00); + st25r3916_write_reg(handle, ST25R3916_REG_UNDERSHOOT_CONF2, 0x00); + + return FuriHalNfcErrorNone; } -void furi_hal_nfc_stop() { - if(event) { - furi_event_flag_set(event, EVENT_FLAG_STOP); - } +static FuriHalNfcError furi_hal_nfc_listener_init_common(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + // No common listener configuration + return FuriHalNfcErrorNone; } -bool furi_hal_nfc_emulate_nfca( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - FuriHalNfcEmulateCallback callback, - void* context, - uint32_t timeout) { - rfalSetUpperLayerCallback(rfal_interrupt_callback_handler); - rfal_set_state_changed_callback(rfal_state_changed_callback); - - rfalLmConfPA config; - config.nfcidLen = uid_len; - memcpy(config.nfcid, uid, uid_len); - memcpy(config.SENS_RES, atqa, RFAL_LM_SENS_RES_LEN); - config.SEL_RES = sak; - uint8_t buff_rx[256]; - uint16_t buff_rx_size = 256; - uint16_t buff_rx_len = 0; - uint8_t buff_tx[1040]; - uint16_t buff_tx_len = 0; - uint32_t data_type = FURI_HAL_NFC_TXRX_DEFAULT; - - rfalLowPowerModeStop(); - if(rfalListenStart( - RFAL_LM_MASK_NFCA, - &config, - NULL, - NULL, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len)) { - rfalListenStop(); - FURI_LOG_E(TAG, "Failed to start listen mode"); - return false; - } - while(true) { - buff_rx_len = 0; - buff_tx_len = 0; - uint32_t flag = furi_event_flag_wait(event, EVENT_FLAG_ALL, FuriFlagWaitAny, timeout); - if(flag == (unsigned)FuriFlagErrorTimeout || flag == EVENT_FLAG_STOP) { - break; - } - bool data_received = false; - buff_rx_len = 0; - rfalWorker(); - rfalLmState state = rfalListenGetState(&data_received, NULL); - if(data_received) { - rfalTransceiveBlockingRx(); - if(nfca_emulation_handler(buff_rx, buff_rx_len, buff_tx, &buff_tx_len)) { - if(rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len)) { - FURI_LOG_E(TAG, "Failed to enter sleep mode"); - break; - } else { - continue; - } - } - if(buff_tx_len) { - ReturnCode ret = rfalTransceiveBitsBlockingTx( - buff_tx, - buff_tx_len, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len, - data_type, - RFAL_FWT_NONE); - if(ret) { - FURI_LOG_E(TAG, "Tranceive failed with status %d", ret); - break; - } - continue; - } - if((state == RFAL_LM_STATE_ACTIVE_A || state == RFAL_LM_STATE_ACTIVE_Ax)) { - if(callback) { - callback(buff_rx, buff_rx_len, buff_tx, &buff_tx_len, &data_type, context); - } - if(!rfalIsExtFieldOn()) { - break; - } - if(buff_tx_len) { - if(buff_tx_len == UINT16_MAX) buff_tx_len = 0; - - ReturnCode ret = rfalTransceiveBitsBlockingTx( - buff_tx, - buff_tx_len, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len, - data_type, - RFAL_FWT_NONE); - if(ret) { - FURI_LOG_E(TAG, "Tranceive failed with status %d", ret); - continue; - } - } else { - break; - } - } - } +FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech) { + furi_assert(mode < FuriHalNfcModeNum); + furi_assert(tech < FuriHalNfcTechNum); + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + FuriHalNfcError error = FuriHalNfcErrorNone; + + if(mode == FuriHalNfcModePoller) { + do { + error = furi_hal_nfc_poller_init_common(handle); + if(error != FuriHalNfcErrorNone) break; + error = furi_hal_nfc_tech[tech]->poller.init(handle); + } while(false); + + } else if(mode == FuriHalNfcModeListener) { + do { + error = furi_hal_nfc_listener_init_common(handle); + if(error != FuriHalNfcErrorNone) break; + error = furi_hal_nfc_tech[tech]->listener.init(handle); + } while(false); } - rfalListenStop(); - return true; + + furi_hal_nfc.mode = mode; + furi_hal_nfc.tech = tech; + return error; } -static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { - furi_assert(tx_rx->nfca_signal); +FuriHalNfcError furi_hal_nfc_reset_mode() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - bool ret = false; + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); - // Start transparent mode - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); - // Reconfigure gpio for Transparent mode - furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + const FuriHalNfcMode mode = furi_hal_nfc.mode; + const FuriHalNfcTech tech = furi_hal_nfc.tech; + if(mode == FuriHalNfcModePoller) { + error = furi_hal_nfc_tech[tech]->poller.deinit(handle); + } else if(mode == FuriHalNfcModeListener) { + error = furi_hal_nfc_tech[tech]->listener.deinit(handle); + } + // Set default value in mode register + st25r3916_write_reg(handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_om0); + st25r3916_write_reg(handle, ST25R3916_REG_STREAM_MODE, 0); + st25r3916_clear_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_BIT_RATE, + ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask); - // Send signal - FURI_CRITICAL_ENTER(); - nfca_signal_encode(tx_rx->nfca_signal, tx_rx->tx_data, tx_rx->tx_bits, tx_rx->tx_parity); - digital_signal_send(tx_rx->nfca_signal->tx_signal, &gpio_spi_r_mosi); - FURI_CRITICAL_EXIT(); - furi_hal_gpio_write(&gpio_spi_r_mosi, false); + // Write default values + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, 0); + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_sqm_dyn | ST25R3916_REG_RX_CONF2_agc_en | + ST25R3916_REG_RX_CONF2_agc_m); - // Configure gpio back to SPI and exit transparent - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s7 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s1 | ST25R3916_REG_CORR_CONF1_corr_s0); + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0); - // Manually wait for interrupt - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); - st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE); + return error; +} - if(tx_rx->sniff_tx) { - tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, false, tx_rx->sniff_context); - } +FuriHalNfcError furi_hal_nfc_field_detect_start() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - uint32_t irq = 0; - uint8_t rxe = 0; - uint32_t start = DWT->CYCCNT; - while(true) { - if(!rfalIsExtFieldOn()) { - return false; - } - if(furi_hal_gpio_read(&gpio_nfc_irq_rfid_pull) == true) { - st25r3916ReadRegister(ST25R3916_REG_IRQ_MAIN, &rxe); - if(rxe & (1 << 4)) { - irq = 1; - break; - } - } - uint32_t timeout = DWT->CYCCNT - start; - if(timeout / furi_hal_cortex_instructions_per_microsecond() > timeout_ms * 1000) { - FURI_LOG_D(TAG, "Interrupt waiting timeout"); - break; - } - } - if(irq) { - uint8_t fifo_stat[2]; - st25r3916ReadMultipleRegisters( - ST25R3916_REG_FIFO_STATUS1, fifo_stat, ST25R3916_FIFO_STATUS_LEN); - uint16_t len = - ((((uint16_t)fifo_stat[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) - << RFAL_BITS_IN_BYTE); - len |= (((uint16_t)fifo_stat[0]) & 0x00FFU); - uint8_t rx[100]; - st25r3916ReadFifo(rx, len); - - tx_rx->rx_bits = len * 8; - memcpy(tx_rx->rx_data, rx, len); - - if(tx_rx->sniff_rx) { - tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, false, tx_rx->sniff_context); - } + st25r3916_write_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask); + st25r3916_write_reg( + handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om0); - ret = true; - } else { - FURI_LOG_E(TAG, "Timeout error"); - ret = false; - } + return error; +} + +FuriHalNfcError furi_hal_nfc_field_detect_stop() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - st25r3916ClearInterrupts(); + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask)); - return ret; + return error; } -static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) { - uint32_t flags = 0; - - if(type == FuriHalNfcTxRxTypeRxNoCrc) { - flags = RFAL_TXRX_FLAGS_CRC_RX_KEEP; - } else if(type == FuriHalNfcTxRxTypeRxKeepPar) { - flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | - RFAL_TXRX_FLAGS_PAR_RX_KEEP; - } else if(type == FuriHalNfcTxRxTypeRaw) { - flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | - RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE; - } else if(type == FuriHalNfcTxRxTypeRxRaw) { - flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | - RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE; - } +bool furi_hal_nfc_field_is_present() { + bool is_present = false; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + if(st25r3916_check_reg( + handle, + ST25R3916_REG_AUX_DISPLAY, + ST25R3916_REG_AUX_DISPLAY_efd_o, + ST25R3916_REG_AUX_DISPLAY_efd_o)) { + is_present = true; + } + + return is_present; +} + +FuriHalNfcError furi_hal_nfc_poller_field_on() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + if(!st25r3916_check_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_tx_en, + ST25R3916_REG_OP_CONTROL_tx_en)) { + // Set min guard time + st25r3916_write_reg(handle, ST25R3916_REG_FIELD_ON_GT, 0); + // Enable tx rx + st25r3916_set_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + (ST25R3916_REG_OP_CONTROL_rx_en | ST25R3916_REG_OP_CONTROL_tx_en)); + } + + return error; +} + +FuriHalNfcError furi_hal_nfc_poller_tx_common( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + furi_assert(tx_data); + + FuriHalNfcError err = FuriHalNfcErrorNone; + + // Prepare tx + st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off)); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); - return flags; + st25r3916_write_fifo(handle, tx_data, tx_bits); + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); + + return err; } -static uint16_t furi_hal_nfc_data_and_parity_to_bitstream( - uint8_t* data, - uint16_t len, - uint8_t* parity, - uint8_t* out) { - furi_assert(data); - furi_assert(out); - - uint8_t next_par_bit = 0; - uint16_t curr_bit_pos = 0; - for(uint16_t i = 0; i < len; i++) { - next_par_bit = FURI_BIT(parity[i / 8], 7 - (i % 8)); - if(curr_bit_pos % 8 == 0) { - out[curr_bit_pos / 8] = data[i]; - curr_bit_pos += 8; - out[curr_bit_pos / 8] = next_par_bit; - curr_bit_pos++; - } else { - out[curr_bit_pos / 8] |= data[i] << (curr_bit_pos % 8); - out[curr_bit_pos / 8 + 1] = data[i] >> (8 - curr_bit_pos % 8); - out[curr_bit_pos / 8 + 1] |= next_par_bit << (curr_bit_pos % 8); - curr_bit_pos += 9; - } - } - return curr_bit_pos; +FuriHalNfcError furi_hal_nfc_common_fifo_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + FuriHalNfcError err = FuriHalNfcErrorNone; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); + st25r3916_write_fifo(handle, tx_data, tx_bits); + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); + + return err; } -uint16_t furi_hal_nfc_bitstream_to_data_and_parity( - uint8_t* in_buff, - uint16_t in_buff_bits, - uint8_t* out_data, - uint8_t* out_parity) { - if(in_buff_bits < 8) { - out_data[0] = in_buff[0]; - return in_buff_bits; - } - if(in_buff_bits % 9 != 0) { - return 0; - } +FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - uint8_t curr_byte = 0; - uint16_t bit_processed = 0; - memset(out_parity, 0, in_buff_bits / 9); - while(bit_processed < in_buff_bits) { - out_data[curr_byte] = in_buff[bit_processed / 8] >> (bit_processed % 8); - out_data[curr_byte] |= in_buff[bit_processed / 8 + 1] << (8 - bit_processed % 8); - out_parity[curr_byte / 8] |= FURI_BIT(in_buff[bit_processed / 8 + 1], bit_processed % 8) - << (7 - curr_byte % 8); - bit_processed += 9; - curr_byte++; - } - return curr_byte * 8; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.tx(handle, tx_data, tx_bits); } -bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { - furi_assert(tx_rx); +FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - ReturnCode ret; - rfalNfcState state = RFAL_NFC_STATE_ACTIVATED; - uint8_t temp_tx_buff[FURI_HAL_NFC_DATA_BUFF_SIZE] = {}; - uint16_t temp_tx_bits = 0; - uint8_t* temp_rx_buff = NULL; - uint16_t* temp_rx_bits = NULL; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.rx(handle, rx_data, rx_data_size, rx_bits); +} - if(tx_rx->tx_rx_type == FuriHalNfcTxRxTransparent) { - return furi_hal_nfc_transparent_tx_rx(tx_rx, timeout_ms); - } +FuriHalNfcEvent furi_hal_nfc_poller_wait_event(uint32_t timeout_ms) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); - // Prepare data for FIFO if necessary - uint32_t flags = furi_hal_nfc_tx_rx_get_flag(tx_rx->tx_rx_type); - if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) { - temp_tx_bits = furi_hal_nfc_data_and_parity_to_bitstream( - tx_rx->tx_data, tx_rx->tx_bits / 8, tx_rx->tx_parity, temp_tx_buff); - ret = rfalNfcDataExchangeCustomStart( - temp_tx_buff, temp_tx_bits, &temp_rx_buff, &temp_rx_bits, RFAL_FWT_NONE, flags); - } else { - ret = rfalNfcDataExchangeCustomStart( - tx_rx->tx_data, tx_rx->tx_bits, &temp_rx_buff, &temp_rx_bits, RFAL_FWT_NONE, flags); - } - if(ret != ERR_NONE) { - FURI_LOG_E(TAG, "Failed to start data exchange"); - return false; - } + return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.wait_event(timeout_ms); +} - if(tx_rx->sniff_tx) { - bool crc_dropped = !(flags & RFAL_TXRX_FLAGS_CRC_TX_MANUAL); - tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, crc_dropped, tx_rx->sniff_context); - } +FuriHalNfcEvent furi_hal_nfc_listener_wait_event(uint32_t timeout_ms) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); - uint32_t start = DWT->CYCCNT; - while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) { - rfalNfcWorker(); - state = rfalNfcGetState(); - ret = rfalNfcDataExchangeGetStatus(); - if(ret == ERR_WRONG_STATE) { - return false; - } else if(ret == ERR_BUSY) { - if(DWT->CYCCNT - start > timeout_ms * clocks_in_ms) { - FURI_LOG_D(TAG, "Timeout during data exchange"); - return false; - } - continue; - } else { - start = DWT->CYCCNT; - } - furi_delay_tick(1); - } + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.wait_event(timeout_ms); +} - if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw || - tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) { - tx_rx->rx_bits = furi_hal_nfc_bitstream_to_data_and_parity( - temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity); - } else { - memcpy(tx_rx->rx_data, temp_rx_buff, MIN(*temp_rx_bits / 8, FURI_HAL_NFC_DATA_BUFF_SIZE)); - tx_rx->rx_bits = *temp_rx_bits; - } +FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits) { + furi_assert(tx_data); - if(tx_rx->sniff_rx) { - bool crc_dropped = !(flags & RFAL_TXRX_FLAGS_CRC_RX_KEEP); - tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, crc_dropped, tx_rx->sniff_context); - } + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); - return true; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.tx(handle, tx_data, tx_bits); } -bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx) { - uint16_t part_len_bytes; +FuriHalNfcError furi_hal_nfc_common_fifo_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; - if(!furi_hal_nfc_tx_rx(tx_rx, 1000)) { - return false; - } - while(tx_rx->rx_bits && tx_rx->rx_data[0] == 0xAF) { - FuriHalNfcTxRxContext tmp = *tx_rx; - tmp.tx_data[0] = 0xAF; - tmp.tx_bits = 8; - if(!furi_hal_nfc_tx_rx(&tmp, 1000)) { - return false; - } - part_len_bytes = tmp.rx_bits / 8; - if(part_len_bytes > FURI_HAL_NFC_DATA_BUFF_SIZE - tx_rx->rx_bits / 8) { - FURI_LOG_W(TAG, "Overrun rx buf"); - return false; - } - if(part_len_bytes == 0) { - FURI_LOG_W(TAG, "Empty 0xAF response"); - return false; - } - memcpy(tx_rx->rx_data + tx_rx->rx_bits / 8, tmp.rx_data + 1, part_len_bytes - 1); - tx_rx->rx_data[0] = tmp.rx_data[0]; - tx_rx->rx_bits += 8 * (part_len_bytes - 1); + if(!st25r3916_read_fifo(handle, rx_data, rx_data_size, rx_bits)) { + error = FuriHalNfcErrorBufferOverflow; } - return true; + return error; } -void furi_hal_nfc_sleep() { - rfalNfcDeactivate(false); - rfalLowPowerModeStart(); -} +FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { + furi_assert(rx_data); + furi_assert(rx_bits); -FuriHalNfcReturn - furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR) { - return rfalSetMode((rfalMode)mode, (rfalBitRate)txBR, (rfalBitRate)rxBR); -} + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); -void furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandling eHandling) { - rfalSetErrorHandling((rfalEHandling)eHandling); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.rx( + handle, rx_data, rx_data_size, rx_bits); } -void furi_hal_nfc_ll_set_guard_time(uint32_t cycles) { - rfalSetGT(cycles); -} +FuriHalNfcError furi_hal_nfc_trx_reset() { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; -void furi_hal_nfc_ll_set_fdt_listen(uint32_t cycles) { - rfalSetFDTListen(cycles); -} + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); -void furi_hal_nfc_ll_set_fdt_poll(uint32_t FDTPoll) { - rfalSetFDTPoll(FDTPoll); + return FuriHalNfcErrorNone; } -void furi_hal_nfc_ll_txrx_on() { - st25r3916TxRxOn(); -} +FuriHalNfcError furi_hal_nfc_listener_sleep() { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); -void furi_hal_nfc_ll_txrx_off() { - st25r3916TxRxOff(); -} + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; -FuriHalNfcReturn furi_hal_nfc_ll_txrx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - return rfalTransceiveBlockingTxRx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.sleep(handle); } -FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - return rfalTransceiveBitsBlockingTxRx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); -} +FuriHalNfcError furi_hal_nfc_listener_idle() { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); -void furi_hal_nfc_ll_poll() { - rfalWorker(); -} + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; -void furi_hal_nfc_field_detect_start() { - st25r3916WriteRegister( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask); - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om0); + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.idle(handle); } -bool furi_hal_nfc_field_is_present() { - return st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_efd_o, - ST25R3916_REG_AUX_DISPLAY_efd_o); -} \ No newline at end of file +FuriHalNfcError furi_hal_nfc_listener_enable_rx() { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); + + return FuriHalNfcErrorNone; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.h b/firmware/targets/f7/furi_hal/furi_hal_nfc.h deleted file mode 100644 index f4051926a5..0000000000 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.h +++ /dev/null @@ -1,431 +0,0 @@ -/** - * @file furi_hal_nfc.h - * NFC HAL API - */ - -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif -#include -#include - -#define FURI_HAL_NFC_UID_MAX_LEN 10 -#define FURI_HAL_NFC_DATA_BUFF_SIZE (512) -#define FURI_HAL_NFC_PARITY_BUFF_SIZE (FURI_HAL_NFC_DATA_BUFF_SIZE / 8) - -#define FURI_HAL_NFC_TXRX_DEFAULT \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO) - -#define FURI_HAL_NFC_TX_DEFAULT_RX_NO_CRC \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO) - -#define FURI_HAL_NFC_TXRX_WITH_PAR \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO) - -#define FURI_HAL_NFC_TXRX_RAW \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) - -#define FURI_HAL_NFC_TX_RAW_RX_DEFAULT \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) - -typedef enum { - FuriHalNfcTxRxTypeDefault, - FuriHalNfcTxRxTypeRxNoCrc, - FuriHalNfcTxRxTypeRxKeepPar, - FuriHalNfcTxRxTypeRaw, - FuriHalNfcTxRxTypeRxRaw, - FuriHalNfcTxRxTransparent, -} FuriHalNfcTxRxType; - -typedef bool (*FuriHalNfcEmulateCallback)( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len, - uint32_t* flags, - void* context); - -typedef enum { - FuriHalNfcTypeA, - FuriHalNfcTypeB, - FuriHalNfcTypeF, - FuriHalNfcTypeV, -} FuriHalNfcType; - -typedef enum { - FuriHalNfcInterfaceRf, - FuriHalNfcInterfaceIsoDep, - FuriHalNfcInterfaceNfcDep, -} FuriHalNfcInterface; - -typedef struct { - FuriHalNfcType type; - FuriHalNfcInterface interface; - uint8_t uid_len; - uint8_t uid[10]; - uint32_t cuid; - uint8_t atqa[2]; - uint8_t sak; -} FuriHalNfcDevData; - -typedef void ( - *FuriHalNfcTxRxSniffCallback)(uint8_t* data, uint16_t bits, bool crc_dropped, void* context); - -typedef struct { - uint8_t tx_data[FURI_HAL_NFC_DATA_BUFF_SIZE]; - uint8_t tx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; - uint16_t tx_bits; - uint8_t rx_data[FURI_HAL_NFC_DATA_BUFF_SIZE]; - uint8_t rx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; - uint16_t rx_bits; - FuriHalNfcTxRxType tx_rx_type; - NfcaSignal* nfca_signal; - - FuriHalNfcTxRxSniffCallback sniff_tx; - FuriHalNfcTxRxSniffCallback sniff_rx; - void* sniff_context; -} FuriHalNfcTxRxContext; - -/** Init nfc - */ -void furi_hal_nfc_init(); - -/** Deinit nfc - */ -void furi_hal_nfc_deinit(); - -/** Check if nfc worker is busy - * - * @return true if busy - */ -bool furi_hal_nfc_is_busy(); - -/** Check if nfc is initialized - * - * @return true if initialized - */ -bool furi_hal_nfc_is_init(); - -/** NFC field on - */ -void furi_hal_nfc_field_on(); - -/** NFC field off - */ -void furi_hal_nfc_field_off(); - -/** NFC start sleep - */ -void furi_hal_nfc_start_sleep(); - -void furi_hal_nfc_stop_cmd(); - -/** NFC stop sleep - */ -void furi_hal_nfc_exit_sleep(); - -/** NFC poll - * - * @param dev_list pointer to rfalNfcDevice buffer - * @param dev_cnt pointer device count - * @param timeout timeout in ms - * @param deactivate deactivate flag - * - * @return true on success - */ -bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout); - -/** Activate NFC-A tag - * - * @param timeout timeout in ms - * @param cuid pointer to 32bit uid - * - * @return true on succeess - */ -bool furi_hal_nfc_activate_nfca(uint32_t timeout, uint32_t* cuid); - -/** NFC listen - * - * @param uid pointer to uid buffer - * @param uid_len uid length - * @param atqa pointer to atqa - * @param sak sak - * @param activate_after_sak activate after sak flag - * @param timeout timeout in ms - * - * @return true on success - */ -bool furi_hal_nfc_listen( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - bool activate_after_sak, - uint32_t timeout); - -/** Start Target Listen mode - * @note RFAL free implementation - * - * @param nfc_data FuriHalNfcDevData instance - */ -void furi_hal_nfc_listen_start(FuriHalNfcDevData* nfc_data); - -/** Read data in Target Listen mode - * @note Must be called only after furi_hal_nfc_listen_start() - * - * @param tx_rx FuriHalNfcTxRxContext instance - * @param timeout_ms timeout im ms - * - * @return true on not empty receive - */ -bool furi_hal_nfc_listen_rx(FuriHalNfcTxRxContext* tx_rx, uint32_t timeout_ms); - -/** Set Target in Sleep state */ -void furi_hal_nfc_listen_sleep(); - -/** Emulate NFC-A Target - * @note RFAL based implementation - * - * @param uid NFC-A UID - * @param uid_len NFC-A UID length - * @param atqa NFC-A ATQA - * @param sak NFC-A SAK - * @param callback FuriHalNfcEmulateCallback instance - * @param context pointer to context for callback - * @param timeout timeout in ms - * - * @return true on success - */ -bool furi_hal_nfc_emulate_nfca( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - FuriHalNfcEmulateCallback callback, - void* context, - uint32_t timeout); - -/** NFC data exchange - * - * @param tx_rx_ctx FuriHalNfcTxRxContext instance - * - * @return true on success - */ -bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms); - -/** NFC data full exhange - * - * @param tx_rx_ctx FuriHalNfcTxRxContext instance - * - * @return true on success - */ -bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx); - -/** NFC deactivate and start sleep - */ -void furi_hal_nfc_sleep(); - -void furi_hal_nfc_stop(); - -/* Low level transport API, use it to implement your own transport layers */ - -#define furi_hal_nfc_ll_ms2fc rfalConvMsTo1fc - -#define FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL RFAL_TXRX_FLAGS_CRC_TX_MANUAL -#define FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON RFAL_TXRX_FLAGS_AGC_ON -#define FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV RFAL_TXRX_FLAGS_PAR_RX_REMV -#define FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP RFAL_TXRX_FLAGS_CRC_RX_KEEP - -typedef enum { - FuriHalNfcReturnOk = 0, /*!< no error occurred */ - FuriHalNfcReturnNomem = 1, /*!< not enough memory to perform the requested operation */ - FuriHalNfcReturnBusy = 2, /*!< device or resource busy */ - FuriHalNfcReturnIo = 3, /*!< generic IO error */ - FuriHalNfcReturnTimeout = 4, /*!< error due to timeout */ - FuriHalNfcReturnRequest = - 5, /*!< invalid request or requested function can't be executed at the moment */ - FuriHalNfcReturnNomsg = 6, /*!< No message of desired type */ - FuriHalNfcReturnParam = 7, /*!< Parameter error */ - FuriHalNfcReturnSystem = 8, /*!< System error */ - FuriHalNfcReturnFraming = 9, /*!< Framing error */ - FuriHalNfcReturnOverrun = 10, /*!< lost one or more received bytes */ - FuriHalNfcReturnProto = 11, /*!< protocol error */ - FuriHalNfcReturnInternal = 12, /*!< Internal Error */ - FuriHalNfcReturnAgain = 13, /*!< Call again */ - FuriHalNfcReturnMemCorrupt = 14, /*!< memory corruption */ - FuriHalNfcReturnNotImplemented = 15, /*!< not implemented */ - FuriHalNfcReturnPcCorrupt = - 16, /*!< Program Counter has been manipulated or spike/noise trigger illegal operation */ - FuriHalNfcReturnSend = 17, /*!< error sending*/ - FuriHalNfcReturnIgnore = 18, /*!< indicates error detected but to be ignored */ - FuriHalNfcReturnSemantic = 19, /*!< indicates error in state machine (unexpected cmd) */ - FuriHalNfcReturnSyntax = 20, /*!< indicates error in state machine (unknown cmd) */ - FuriHalNfcReturnCrc = 21, /*!< crc error */ - FuriHalNfcReturnNotfound = 22, /*!< transponder not found */ - FuriHalNfcReturnNotunique = - 23, /*!< transponder not unique - more than one transponder in field */ - FuriHalNfcReturnNotsupp = 24, /*!< requested operation not supported */ - FuriHalNfcReturnWrite = 25, /*!< write error */ - FuriHalNfcReturnFifo = 26, /*!< fifo over or underflow error */ - FuriHalNfcReturnPar = 27, /*!< parity error */ - FuriHalNfcReturnDone = 28, /*!< transfer has already finished */ - FuriHalNfcReturnRfCollision = - 29, /*!< collision error (Bit Collision or during RF Collision avoidance ) */ - FuriHalNfcReturnHwOverrun = 30, /*!< lost one or more received bytes */ - FuriHalNfcReturnReleaseReq = 31, /*!< device requested release */ - FuriHalNfcReturnSleepReq = 32, /*!< device requested sleep */ - FuriHalNfcReturnWrongState = 33, /*!< incorrent state for requested operation */ - FuriHalNfcReturnMaxReruns = 34, /*!< blocking procedure reached maximum runs */ - FuriHalNfcReturnDisabled = 35, /*!< operation aborted due to disabled configuration */ - FuriHalNfcReturnHwMismatch = 36, /*!< expected hw do not match */ - FuriHalNfcReturnLinkLoss = - 37, /*!< Other device's field didn't behave as expected: turned off by Initiator in Passive mode, or AP2P did not turn on field */ - FuriHalNfcReturnInvalidHandle = 38, /*!< invalid or not initalized device handle */ - FuriHalNfcReturnIncompleteByte = 40, /*!< Incomplete byte rcvd */ - FuriHalNfcReturnIncompleteByte01 = 41, /*!< Incomplete byte rcvd - 1 bit */ - FuriHalNfcReturnIncompleteByte02 = 42, /*!< Incomplete byte rcvd - 2 bit */ - FuriHalNfcReturnIncompleteByte03 = 43, /*!< Incomplete byte rcvd - 3 bit */ - FuriHalNfcReturnIncompleteByte04 = 44, /*!< Incomplete byte rcvd - 4 bit */ - FuriHalNfcReturnIncompleteByte05 = 45, /*!< Incomplete byte rcvd - 5 bit */ - FuriHalNfcReturnIncompleteByte06 = 46, /*!< Incomplete byte rcvd - 6 bit */ - FuriHalNfcReturnIncompleteByte07 = 47, /*!< Incomplete byte rcvd - 7 bit */ -} FuriHalNfcReturn; - -typedef enum { - FuriHalNfcModeNone = 0, /*!< No mode selected/defined */ - FuriHalNfcModePollNfca = 1, /*!< Mode to perform as NFCA (ISO14443A) Poller (PCD) */ - FuriHalNfcModePollNfcaT1t = 2, /*!< Mode to perform as NFCA T1T (Topaz) Poller (PCD) */ - FuriHalNfcModePollNfcb = 3, /*!< Mode to perform as NFCB (ISO14443B) Poller (PCD) */ - FuriHalNfcModePollBPrime = 4, /*!< Mode to perform as B' Calypso (Innovatron) (PCD) */ - FuriHalNfcModePollBCts = 5, /*!< Mode to perform as CTS Poller (PCD) */ - FuriHalNfcModePollNfcf = 6, /*!< Mode to perform as NFCF (FeliCa) Poller (PCD) */ - FuriHalNfcModePollNfcv = 7, /*!< Mode to perform as NFCV (ISO15963) Poller (PCD) */ - FuriHalNfcModePollPicopass = 8, /*!< Mode to perform as PicoPass / iClass Poller (PCD) */ - FuriHalNfcModePollActiveP2p = 9, /*!< Mode to perform as Active P2P (ISO18092) Initiator */ - FuriHalNfcModeListenNfca = 10, /*!< Mode to perform as NFCA (ISO14443A) Listener (PICC) */ - FuriHalNfcModeListenNfcb = 11, /*!< Mode to perform as NFCA (ISO14443B) Listener (PICC) */ - FuriHalNfcModeListenNfcf = 12, /*!< Mode to perform as NFCA (ISO15963) Listener (PICC) */ - FuriHalNfcModeListenActiveP2p = 13 /*!< Mode to perform as Active P2P (ISO18092) Target */ -} FuriHalNfcMode; - -typedef enum { - FuriHalNfcBitrate106 = 0, /*!< Bit Rate 106 kbit/s (fc/128) */ - FuriHalNfcBitrate212 = 1, /*!< Bit Rate 212 kbit/s (fc/64) */ - FuriHalNfcBitrate424 = 2, /*!< Bit Rate 424 kbit/s (fc/32) */ - FuriHalNfcBitrate848 = 3, /*!< Bit Rate 848 kbit/s (fc/16) */ - FuriHalNfcBitrate1695 = 4, /*!< Bit Rate 1695 kbit/s (fc/8) */ - FuriHalNfcBitrate3390 = 5, /*!< Bit Rate 3390 kbit/s (fc/4) */ - FuriHalNfcBitrate6780 = 6, /*!< Bit Rate 6780 kbit/s (fc/2) */ - FuriHalNfcBitrate13560 = 7, /*!< Bit Rate 13560 kbit/s (fc) */ - FuriHalNfcBitrate52p97 = 0xEB, /*!< Bit Rate 52.97 kbit/s (fc/256) Fast Mode VICC->VCD */ - FuriHalNfcBitrate26p48 = - 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */ - FuriHalNfcBitrate1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */ - FuriHalNfcBitrateKeep = 0xFF /*!< Value indicating to keep the same previous bit rate */ -} FuriHalNfcBitrate; - -FuriHalNfcReturn - furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR); - -#define FURI_HAL_NFC_LL_GT_NFCA furi_hal_nfc_ll_ms2fc(5U) /*!< GTA Digital 2.0 6.10.4.1 & B.2 */ -#define FURI_HAL_NFC_LL_GT_NFCB furi_hal_nfc_ll_ms2fc(5U) /*!< GTB Digital 2.0 7.9.4.1 & B.3 */ -#define FURI_HAL_NFC_LL_GT_NFCF furi_hal_nfc_ll_ms2fc(20U) /*!< GTF Digital 2.0 8.7.4.1 & B.4 */ -#define FURI_HAL_NFC_LL_GT_NFCV furi_hal_nfc_ll_ms2fc(5U) /*!< GTV Digital 2.0 9.7.5.1 & B.5 */ -#define FURI_HAL_NFC_LL_GT_PICOPASS furi_hal_nfc_ll_ms2fc(1U) /*!< GT Picopass */ -#define FURI_HAL_NFC_LL_GT_AP2P furi_hal_nfc_ll_ms2fc(5U) /*!< TIRFG Ecma 340 11.1.1 */ -#define FURI_HAL_NFC_LL_GT_AP2P_ADJUSTED \ - furi_hal_nfc_ll_ms2fc( \ - 5U + \ - 25U) /*!< Adjusted GT for greater interoperability (Sony XPERIA P, Nokia N9, Huawei P2) */ - -void furi_hal_nfc_ll_set_guard_time(uint32_t cycles); - -typedef enum { - FuriHalNfcErrorHandlingNone = 0, /*!< No special error handling will be performed */ - FuriHalNfcErrorHandlingNfc = 1, /*!< Error handling set to perform as NFC compliant device */ - FuriHalNfcErrorHandlingEmvco = - 2 /*!< Error handling set to perform as EMVCo compliant device */ -} FuriHalNfcErrorHandling; - -void furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandling eHandling); - -/* RFAL Frame Delay Time (FDT) Listen default values */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER \ - 1172U /*!< FDTA,LISTEN,MIN (n=9) Last bit: Logic "1" - tnn,min/2 Digital 1.1 6.10 ; EMV CCP Spec Book D v2.01 4.8.1.3 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_POLLER \ - 1008U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_POLLER \ - 2672U /*!< TR0F,LISTEN,MIN Digital 1.1 8.7.1.1 & A.4 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER \ - 4310U /*!< FDTV,LISTEN,MIN t1 min Digital 2.1 B.5 ; ISO15693-3 2009 9.1 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER \ - 3400U /*!< ISO15693 t1 min - observed adjustment */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_POLLER \ - 64U /*!< FDT AP2P No actual FDTListen is required as fields switch and collision avoidance */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_LISTENER 1172U /*!< FDTA,LISTEN,MIN Digital 1.1 6.10 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_LISTENER \ - 1024U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_LISTENER \ - 2688U /*!< TR0F,LISTEN,MIN Digital 2.1 8.7.1.1 & B.4 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_LISTENER \ - 64U /*!< FDT AP2P No actual FDTListen exists as fields switch and collision avoidance */ - -void furi_hal_nfc_ll_set_fdt_listen(uint32_t cycles); - -/* RFAL Frame Delay Time (FDT) Poll default values */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER \ - 6780U /*!< FDTA,POLL,MIN Digital 1.1 6.10.3.1 & A.2 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_T1T_POLLER \ - 384U /*!< RRDDT1T,MIN,B1 Digital 1.1 10.7.1 & A.5 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCB_POLLER \ - 6780U /*!< FDTB,POLL,MIN = TR2B,MIN,DEFAULT Digital 1.1 7.9.3 & A.3 ; EMVCo 3.0 FDTB,PCD,MIN Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCF_POLLER 6800U /*!< FDTF,POLL,MIN Digital 2.1 8.7.3 & B.4 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER 4192U /*!< FDTV,POLL Digital 2.1 9.7.3.1 & B.5 */ -#define FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER 1790U /*!< FDT Max */ -#define FURI_HAL_NFC_LL_FDT_POLL_AP2P_POLLER \ - 0U /*!< FDT AP2P No actual FDTPoll exists as fields switch and collision avoidance */ - -void furi_hal_nfc_ll_set_fdt_poll(uint32_t FDTPoll); - -void furi_hal_nfc_ll_txrx_on(); - -void furi_hal_nfc_ll_txrx_off(); - -FuriHalNfcReturn furi_hal_nfc_ll_txrx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -void furi_hal_nfc_ll_poll(); - -void furi_hal_nfc_field_detect_start(); - -bool furi_hal_nfc_field_is_present(); - -#ifdef __cplusplus -} -#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c new file mode 100644 index 0000000000..cce16c5dc9 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c @@ -0,0 +1,116 @@ +#include + +FuriHalNfcEventInternal* furi_hal_nfc_event = NULL; + +void furi_hal_nfc_event_init() { + furi_hal_nfc_event = malloc(sizeof(FuriHalNfcEventInternal)); +} + +FuriHalNfcError furi_hal_nfc_event_start() { + furi_assert(furi_hal_nfc_event); + + furi_hal_nfc_event->thread = furi_thread_get_current_id(); + furi_thread_flags_clear(FURI_HAL_NFC_EVENT_INTERNAL_ALL); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_event_stop() { + furi_assert(furi_hal_nfc_event); + + furi_hal_nfc_event->thread = NULL; + + return FuriHalNfcErrorNone; +} + +void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event) { + furi_assert(furi_hal_nfc_event); + furi_assert(furi_hal_nfc_event->thread); + + furi_thread_flags_set(furi_hal_nfc_event->thread, event); +} + +FuriHalNfcError furi_hal_nfc_abort() { + furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeAbort); + return FuriHalNfcErrorNone; +} + +FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) { + furi_assert(furi_hal_nfc_event); + furi_assert(furi_hal_nfc_event->thread); + + FuriHalNfcEvent event = 0; + uint32_t event_timeout = timeout_ms == FURI_HAL_NFC_EVENT_WAIT_FOREVER ? FuriWaitForever : + timeout_ms; + uint32_t event_flag = + furi_thread_flags_wait(FURI_HAL_NFC_EVENT_INTERNAL_ALL, FuriFlagWaitAny, event_timeout); + if(event_flag != (unsigned)FuriFlagErrorTimeout) { + if(event_flag & FuriHalNfcEventInternalTypeIrq) { + furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + uint32_t irq = furi_hal_nfc_get_irq(handle); + if(irq & ST25R3916_IRQ_MASK_OSC) { + event |= FuriHalNfcEventOscOn; + } + if(irq & ST25R3916_IRQ_MASK_TXE) { + event |= FuriHalNfcEventTxEnd; + } + if(irq & ST25R3916_IRQ_MASK_RXS) { + event |= FuriHalNfcEventRxStart; + } + if(irq & ST25R3916_IRQ_MASK_RXE) { + event |= FuriHalNfcEventRxEnd; + } + if(irq & ST25R3916_IRQ_MASK_COL) { + event |= FuriHalNfcEventCollision; + } + if(irq & ST25R3916_IRQ_MASK_EON) { + event |= FuriHalNfcEventFieldOn; + } + if(irq & ST25R3916_IRQ_MASK_EOF) { + event |= FuriHalNfcEventFieldOff; + } + if(irq & ST25R3916_IRQ_MASK_WU_A) { + event |= FuriHalNfcEventListenerActive; + } + if(irq & ST25R3916_IRQ_MASK_WU_A_X) { + event |= FuriHalNfcEventListenerActive; + } + } + if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) { + event |= FuriHalNfcEventTimerFwtExpired; + furi_thread_flags_clear(FuriHalNfcEventInternalTypeTimerFwtExpired); + } + if(event_flag & FuriHalNfcEventInternalTypeTimerBlockTxExpired) { + event |= FuriHalNfcEventTimerBlockTxExpired; + furi_thread_flags_clear(FuriHalNfcEventInternalTypeTimerBlockTxExpired); + } + if(event_flag & FuriHalNfcEventInternalTypeAbort) { + event |= FuriHalNfcEventAbortRequest; + furi_thread_flags_clear(FuriHalNfcEventInternalTypeAbort); + } + } else { + event = FuriHalNfcEventTimeout; + } + + return event; +} + +bool furi_hal_nfc_event_wait_for_specific_irq( + FuriHalSpiBusHandle* handle, + uint32_t mask, + uint32_t timeout_ms) { + furi_assert(furi_hal_nfc_event); + furi_assert(furi_hal_nfc_event->thread); + + bool irq_received = false; + uint32_t event_flag = + furi_thread_flags_wait(FuriHalNfcEventInternalTypeIrq, FuriFlagWaitAny, timeout_ms); + if(event_flag == FuriHalNfcEventInternalTypeIrq) { + uint32_t irq = furi_hal_nfc_get_irq(handle); + irq_received = ((irq & mask) == mask); + furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq); + } + + return irq_received; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c new file mode 100644 index 0000000000..e4b8ac0ee6 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -0,0 +1,69 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) { + // Enable Felica mode, AM modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_felica | ST25R3916_REG_MODE_tr_am_am); + + // 10% ASK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_TX_DRIVER, + ST25R3916_REG_TX_DRIVER_am_mod_mask, + ST25R3916_REG_TX_DRIVER_am_mod_10percent); + + // Use regulator AM, resistive AM disabled + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am); + + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_BIT_RATE, + ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask, + ST25R3916_REG_BIT_RATE_txrate_212 | ST25R3916_REG_BIT_RATE_rxrate_212); + + // Receive configuration + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF1, + ST25R3916_REG_RX_CONF1_lp0 | ST25R3916_REG_RX_CONF1_hz_12_80khz); + + // Correlator setup + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s6 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s3); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_felica = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_felica_poller_init, + .deinit = furi_hal_nfc_felica_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_poller_tx_common, + .rx = furi_hal_nfc_common_fifo_rx, + }, + + .listener = {}, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h b/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h new file mode 100644 index 0000000000..53a25644d0 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h @@ -0,0 +1,191 @@ +/** + * @file furi_hal_nfc_i.h + * @brief NFC HAL library (private definitions). + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#pragma once + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Common frame delay time compensation for pollers. */ +#define FURI_HAL_NFC_POLLER_FDT_COMP_FC (-500) +/** @brief Common frame wait time compensation for pollers. */ +#define FURI_HAL_NFC_POLLER_FWT_COMP_FC (FURI_HAL_NFC_POLLER_FDT_COMP_FC) + +/** + * @brief Enumeration containing bitmask values for NFC HAL internal events. + */ +typedef enum { + FuriHalNfcEventInternalTypeAbort = (1U << 0), /**< Abort waiting for hardware events. */ + FuriHalNfcEventInternalTypeIrq = (1U << 1), /**< NFC hardware interrupt has occurred. */ + FuriHalNfcEventInternalTypeTimerFwtExpired = + (1U << 2), /**< Frame wait time timeout has expired. */ + FuriHalNfcEventInternalTypeTimerBlockTxExpired = + (1U << 3), /**< Transmission block timeout has expired. */ + FuriHalNfcEventInternalTypeTransparentDataReceived = + (1U << 4), /**< Data was received in transparent mode. */ +} FuriHalNfcEventInternalType; + +/** @brief Special bitmask value of all internal events. */ +#define FURI_HAL_NFC_EVENT_INTERNAL_ALL \ + ((FuriHalNfcEventInternalTypeAbort | FuriHalNfcEventInternalTypeIrq | \ + FuriHalNfcEventInternalTypeTimerFwtExpired | \ + FuriHalNfcEventInternalTypeTimerBlockTxExpired | \ + FuriHalNfcEventInternalTypeTransparentDataReceived)) + +/** + * @brief NFC HAL internal event structure. + */ +typedef struct { + FuriThreadId thread; /**< Identifier of the thread that will be receiving events. */ + void* context; /**< Pointer to the user-provided context (will be passed to the event callback). */ +} FuriHalNfcEventInternal; + +/** + * @brief NFC HAL global state structure. + */ +typedef struct { + FuriMutex* mutex; /**< Pointer to the mutex serving as global NFC HAL lock. */ + FuriHalNfcMode mode; /**< Currently selected operating mode. */ + FuriHalNfcTech tech; /**< Currently selected NFC technology. */ +} FuriHalNfc; + +/** + * @brief NFC HAL global state object declaration. + */ +extern FuriHalNfc furi_hal_nfc; + +/** + * @brief Initialise NFC HAL event system. + */ +void furi_hal_nfc_event_init(); + +/** + * @brief Forcibly emit (a) particular internal event(s). + * + * @param[in] event bitmask of one or more events to be emitted. + */ +void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event); + +/** + * @brief Initialise GPIO to generate an interrupt from the NFC hardware. + */ +void furi_hal_nfc_init_gpio_isr(); + +/** + * @brief Disable interrupts from the NFC hardware. + */ +void furi_hal_nfc_deinit_gpio_isr(); + +/** + * @brief Initialise all NFC timers. + */ +void furi_hal_nfc_timers_init(); + +/** + * @brief Disable all NFC timers. + */ +void furi_hal_nfc_timers_deinit(); + +/** + * @brief Get the interrupt bitmask from the NFC hardware. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @returns bitmask of zero or more occurred interrupts. + */ +uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle); + +/** + * @brief Wait until a specified type of interrupt occurs. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[in] mask bitmask of one or more interrupts to wait for. + * @param[in] timeout_ms maximum time to wait for an interrupt, in milliseconds. + * @returns true if specified interrupt(s) have occured within timeout, false otherwise. + */ +bool furi_hal_nfc_event_wait_for_specific_irq( + FuriHalSpiBusHandle* handle, + uint32_t mask, + uint32_t timeout_ms); + +/** + * @brief Wait for any event to occur. + * + * This function is common to all technologies. + * + * @param[in] timeout_ms maximum time to wait for an event, in milliseconds. + * @returns bitmask of zero or more occurred events. + */ +FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms); + +/** + * @brief Start reception in listener mode. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_common_listener_rx_start(FuriHalSpiBusHandle* handle); + +/** + * @brief Transmit data using on-chip FIFO. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_common_fifo_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits); + +/** + * @brief Receive data using on-chip FIFO. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[out] rx_bits pointer to the variable to contain the received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_common_fifo_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits); + +/** + * @brief Transmit data in poller mode. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_tx_common( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c new file mode 100644 index 0000000000..63dc354155 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c @@ -0,0 +1,28 @@ +#include "furi_hal_nfc_i.h" + +#include +#include + +static void furi_hal_nfc_int_callback() { + furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeIrq); +} + +uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle) { + uint32_t irq = 0; + while(furi_hal_gpio_read_port_pin(gpio_nfc_irq_rfid_pull.port, gpio_nfc_irq_rfid_pull.pin)) { + irq |= st25r3916_get_irq(handle); + } + return irq; +} + +void furi_hal_nfc_init_gpio_isr() { + furi_hal_gpio_init( + &gpio_nfc_irq_rfid_pull, GpioModeInterruptRise, GpioPullDown, GpioSpeedVeryHigh); + furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, furi_hal_nfc_int_callback, NULL); + furi_hal_gpio_enable_int_callback(&gpio_nfc_irq_rfid_pull); +} + +void furi_hal_nfc_deinit_gpio_isr() { + furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_remove_int_callback(&gpio_nfc_irq_rfid_pull); +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c new file mode 100644 index 0000000000..278ecf3849 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c @@ -0,0 +1,356 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include +#include + +#include + +#define TAG "FuriHalIso14443a" + +// Prevent FDT timer from starting +#define FURI_HAL_NFC_ISO14443A_LISTENER_FDT_COMP_FC (INT32_MAX) + +static Iso14443_3aSignal* iso14443_3a_signal = NULL; + +static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(FuriHalSpiBusHandle* handle) { + // Common NFC-A settings, 106 kbps + + // 1st stage zero = 600kHz, 3rd stage zero = 200 kHz + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, ST25R3916_REG_RX_CONF1_z600k); + // AGC enabled, ratio 3:1, squelch after TX + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m | + ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn); + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + // Correlator config + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s6); + // Sleep mode disable, 424kHz mode off + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(FuriHalSpiBusHandle* handle) { + // Enable ISO14443A mode, OOK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_iso14443a | ST25R3916_REG_MODE_tr_am_ook); + + // Overshoot protection - is this necessary here? + st25r3916_change_reg_bits(handle, ST25R3916_REG_OVERSHOOT_CONF1, 0xff, 0x40); + st25r3916_change_reg_bits(handle, ST25R3916_REG_OVERSHOOT_CONF2, 0xff, 0x03); + st25r3916_change_reg_bits(handle, ST25R3916_REG_UNDERSHOOT_CONF1, 0xff, 0x40); + st25r3916_change_reg_bits(handle, ST25R3916_REG_UNDERSHOOT_CONF2, 0xff, 0x03); + + return furi_hal_nfc_iso14443a_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(FuriHalSpiBusHandle* handle) { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off)); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(FuriHalSpiBusHandle* handle) { + furi_check(iso14443_3a_signal == NULL); + iso14443_3a_signal = iso14443_3a_signal_alloc(&gpio_spi_r_mosi); + + st25r3916_write_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + st25r3916_write_reg( + handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om0); + st25r3916_write_reg( + handle, + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_fdel_2 | ST25R3916_REG_PASSIVE_TARGET_fdel_0 | + ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r); + + st25r3916_write_reg(handle, ST25R3916_REG_MASK_RX_TIMER, 0x02); + + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE | + ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X | + ST25R3916_IRQ_MASK_WU_A); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + // Enable auto collision resolution + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE); + + return furi_hal_nfc_iso14443a_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + + if(iso14443_3a_signal) { + iso14443_3a_signal_free(iso14443_3a_signal); + iso14443_3a_signal = NULL; + } + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcEvent furi_hal_nfc_iso14443_3a_listener_wait_event(uint32_t timeout_ms) { + FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + if(event & FuriHalNfcEventListenerActive) { + st25r3916_set_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + } + + return event; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrame frame) { + FuriHalNfcError error = FuriHalNfcErrorNone; + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + // Disable crc check + st25r3916_set_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off)); + + st25r3916_write_reg(handle, ST25R3916_REG_NUM_TX_BYTES2, 0); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + if(frame == FuriHalNfcaShortFrameAllReq) { + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_REQA); + } else { + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WUPA); + } + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_tx_sdd_frame(const uint8_t* tx_data, size_t tx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + // No anticollision is supported in this version of library + error = furi_hal_nfc_poller_tx(tx_data, tx_bits); + + return error; +} + +FuriHalNfcError + furi_hal_nfc_iso14443a_rx_sdd_frame(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + UNUSED(rx_data); + UNUSED(rx_bits); + UNUSED(rx_data_size); + + error = furi_hal_nfc_poller_rx(rx_data, rx_data_size, rx_bits); + // No anticollision is supported in this version of library + + return error; +} + +FuriHalNfcError + furi_hal_nfc_iso14443a_poller_tx_custom_parity(const uint8_t* tx_data, size_t tx_bits) { + furi_assert(tx_data); + + FuriHalNfcError err = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + // Prepare tx + st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par)); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + + st25r3916_write_fifo(handle, tx_data, tx_bits); + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); + return err; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + furi_assert(uid); + furi_assert(atqa); + UNUSED(uid_len); + UNUSED(sak); + FuriHalNfcError error = FuriHalNfcErrorNone; + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + // Set 4 or 7 bytes UID + if(uid_len == 4) { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX, + ST25R3916_REG_AUX_nfc_id_mask, + ST25R3916_REG_AUX_nfc_id_4bytes); + } else { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX, + ST25R3916_REG_AUX_nfc_id_mask, + ST25R3916_REG_AUX_nfc_id_7bytes); + } + // Write PT Memory + uint8_t pt_memory[15] = {}; + memcpy(pt_memory, uid, uid_len); + pt_memory[10] = atqa[0]; + pt_memory[11] = atqa[1]; + if(uid_len == 4) { + pt_memory[12] = sak & ~0x04; + } else { + pt_memory[12] = 0x04; + } + pt_memory[13] = sak & ~0x04; + pt_memory[14] = sak & ~0x04; + + st25r3916_write_pta_mem(handle, pt_memory, sizeof(pt_memory)); + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso4443a_listener_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + + do { + error = furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits); + if(error != FuriHalNfcErrorNone) break; + + bool tx_end = furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_TXE, 10); + if(!tx_end) { + error = FuriHalNfcErrorCommunicationTimeout; + break; + } + + } while(false); + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits) { + furi_assert(tx_data); + furi_assert(tx_parity); + + furi_assert(iso14443_3a_signal); + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); + // Reconfigure gpio for Transparent mode + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + + // Send signal + iso14443_3a_signal_tx(iso14443_3a_signal, tx_data, tx_parity, tx_bits); + + // Exit transparent mode + furi_hal_gpio_write(&gpio_spi_r_mosi, false); + + // Configure gpio back to SPI and exit transparent + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(FuriHalSpiBusHandle* handle) { + // Enable auto collision resolution + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SLEEP); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_idle(FuriHalSpiBusHandle* handle) { + // Enable auto collision resolution + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE); + + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_iso14443a = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_iso14443a_poller_init, + .deinit = furi_hal_nfc_iso14443a_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_poller_tx_common, + .rx = furi_hal_nfc_common_fifo_rx, + }, + + .listener = + { + .compensation = + { + .fdt = FURI_HAL_NFC_ISO14443A_LISTENER_FDT_COMP_FC, + }, + .init = furi_hal_nfc_iso14443a_listener_init, + .deinit = furi_hal_nfc_iso14443a_listener_deinit, + .wait_event = furi_hal_nfc_iso14443_3a_listener_wait_event, + .tx = furi_hal_nfc_iso4443a_listener_tx, + .rx = furi_hal_nfc_common_fifo_rx, + .sleep = furi_hal_nfc_iso14443_3a_listener_sleep, + .idle = furi_hal_nfc_iso14443_3a_listener_idle, + }, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c new file mode 100644 index 0000000000..bb1a63515b --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c @@ -0,0 +1,108 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(FuriHalSpiBusHandle* handle) { + // Common NFC-B settings, 106kbps + + // 1st stage zero = 60kHz, 3rd stage zero = 200 kHz + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, ST25R3916_REG_RX_CONF1_h200); + + // Enable AGC + // AGC Ratio 6 + // AGC algorithm with RESET (recommended for ISO14443-B) + // AGC operation during complete receive period + // Squelch ratio 6/3 (recommended for ISO14443-B) + // Squelch automatic activation on TX end + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_alg | + ST25R3916_REG_RX_CONF2_agc_m | ST25R3916_REG_RX_CONF2_agc_en | + ST25R3916_REG_RX_CONF2_pulz_61 | ST25R3916_REG_RX_CONF2_sqm_dyn); + + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + + // Subcarrier end detector enabled + // Subcarrier end detection level = 66% + // BPSK start 33 pilot pulses + // AM & PM summation before digitizing on + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s1 | + ST25R3916_REG_CORR_CONF1_corr_s3 | ST25R3916_REG_CORR_CONF1_corr_s4); + // Sleep mode disable, 424kHz mode off + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(FuriHalSpiBusHandle* handle) { + // Enable ISO14443B mode, AM modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_iso14443b | ST25R3916_REG_MODE_tr_am_am); + + // 10% ASK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_TX_DRIVER, + ST25R3916_REG_TX_DRIVER_am_mod_mask, + ST25R3916_REG_TX_DRIVER_am_mod_10percent); + + // Use regulator AM, resistive AM disabled + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am); + + // EGT = 0 etu + // SOF = 10 etu LOW + 2 etu HIGH + // EOF = 10 etu + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443B_1, + ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | + ST25R3916_REG_ISO14443B_1_eof, + (0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | + ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu); + + // TR1 = 80 / fs + // B' mode off (no_sof & no_eof = 0) + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443B_2, + ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | + ST25R3916_REG_ISO14443B_2_no_eof, + ST25R3916_REG_ISO14443B_2_tr1_80fs80fs); + + return furi_hal_nfc_iso14443b_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_iso14443b = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_iso14443b_poller_init, + .deinit = furi_hal_nfc_iso14443b_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_poller_tx_common, + .rx = furi_hal_nfc_common_fifo_rx, + }, + + .listener = {}, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c new file mode 100644 index 0000000000..19ac6dc034 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -0,0 +1,463 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include +#include + +#include + +#define FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE (1024U) +#define FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE (64) + +#define FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE (5) +#define FURI_HAL_NFC_ISO15693_RESP_EOF_SIZE (5) +#define FURI_HAL_NFC_ISO15693_RESP_SOF_MASK (0x1FU) +#define FURI_HAL_NFC_ISO15693_RESP_SOF_PATTERN (0x17U) +#define FURI_HAL_NFC_ISO15693_RESP_EOF_PATTERN (0x1DU) + +#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_MASK (0x03U) +#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_0 (0x01U) +#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_1 (0x02U) + +// Derived experimentally +#define FURI_HAL_NFC_ISO15693_POLLER_FWT_COMP_FC (-1300) +#define FURI_HAL_NFC_ISO15693_LISTENER_FDT_COMP_FC (2850) + +#define BITS_IN_BYTE (8U) + +#define TAG "FuriHalIso15693" + +typedef struct { + Iso15693Signal* signal; + Iso15693Parser* parser; +} FuriHalNfcIso15693Listener; + +typedef struct { + // 4 bits per data bit on transmit + uint8_t fifo_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 4]; + size_t fifo_buf_bits; + uint8_t frame_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 2]; + size_t frame_buf_bits; +} FuriHalNfcIso15693Poller; + +static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener = NULL; +static FuriHalNfcIso15693Poller* furi_hal_nfc_iso15693_poller = NULL; + +static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener_alloc() { + FuriHalNfcIso15693Listener* instance = malloc(sizeof(FuriHalNfcIso15693Listener)); + + instance->signal = iso15693_signal_alloc(&gpio_spi_r_mosi); + instance->parser = + iso15693_parser_alloc(&gpio_spi_r_miso, FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE); + + return instance; +} + +static void furi_hal_nfc_iso15693_listener_free(FuriHalNfcIso15693Listener* instance) { + furi_assert(instance); + + iso15693_signal_free(instance->signal); + iso15693_parser_free(instance->parser); + + free(instance); +} + +static FuriHalNfcIso15693Poller* furi_hal_nfc_iso15693_poller_alloc() { + FuriHalNfcIso15693Poller* instance = malloc(sizeof(FuriHalNfcIso15693Poller)); + + return instance; +} + +static void furi_hal_nfc_iso15693_poller_free(FuriHalNfcIso15693Poller* instance) { + furi_assert(instance); + + free(instance); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_common_init(FuriHalSpiBusHandle* handle) { + // Common NFC-V settings, 26.48 kbps + + // 1st stage zero = 12 kHz, 3rd stage zero = 80 kHz, low-pass = 600 kHz + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF1, + ST25R3916_REG_RX_CONF1_z12k | ST25R3916_REG_RX_CONF1_h80 | + ST25R3916_REG_RX_CONF1_lp_600khz); + + // Enable AGC + // AGC Ratio 6 + // AGC algorithm with RESET (recommended for ISO15693) + // AGC operation during complete receive period + // Squelch automatic activation on TX end + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m | + ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn); + + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + + // Collision detection level 53% + // AM & PM summation before digitizing on + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s1 | + ST25R3916_REG_CORR_CONF1_corr_s4); + // 424 kHz subcarrier stream mode on + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, ST25R3916_REG_CORR_CONF2_corr_s8); + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(FuriHalSpiBusHandle* handle) { + furi_assert(furi_hal_nfc_iso15693_poller == NULL); + + furi_hal_nfc_iso15693_poller = furi_hal_nfc_iso15693_poller_alloc(); + + // Enable Subcarrier Stream mode, OOK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_subcarrier_stream | ST25R3916_REG_MODE_tr_am_ook); + + // Subcarrier 424 kHz mode + // 8 sub-carrier pulses in report period + st25r3916_write_reg( + handle, + ST25R3916_REG_STREAM_MODE, + ST25R3916_REG_STREAM_MODE_scf_sc424 | ST25R3916_REG_STREAM_MODE_stx_106 | + ST25R3916_REG_STREAM_MODE_scp_8pulses); + + // Use regulator AM, resistive AM disabled + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am); + + return furi_hal_nfc_iso15693_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + furi_assert(furi_hal_nfc_iso15693_poller); + + furi_hal_nfc_iso15693_poller_free(furi_hal_nfc_iso15693_poller); + furi_hal_nfc_iso15693_poller = NULL; + + return FuriHalNfcErrorNone; +} + +static void iso15693_3_poller_encode_frame( + const uint8_t* tx_data, + size_t tx_bits, + uint8_t* frame_buf, + size_t frame_buf_size, + size_t* frame_buf_bits) { + static const uint8_t bit_patterns_1_out_of_4[] = {0x02, 0x08, 0x20, 0x80}; + size_t frame_buf_size_calc = (tx_bits / 2) + 2; + furi_assert(frame_buf_size >= frame_buf_size_calc); + + // Add SOF 1 out of 4 + frame_buf[0] = 0x21; + + size_t byte_pos = 1; + for(size_t i = 0; i < tx_bits / BITS_IN_BYTE; ++i) { + for(size_t j = 0; j < BITS_IN_BYTE; j += (BITS_IN_BYTE) / 4) { + const uint8_t bit_pair = (tx_data[i] >> j) & 0x03; + frame_buf[byte_pos++] = bit_patterns_1_out_of_4[bit_pair]; + } + } + // Add EOF + frame_buf[byte_pos++] = 0x04; + *frame_buf_bits = byte_pos * BITS_IN_BYTE; +} + +static FuriHalNfcError iso15693_3_poller_decode_frame( + const uint8_t* buf, + size_t buf_bits, + uint8_t* buf_decoded, + size_t buf_decoded_size, + size_t* buf_decoded_bits) { + FuriHalNfcError ret = FuriHalNfcErrorDataFormat; + size_t bit_pos = 0; + memset(buf_decoded, 0, buf_decoded_size); + + do { + if(buf_bits == 0) break; + // Check SOF + if((buf[0] & FURI_HAL_NFC_ISO15693_RESP_SOF_MASK) != + FURI_HAL_NFC_ISO15693_RESP_SOF_PATTERN) + break; + + if(buf_bits == BITS_IN_BYTE) { + ret = FuriHalNfcErrorIncompleteFrame; + break; + } + + // 2 response bits = 1 data bit + for(uint32_t i = FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE; + i < buf_bits - FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE; + i += BITS_IN_BYTE / 4) { + const size_t byte_index = i / BITS_IN_BYTE; + const size_t bit_offset = i % BITS_IN_BYTE; + const uint8_t resp_byte = (buf[byte_index] >> bit_offset) | + (buf[byte_index + 1] << (BITS_IN_BYTE - bit_offset)); + + // Check EOF + if(resp_byte == FURI_HAL_NFC_ISO15693_RESP_EOF_PATTERN) { + ret = FuriHalNfcErrorNone; + break; + } + + const uint8_t bit_pattern = resp_byte & FURI_HAL_NFC_ISO15693_RESP_PATTERN_MASK; + + if(bit_pattern == FURI_HAL_NFC_ISO15693_RESP_PATTERN_0) { + bit_pos++; + } else if(bit_pattern == FURI_HAL_NFC_ISO15693_RESP_PATTERN_1) { + buf_decoded[bit_pos / BITS_IN_BYTE] |= 1 << (bit_pos % BITS_IN_BYTE); + bit_pos++; + } else { + break; + } + if(bit_pos / BITS_IN_BYTE > buf_decoded_size) { + break; + } + } + + } while(false); + + if(ret == FuriHalNfcErrorNone) { + *buf_decoded_bits = bit_pos; + } + + return ret; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller; + iso15693_3_poller_encode_frame( + tx_data, + tx_bits, + instance->frame_buf, + sizeof(instance->frame_buf), + &instance->frame_buf_bits); + return furi_hal_nfc_poller_tx_common(handle, instance->frame_buf, instance->frame_buf_bits); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller; + + do { + error = furi_hal_nfc_common_fifo_rx( + handle, instance->fifo_buf, sizeof(instance->fifo_buf), &instance->fifo_buf_bits); + if(error != FuriHalNfcErrorNone) break; + + error = iso15693_3_poller_decode_frame( + instance->fifo_buf, + instance->fifo_buf_bits, + instance->frame_buf, + sizeof(instance->frame_buf), + &instance->frame_buf_bits); + if(error != FuriHalNfcErrorNone) break; + + if(rx_data_size < instance->frame_buf_bits / BITS_IN_BYTE) { + error = FuriHalNfcErrorBufferOverflow; + break; + } + + memcpy(rx_data, instance->frame_buf, instance->frame_buf_bits / BITS_IN_BYTE); + *rx_bits = instance->frame_buf_bits; + } while(false); + + return error; +} + +static void furi_hal_nfc_iso15693_listener_transparent_mode_enter(FuriHalSpiBusHandle* handle) { + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); + + furi_hal_spi_bus_handle_deinit(handle); + furi_hal_nfc_deinit_gpio_isr(); +} + +static void furi_hal_nfc_iso15693_listener_transparent_mode_exit(FuriHalSpiBusHandle* handle) { + // Configure gpio back to SPI and exit transparent mode + furi_hal_nfc_init_gpio_isr(); + furi_hal_spi_bus_handle_init(handle); + + st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(FuriHalSpiBusHandle* handle) { + furi_assert(furi_hal_nfc_iso15693_listener == NULL); + + furi_hal_nfc_iso15693_listener = furi_hal_nfc_iso15693_listener_alloc(); + + // Set default operation mode + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_targ_nfca | ST25R3916_REG_MODE_tr_am_ook); + + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_rx_en, + ST25R3916_REG_OP_CONTROL_rx_en); + + // Enable passive target mode + st25r3916_change_reg_bits( + handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ, ST25R3916_REG_MODE_targ_targ); + + FuriHalNfcError error = furi_hal_nfc_iso15693_common_init(handle); + + furi_hal_nfc_iso15693_listener_transparent_mode_enter(handle); + + return error; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_deinit(FuriHalSpiBusHandle* handle) { + furi_assert(furi_hal_nfc_iso15693_listener); + + furi_hal_nfc_iso15693_listener_transparent_mode_exit(handle); + + furi_hal_nfc_iso15693_listener_free(furi_hal_nfc_iso15693_listener); + furi_hal_nfc_iso15693_listener = NULL; + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError + furi_hal_nfc_iso15693_listener_tx_transparent(const uint8_t* data, size_t data_size) { + iso15693_signal_tx( + furi_hal_nfc_iso15693_listener->signal, Iso15693SignalDataRateHi, data, data_size); + + return FuriHalNfcErrorNone; +} + +static void furi_hal_nfc_iso15693_parser_callback(Iso15693ParserEvent event, void* context) { + furi_assert(context); + + if(event == Iso15693ParserEventDataReceived) { + FuriThreadId thread_id = context; + furi_thread_flags_set(thread_id, FuriHalNfcEventInternalTypeTransparentDataReceived); + } +} + +static FuriHalNfcEvent furi_hal_nfc_iso15693_wait_event(uint32_t timeout_ms) { + FuriHalNfcEvent event = 0; + + FuriThreadId thread_id = furi_thread_get_current_id(); + iso15693_parser_start( + furi_hal_nfc_iso15693_listener->parser, furi_hal_nfc_iso15693_parser_callback, thread_id); + + while(true) { + uint32_t flag = furi_thread_flags_wait( + FuriHalNfcEventInternalTypeAbort | FuriHalNfcEventInternalTypeTransparentDataReceived, + FuriFlagWaitAny, + timeout_ms); + furi_thread_flags_clear(flag); + + if(flag & FuriHalNfcEventInternalTypeAbort) { + event = FuriHalNfcEventAbortRequest; + break; + } + if(flag & FuriHalNfcEventInternalTypeTransparentDataReceived) { + if(iso15693_parser_run(furi_hal_nfc_iso15693_listener->parser)) { + event = FuriHalNfcEventRxEnd; + break; + } + } + } + iso15693_parser_stop(furi_hal_nfc_iso15693_listener->parser); + + return event; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + UNUSED(handle); + furi_assert(furi_hal_nfc_iso15693_listener); + + FuriHalNfcError error = FuriHalNfcErrorNone; + + error = furi_hal_nfc_iso15693_listener_tx_transparent(tx_data, tx_bits / BITS_IN_BYTE); + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof() { + iso15693_signal_tx_sof(furi_hal_nfc_iso15693_listener->signal, Iso15693SignalDataRateHi); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits) { + furi_assert(furi_hal_nfc_iso15693_listener); + UNUSED(handle); + + if(rx_data_size < + iso15693_parser_get_data_size_bytes(furi_hal_nfc_iso15693_listener->parser)) { + return FuriHalNfcErrorBufferOverflow; + } + + iso15693_parser_get_data( + furi_hal_nfc_iso15693_listener->parser, rx_data, rx_data_size, rx_bits); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso15693_listener_sleep(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_iso15693 = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_ISO15693_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_iso15693_poller_init, + .deinit = furi_hal_nfc_iso15693_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_iso15693_poller_tx, + .rx = furi_hal_nfc_iso15693_poller_rx, + }, + + .listener = + { + .compensation = + { + .fdt = FURI_HAL_NFC_ISO15693_LISTENER_FDT_COMP_FC, + }, + .init = furi_hal_nfc_iso15693_listener_init, + .deinit = furi_hal_nfc_iso15693_listener_deinit, + .wait_event = furi_hal_nfc_iso15693_wait_event, + .tx = furi_hal_nfc_iso15693_listener_tx, + .rx = furi_hal_nfc_iso15693_listener_rx, + .sleep = furi_hal_nfc_iso15693_listener_sleep, + .idle = furi_hal_nfc_iso15693_listener_sleep, + }, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h b/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h new file mode 100644 index 0000000000..e36dc852e8 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h @@ -0,0 +1,167 @@ +/** + * @file furi_hal_nfc_tech_i.h + * @brief NFC HAL technology-related private definitions. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + * + * This file is to be changed in an unlikely event of adding support + * for a new NFC technology. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configure the NFC chip for use with this technology. + * + * Used for init() and deinit() functions. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcChipConfig)(FuriHalSpiBusHandle* handle); + +/** + * @brief Transmit data using technology-specific framing and timings. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError ( + *FuriHalNfcTx)(FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Receive data using technology-specific framing and timings. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data length, in bytes. + * @param[out] rx_bits pointer to a variable to contain received data length, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcRx)( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits); + +/** + * @brief Wait for an event using technology-specific method. + * + * @param[in] timeout_ms maximum time to wait, in milliseconds. + * @return bitmask of occurred events. + */ +typedef FuriHalNfcEvent (*FuriHalNfcWaitEvent)(uint32_t timeout_ms); + +/** + * @brief Go to sleep in listener mode. + * + * Puts the passive target logic into Sleep (Halt) state. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcSleep)(FuriHalSpiBusHandle* handle); + +/** + * @brief Go to idle in listener mode. + * + * Puts the passive target logic into Sense (Idle) state. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcIdle)(FuriHalSpiBusHandle* handle); + +/** + * @brief Technology-specific compenstaion values for pollers. + * + * Timing compensations are needed due to execution delays not accounted for + * in standards, they are usually found out experimentally. + * + * The compensation value will be subtracted from the respective timer running + * time, so positive values shorten timeouts, and negative ones make them longer. + */ +typedef struct { + int32_t fdt; /**< Frame delay time compensation, in carrier cycles. */ + int32_t fwt; /**< Frame wait time compensaton, in carrier cycles. */ +} FuriHalNfcPollerCompensation; + +/** + * @brief Abstract technology-specific poller structure. + */ +typedef struct { + FuriHalNfcPollerCompensation compensation; /**< Compensation values in poller mode. */ + FuriHalNfcChipConfig init; /**< Pointer to the init() function. */ + FuriHalNfcChipConfig deinit; /**< Pointer to the deinit() function. */ + FuriHalNfcWaitEvent wait_event; /**< Pointer to the wait_event() function. */ + FuriHalNfcTx tx; /**< Pointer to the tx() function. */ + FuriHalNfcRx rx; /**< Pointer to the rx() function. */ +} FuriHalNfcTechPollerBase; + +/** + * @brief Technology-specific compenstaion values for listeners. + * + * Same considerations apply as with FuriHalNfcPollerCompensation. + */ +typedef struct { + int32_t fdt; /**< Frame delay time compensation, in carrier cycles. */ +} FuriHalNfcListenerCompensation; + +/** + * @brief Abstract technology-specific listener structure. + * + * If the listener operating mode is not supported for a particular + * technology, fill this structure with zeroes. + */ +typedef struct { + FuriHalNfcListenerCompensation compensation; /**< Compensation values in listener mode. */ + FuriHalNfcChipConfig init; /**< Pointer to the init() function. */ + FuriHalNfcChipConfig deinit; /**< Pointer to the deinit() function. */ + FuriHalNfcWaitEvent wait_event; /**< Pointer to the wait_event() function. */ + FuriHalNfcTx tx; /**< Pointer to the tx() function. */ + FuriHalNfcRx rx; /**< Pointer to the rx() function. */ + FuriHalNfcSleep sleep; /**< Pointer to the sleep() function. */ + FuriHalNfcIdle idle; /**< Pointer to the idle() function. */ +} FuriHalNfcTechListenerBase; + +/** + * @brief Abstract NFC technology definition structure. + * + * Each concrete technology implementation must fill this structure + * with its proper functions and constants. + */ +typedef struct { + FuriHalNfcTechPollerBase poller; /**< Structure containing the poller definition. */ + FuriHalNfcTechListenerBase listener; /**< Structure containing the listener definition. */ +} FuriHalNfcTechBase; + +/** @brief Technology declaration for ISO14443 (Type A). */ +extern const FuriHalNfcTechBase furi_hal_nfc_iso14443a; +/** @brief Technology declaration for ISO14443 (Type B). */ +extern const FuriHalNfcTechBase furi_hal_nfc_iso14443b; +/** @brief Technology declaration for ISO15693. */ +extern const FuriHalNfcTechBase furi_hal_nfc_iso15693; +/** @brief Technology declaration for FeliCa. */ +extern const FuriHalNfcTechBase furi_hal_nfc_felica; +/* Declare new tehcnologies here. */ + +/** + * @brief Array of pointers to every supported technology. + * + * This variable is defined in furi_hal_nfc.c. It will need to be modified + * in case when a new technology is to be added. + */ +extern const FuriHalNfcTechBase* furi_hal_nfc_tech[]; + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c new file mode 100644 index 0000000000..c9de9dfe85 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c @@ -0,0 +1,229 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include + +#include +#include +#include + +#define TAG "FuriHalNfcTimer" + +#define FURI_HAL_NFC_TIMER_US_IN_S (1000000UL) + +/** + * To enable timer debug output on GPIO, define the FURI_HAL_NFC_TIMER_DEBUG macro + * Example: ./fbt --extra-define=FURI_HAL_NFC_TIMER_DEBUG + */ + +typedef enum { + FuriHalNfcTimerFwt, + FuriHalNfcTimerBlockTx, + FuriHalNfcTimerCount, +} FuriHalNfcTimer; + +typedef struct { + TIM_TypeDef* timer; + FuriHalBus bus; + uint32_t prescaler; + uint32_t freq_khz; + FuriHalNfcEventInternalType event; + FuriHalInterruptId irq_id; + IRQn_Type irq_type; +#ifdef FURI_HAL_NFC_TIMER_DEBUG + const GpioPin* pin; +#endif +} FuriHalNfcTimerConfig; + +static const FuriHalNfcTimerConfig furi_hal_nfc_timers[FuriHalNfcTimerCount] = { + [FuriHalNfcTimerFwt] = + { + .timer = TIM1, + .bus = FuriHalBusTIM1, + .event = FuriHalNfcEventInternalTypeTimerFwtExpired, + .irq_id = FuriHalInterruptIdTim1UpTim16, + .irq_type = TIM1_UP_TIM16_IRQn, +#ifdef FURI_HAL_NFC_TIMER_DEBUG + .pin = &gpio_ext_pa7, +#endif + }, + [FuriHalNfcTimerBlockTx] = + { + .timer = TIM17, + .bus = FuriHalBusTIM17, + .event = FuriHalNfcEventInternalTypeTimerBlockTxExpired, + .irq_id = FuriHalInterruptIdTim1TrgComTim17, + .irq_type = TIM1_TRG_COM_TIM17_IRQn, +#ifdef FURI_HAL_NFC_TIMER_DEBUG + .pin = &gpio_ext_pa6, +#endif + }, +}; + +static void furi_hal_nfc_timer_irq_callback(void* context) { + // Returning removed const-ness + const FuriHalNfcTimerConfig* config = context; + if(LL_TIM_IsActiveFlag_UPDATE(config->timer)) { + LL_TIM_ClearFlag_UPDATE(config->timer); + furi_hal_nfc_event_set(config->event); +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_write(timer_config->pin, false); +#endif + } +} + +static void furi_hal_nfc_timer_init(FuriHalNfcTimer timer) { + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + + furi_hal_bus_enable(config->bus); + + LL_TIM_SetOnePulseMode(config->timer, LL_TIM_ONEPULSEMODE_SINGLE); + LL_TIM_EnableUpdateEvent(config->timer); + LL_TIM_SetCounterMode(config->timer, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetClockSource(config->timer, LL_TIM_CLOCKSOURCE_INTERNAL); + + furi_hal_interrupt_set_isr( + config->irq_id, + furi_hal_nfc_timer_irq_callback, + // Warning: casting const-ness away + (FuriHalNfcTimerConfig*)config); + NVIC_SetPriority(config->irq_type, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(config->irq_type); +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_init(config->pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(config->pin, false); +#endif +} + +static void furi_hal_nfc_timer_deinit(FuriHalNfcTimer timer) { + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + + LL_TIM_ClearFlag_UPDATE(config->timer); + furi_hal_interrupt_set_isr(config->irq_id, NULL, NULL); + NVIC_DisableIRQ(config->irq_type); + + if(furi_hal_bus_is_enabled(config->bus)) { + furi_hal_bus_disable(config->bus); + } +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_init_simple(config->pin, GpioModeAnalog); + furi_hal_gpio_write(config->pin, false); +#endif +} + +static int32_t furi_hal_nfc_timer_get_compensation(FuriHalNfcTimer timer) { + const FuriHalNfcTechBase* current_tech = furi_hal_nfc_tech[furi_hal_nfc.tech]; + + if(furi_hal_nfc.mode == FuriHalNfcModePoller) { + const FuriHalNfcPollerCompensation* comp = ¤t_tech->poller.compensation; + if(timer == FuriHalNfcTimerFwt) + return comp->fwt; + else if(timer == FuriHalNfcTimerBlockTx) + return comp->fdt; + + } else if(furi_hal_nfc.mode == FuriHalNfcModeListener) { + const FuriHalNfcListenerCompensation* comp = ¤t_tech->listener.compensation; + if(timer == FuriHalNfcTimerBlockTx) return comp->fdt; + } + + return 0; +} + +static inline bool furi_hal_nfc_timer_is_running(FuriHalNfcTimer timer) { + return LL_TIM_IsEnabledCounter(furi_hal_nfc_timers[timer].timer) != 0; +} + +static void furi_hal_nfc_timer_start_core_ticks(FuriHalNfcTimer timer, uint64_t core_ticks) { + furi_check(!furi_hal_nfc_timer_is_running(timer)); + + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + furi_check(furi_hal_bus_is_enabled(config->bus)); + + const uint32_t prescaler = (core_ticks - 1) / UINT16_MAX; + furi_check(prescaler <= UINT16_MAX); + + const uint32_t arr_reg = core_ticks / (prescaler + 1); + furi_check(arr_reg <= UINT16_MAX); + + LL_TIM_DisableIT_UPDATE(config->timer); + + LL_TIM_SetPrescaler(config->timer, prescaler); + LL_TIM_SetAutoReload(config->timer, arr_reg); + + LL_TIM_GenerateEvent_UPDATE(config->timer); + while(!LL_TIM_IsActiveFlag_UPDATE(config->timer)) + ; + LL_TIM_ClearFlag_UPDATE(config->timer); + + LL_TIM_EnableIT_UPDATE(config->timer); + LL_TIM_EnableCounter(config->timer); +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_write(config->pin, true); +#endif +} + +static void furi_hal_nfc_timer_start_us(FuriHalNfcTimer timer, uint32_t time_us) { + furi_hal_nfc_timer_start_core_ticks( + timer, SystemCoreClock / FURI_HAL_NFC_TIMER_US_IN_S * time_us); +} + +static void furi_hal_nfc_timer_start_fc(FuriHalNfcTimer timer, uint32_t time_fc) { + const int32_t comp_fc = furi_hal_nfc_timer_get_compensation(timer); + // Not starting the timer if the compensation value is greater than the requested delay + if(comp_fc >= (int32_t)time_fc) return; + + furi_hal_nfc_timer_start_core_ticks( + timer, ((uint64_t)SystemCoreClock * (time_fc - comp_fc)) / FURI_HAL_NFC_CARRIER_HZ); +} + +static void furi_hal_nfc_timer_stop(FuriHalNfcTimer timer) { + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + + LL_TIM_DisableIT_UPDATE(config->timer); + LL_TIM_DisableCounter(config->timer); + LL_TIM_SetCounter(config->timer, 0); + LL_TIM_SetAutoReload(config->timer, 0); + + if(LL_TIM_IsActiveFlag_UPDATE(config->timer)) { + LL_TIM_ClearFlag_UPDATE(config->timer); + } +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_write(config->pin, false); +#endif +} + +void furi_hal_nfc_timers_init() { + for(size_t i = 0; i < FuriHalNfcTimerCount; i++) { + furi_hal_nfc_timer_init(i); + } +} + +void furi_hal_nfc_timers_deinit() { + for(size_t i = 0; i < FuriHalNfcTimerCount; i++) { + furi_hal_nfc_timer_deinit(i); + } +} + +void furi_hal_nfc_timer_fwt_start(uint32_t time_fc) { + furi_hal_nfc_timer_start_fc(FuriHalNfcTimerFwt, time_fc); +} + +void furi_hal_nfc_timer_fwt_stop() { + furi_hal_nfc_timer_stop(FuriHalNfcTimerFwt); +} + +void furi_hal_nfc_timer_block_tx_start(uint32_t time_fc) { + furi_hal_nfc_timer_start_fc(FuriHalNfcTimerBlockTx, time_fc); +} + +void furi_hal_nfc_timer_block_tx_start_us(uint32_t time_us) { + furi_hal_nfc_timer_start_us(FuriHalNfcTimerBlockTx, time_us); +} + +void furi_hal_nfc_timer_block_tx_stop() { + furi_hal_nfc_timer_stop(FuriHalNfcTimerBlockTx); +} + +bool furi_hal_nfc_timer_block_tx_is_running() { + return furi_hal_nfc_timer_is_running(FuriHalNfcTimerBlockTx); +} diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index 9bb87000c7..63b5cdb927 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -30,9 +30,9 @@ "nfc", "digital_signal", "pulse_reader", + "signal_reader", "microtar", "usb_stm32", - "st25rfal002", "infrared", "appframe", "assets", @@ -44,6 +44,7 @@ "lfrfid", "flipper_application", "flipperformat", - "toolbox" + "toolbox", + "flipper7" ] } \ No newline at end of file diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h new file mode 100644 index 0000000000..ad4080e264 --- /dev/null +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -0,0 +1,457 @@ +/** + * @file furi_hal_nfc.h + * @brief NFC HAL library. + * + * This library contains functions and definitions needed for NFC hardware low-level access. + * + * Application developers should first consider using the NFC protocol stack or + * the NFC transport layer and see if the APIs provided there sufficient + * for the applicaton's intended purpose. + * + * @see nfc.h + * @see nfc_protocol.h + * + * If any of the above mentioned options is used, calling any of the functions provided by this + * library is hardly necessary, as it will be taken care of under the hood. + * + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NFC carrier frequency, in Hz. + */ +#define FURI_HAL_NFC_CARRIER_HZ (13560000UL) + +/** + * @brief Special value indicating that waiting for an event shall never time out. + */ +#define FURI_HAL_NFC_EVENT_WAIT_FOREVER (0xFFFFFFFFU) + +/** + * @brief Enumeration of possible NFC HAL events. + */ +typedef enum { + FuriHalNfcEventOscOn = (1U << 0), /**< Oscillator has been started. */ + FuriHalNfcEventFieldOn = (1U << 1), /**< External field (carrier) has been detected. */ + FuriHalNfcEventFieldOff = (1U << 2), /**< External field (carrier) has been lost. */ + FuriHalNfcEventListenerActive = (1U << 3), /**< Reader has issued a wake-up command. */ + FuriHalNfcEventTxStart = (1U << 4), /**< Transmission has started. */ + FuriHalNfcEventTxEnd = (1U << 5), /**< Transmission has ended. */ + FuriHalNfcEventRxStart = (1U << 6), /**< Reception has started. */ + FuriHalNfcEventRxEnd = (1U << 7), /**< Reception has ended. */ + FuriHalNfcEventCollision = (1U << 8), /**< A collision has occurred. */ + FuriHalNfcEventTimerFwtExpired = (1U << 9), /**< Frame wait timer has expired. */ + FuriHalNfcEventTimerBlockTxExpired = (1U << 10), /**< Transmission block timer has expired. */ + FuriHalNfcEventTimeout = + (1U << 11), /**< No events have occurred in a specified time period. */ + FuriHalNfcEventAbortRequest = + (1U << 12), /**< User has requested to abort current operation. */ +} FuriHalNfcEvent; + +/** + * @brief Enumeration of possible NFC HAL errors. + */ +typedef enum { + FuriHalNfcErrorNone, /**< No error has occurred. */ + FuriHalNfcErrorBusy, /**< The communication bus is busy. */ + FuriHalNfcErrorCommunication, /**< NFC hardware did not respond or responded unexpectedly. */ + FuriHalNfcErrorOscillator, /**< Oscillator failed to start. */ + FuriHalNfcErrorCommunicationTimeout, /**< NFC hardware did not respond in time. */ + FuriHalNfcErrorBufferOverflow, /**< Receive buffer was too small for the received data. */ + FuriHalNfcErrorIncompleteFrame, /**< Not enough data was received to parse a valid frame. */ + FuriHalNfcErrorDataFormat, /**< Cannot parse a frame due to unexpected/invalid data. */ +} FuriHalNfcError; + +/** + * @brief Enumeration of possible NFC HAL operating modes. + */ +typedef enum { + FuriHalNfcModePoller, /**< Configure NFC HAL to operate as a poller. */ + FuriHalNfcModeListener, /**< Configure NFC HAL to operate as a listener. */ + + FuriHalNfcModeNum, /**< Special value equal to the operating modes count. Internal use. */ +} FuriHalNfcMode; + +/** + * @brief Enumeration of supported NFC technologies. + */ +typedef enum { + FuriHalNfcTechIso14443a, /**< Configure NFC HAL to use the ISO14443 (type A) technology. */ + FuriHalNfcTechIso14443b, /**< Configure NFC HAL to use the ISO14443 (type B) technology. */ + FuriHalNfcTechIso15693, /**< Configure NFC HAL to use the ISO15693 technology. */ + FuriHalNfcTechFelica, /**< Configure NFC HAL to use the FeliCa technology. */ + + FuriHalNfcTechNum, /**< Special value equal to the supported technologies count. Internal use. */ + FuriHalNfcTechInvalid, /**< Special value indicating the unconfigured state. Internal use. */ +} FuriHalNfcTech; + +/** + * @brief Initialise the NFC HAL and associated hardware. + * + * This function is called automatically during the firmware initialisation, + * so there is no need to call it explicitly. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_init(); + +/** + * @brief Check whether the NFC HAL was properly initialised and is ready. + * + * @returns FuriHalNfcErrorNone if ready, any other error code if not ready. + */ +FuriHalNfcError furi_hal_nfc_is_hal_ready(); + +/** + * @brief Exclusively take over the NFC HAL and associated hardware. + * + * This function needs to be called whenever an interaction with the NFC HAL + * is to take place (usually once upon the application start). + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_acquire(); + +/** + * @brief Release the exclusive lock and make the NFC HAL available for others. + * + * This function needs to be called when the user code is done working + * with the NFC HAL (usually once upon application exit). It must be called + * from the same thread that has called furi_hal_nfc_acquire(). + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_release(); + +/** + * @brief Configure the NFC hardware to enter the low-power mode. + * + * This function must be called each time when the user code is done working + * with the NFC HAL for the time being (e.g. waiting on user input). + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_low_power_mode_start(); + +/** + * @brief Configure the NFC hardware to exit the low-power mode. + * + * This function must be called each time when the user code begins working + * with the NFC HAL, as the default state is low-power mode. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_low_power_mode_stop(); + +/** + * @brief Configure the NFC HAL to work in a particular mode. + * + * Not all technologies implement the listener operating mode. + * + * @param[in] mode required operating mode. + * @param[in] tech required technology configuration. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech); + +/** + * @brief Reset the NFC HAL to its default (unconfigured) state. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_reset_mode(); + +/** + * @brief Enable field (carrier) detection by the NFC hardware. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_field_detect_start(); + +/** + * @brief Disable field (carrier) detection by the NFC hardware. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_field_detect_stop(); + +/** + * @brief Check if the reader field (carrier) was detected by the NFC hardware. + * + * @returns true if the field was detected, false otherwise. + */ +bool furi_hal_nfc_field_is_present(); + +/** + * @brief Enable field (carrier) generation by the NFC hardware. + * + * No carrier modulation will occur unless a transmission is explicitly started. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_field_on(); + +/** + * @brief Wait for an NFC HAL event in poller mode. + * + * @param[in] timeout_ms time to wait (timeout) in milliseconds. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcEvent furi_hal_nfc_poller_wait_event(uint32_t timeout_ms); + +/** + * @brief Wait for an NFC HAL event in listener mode. + * @param[in] timeout_ms time to wait (timeout) in milliseconds. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcEvent furi_hal_nfc_listener_wait_event(uint32_t timeout_ms); + +/** + * @brief Transmit data in poller mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Receive data in poller mode. + * + * The receive buffer must be big enough to accomodate all of the expected data. + * + * @param rx_data[out] pointer to a byte array to be filled with received data. + * @param rx_data_size[in] maximum received data size, in bytes. + * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); + +/** + * @brief Transmit data in listener mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Receive data in listener mode. + * + * The receive buffer must be big enough to accomodate all of the expected data. + * + * @param rx_data[out] pointer to a byte array to be filled with received data. + * @param rx_data_size[in] maximum received data size, in bytes. + * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); + +/** + * @brief Go to sleep in listener mode. + * + * Puts the passive target logic into Sleep (Halt) state. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_sleep(); + +/** + * @brief Go to idle in listener mode. + * + * Puts the passive target logic into Sense (Idle) state. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_idle(); + +/** + * @brief Enable reception in listener mode. + * + * Starts hardware receivers and receive decoders. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_enable_rx(); + +/** + * @brief Reset communication. + * + * Resets the communication state and stops all activities: transmission, reception, etc. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_trx_reset(); + +/** + * @brief Enable generation of NFC HAL events. + * + * @warning This function must be called from the same thread from which + * the the furi_hal_nfc_*_wait_event() calls will be made. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_event_start(); + +/** + * @brief Disable generation of NFC HAL events. + * + * Unlike furi_hal_nfc_event_start(), this function may be called from any thread. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_event_stop(); + +/** + * @brief Manually emit the FuriHalNfcEventAbortRequest event. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. +*/ +FuriHalNfcError furi_hal_nfc_abort(); + +/** + * @brief Start frame wait timeout timer. + * + * @param[in] time_fc time to wait, in carrier cycles. + */ +void furi_hal_nfc_timer_fwt_start(uint32_t time_fc); + +/** + * @brief Stop frame wait timeout timer. + */ +void furi_hal_nfc_timer_fwt_stop(); + +/** + * @brief Start block transmit (frame delay) timer. + * + * @param[in] time_fc time to wait, in carrier cycles. +*/ +void furi_hal_nfc_timer_block_tx_start(uint32_t time_fc); + +/** + * @brief Start block transmit (frame delay) timer. + * + * @param[in] time_us time to wait, in microseconds. + */ +void furi_hal_nfc_timer_block_tx_start_us(uint32_t time_us); + +/** + * @brief Stop block transmit (frame delay) timer. + */ +void furi_hal_nfc_timer_block_tx_stop(); + +/** + * @brief Check whether block transmit (frame delay) timer is running. + * + * @returns true if timer is running, false otherwise. + */ +bool furi_hal_nfc_timer_block_tx_is_running(); + +/* + * Technology-specific functions. + * + * In a perfect world, this would not be necessary. + * However, the current implementation employs NFC hardware that partially implements + * certain protocols (e.g. ISO14443-3A), thus requiring methods to access such features. + */ + +/******************* Iso14443a specific API *******************/ + +/** + * @brief Enumeration of ISO14443 (Type A) short frame types. + */ +typedef enum { + FuriHalNfcaShortFrameAllReq, + FuriHalNfcaShortFrameSensReq, +} FuriHalNfcaShortFrame; + +/** + * @brief Transmit ISO14443 (Type A) short frame in poller mode. + * + * @param[in] frame short frame type to be transmitted. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrame frame); + +/** Transmit ISO14443 (Type A) SDD frame in poller mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_tx_sdd_frame(const uint8_t* tx_data, size_t tx_bits); + +/** + * Receive ISO14443 (Type A) SDD frame in poller mode. + * + * The receive buffer must be big enough to accomodate all of the expected data. + * + * @param rx_data[out] pointer to a byte array to be filled with received data. + * @param rx_data_size[in] maximum received data size, in bytes. + * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError + furi_hal_nfc_iso14443a_rx_sdd_frame(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); + +/** + * @brief Transmit ISO14443 (Type A) frame with custom parity bits in poller mode. + * + * Same as furi_hal_nfc_poller_tx(), but uses the parity bits provided + * by the user code instead of calculating them automatically. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError + furi_hal_nfc_iso14443a_poller_tx_custom_parity(const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Set ISO14443 (Type A) collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in] uid pointer to a byte array containing the UID. + * @param[in] uid_len UID length in bytes (must be supported by the protocol). + * @param[in] atqa ATQA byte value. + * @param[in] sak SAK byte value. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak); + +/** + * @brief Transmit ISO14443 (Type A) frame with custom parity bits in listener mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_parity pointer to a (bit-packed) byte array containing the parity to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits); + +/** Send ISO15693 SOF in listener mode + * + * @return FuriHalNfcError +*/ +FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(); + +#ifdef __cplusplus +} +#endif diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 138bef2b34..326d933aa5 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -2,7 +2,6 @@ - `FreeRTOS-Kernel` - FreeRTOS kernel source code - `FreeRTOS-glue` - Extra glue to hold together FreeRTOS kernel and flipper firmware -- `ST25RFAL002` - ST25R3916 Driver and protocol stack - `app-scened-template` - C++ app library - `callback-connector` - Callback connector library - `cmsis_core` - CMSIS Core package, contain cortex-m core headers diff --git a/lib/SConscript b/lib/SConscript index 907a5a41de..f2cc9d18a0 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -5,11 +5,11 @@ env.Append( Dir("app-scened-template"), Dir("digital_signal"), Dir("pulse_reader"), + Dir("signal_reader"), Dir("drivers"), Dir("flipper_format"), Dir("infrared"), Dir("nfc"), - Dir("ST25RFAL002"), Dir("subghz"), Dir("toolbox"), Dir("u8g2"), @@ -83,7 +83,6 @@ libs = env.BuildModules( "print", "microtar", "toolbox", - "ST25RFAL002", "libusb_stm32", "drivers", "fatfs", @@ -97,6 +96,7 @@ libs = env.BuildModules( "nfc", "digital_signal", "pulse_reader", + "signal_reader", "appframe", "misc", "lfrfid", diff --git a/lib/ST25RFAL002/SConscript b/lib/ST25RFAL002/SConscript deleted file mode 100644 index d86d2d002e..0000000000 --- a/lib/ST25RFAL002/SConscript +++ /dev/null @@ -1,19 +0,0 @@ -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/ST25RFAL002", - "#/lib/ST25RFAL002/include", - "#/lib/ST25RFAL002/source/st25r3916", - ], -) - - -libenv = env.Clone(FW_LIB_NAME="st25rfal002") -libenv.ApplyLibFlags() - -sources = libenv.GlobRecursive("*.c") - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/lib/ST25RFAL002/doc/Release_Notes.html b/lib/ST25RFAL002/doc/Release_Notes.html deleted file mode 100755 index 28d501c091..0000000000 --- a/lib/ST25RFAL002/doc/Release_Notes.html +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - - - - - - Release Notes for RFAL Library - - - - - - - - - -
-


-

-
- - - - - - -
- - - - - - - - - -

-
-

Release -Notes for RFAL software Library

-

Copyright -2019 STMicroelectronics

-

-
-

 

- - - - - - -

-
The RFAL Library -(RF Abstraction Layer) provides several functionalities required to perform RF/NFC communications. -The RFAL encapsulates the different RF ICs (ST25R3911, ST25R3916, ST25R95 and future ST25R devices) into a common and easy to use interface.
-
- The technologies currently supported by RFAL are: -
    -
  • NFC-A \ ISO14443A (T1T, T2T, T4TA)
  • -
  • NFC-B \ ISO14443B (T4TB)
  • -
  • NFC-F \ FeliCa (T3T)
  • -
  • NFC-V \ ISO15693 (T5T)
  • -
  • P2P \ ISO18092 (NFCIP1, Passive-Active P2P)
  • -
  • ST25TB (ISO14443-2 Type B with Proprietary Protocol)
  • -
  • PicoPass \ iClass
  • -
  • B' \ Calypso
  • -
  • CTS \ CTM
  • -
-

- The protocols provided by RFAL are: -
    -
  • ISO-DEP (ISO14443-4)
  • -
  • NFC-DEP (ISO18092)
  • -
-
-
-
    -
- -

Update History

-
- -

V2.2.0 / 22-May-2020

-

-

Main Changes

-
    -
  • Better alignment to NFC Forum latest requirements (CR12)
  • -
  • Extended NFC-V module with non-addressed mode support and improved aticollision
  • -
  • Feature Switches changed to be not mandatory. Modules disabled by default
  • -
  • Aligned APIs on platform.h (breaks compatibility with previous versions, see example in rfal.chm)
  • -
  • Added API for release/deletion of timers
  • -
  • ST25R3916 default analog table modified to X-NUCLEO-NFC06A1 board
  • -
  • Improved AP2P operation
  • -
  • Fixed issues introduced on previous release linked to SFGT and anticollision retries
  • -
  • Introduced Low-Power mode
  • -
  • Several driver improvements
  • -
-
- -

V2.1.2 / 27-Jan-2020

-

-

Main Changes

-
    -
  • Extended ISO-DEP and NFC-A module to support non-blocking activation interfaces
  • -
  • Extended NFC/HL module to make use of the new APIs further splitting the execution of the worker during the different activities
  • -
  • Modified NFC-A anticollision to strictly comply to NFC Forum DP. A separate proprietary method is now available.
  • -
  • NFC-V changed to use OOK (100% AM) by default
  • -
  • Fixed FWT used by NFC-V Sleep
  • -
  • Fixed NFC-F FDT Poll value
  • -
  • Fixed incorrect register access on ST25R3911B RFO Get/Set method
  • -
  • SPI driver modified to clear Rx buffer prior to operation
  • -
  • Added further code size optimizations based on enabled features
  • -
  • Updated ST25R3916 driver to DS Rev2
  • -
  • Updated SW Tag Detection as describded in AN Rev3
  • -
  • Several driver improvements
  • -
-
- -

V2.1.0 / 30-Sep-2019

-

-

Main Changes

-
    -
  • Extended RFAL NFC Higher Layer for increased functionality and configurations
  • -
  • Several improvements on the ISO-DEP protocol layer
  • -
  • Protocol buffer sizes made fully configurable for increased memory management
  • -
  • Introduced option for Collision Avoidance with Automatic Gain Control
  • -
  • Several driver improvements
  • -
  • ST25R3916 overheat protection disabled
  • -
  • RF Transceive modified for transmission errors to precede other errors
  • -
  • Analog Configs extended to support different DPO power levels
  • -
-
- -

V2.0.10 / 25-Jun-2019

-

-

Main Changes

-
    -
  • Various improvements on RFAL NFC Higher layer
  • -
  • Added alternative NFC-V anticollision method (non NFC Forum compliant)
  • -
  • Several minor improvements and fixes
  • -
-
- -

V2.0.6 / 10-Apr-2019

-

-

Main Changes

-
    -
  • Several NFC-V interoperability improvements
  • -
  • Extended support for specific features of ST's ISO15693 Tags. New ST25Dx module added
  • -
  • Interrupt handling changed and further protection added
  • -
  • RFAL feature switches have been modified and features are now disabled if omitted
  • -
  • ST25R3916 AAT (Automatic Antenna Tuning) module added
  • -
  • RFAL NFC Higher layer added
  • -
  • Several driver improvements
  • -
-
- -

V2.0.4 / 06-Fev-2019

-

Provided with ST25R3916 DISCO v1.0.0 / EMVCo v1.2.0

-

Main Changes

-
    -
  • Minor improvements on NFC-F module
  • -
  • Several improvements on NFC-V module including support for ST proprietary features
  • -
  • Fixed issue with Delta RWT calculation
  • -
  • Fixed incorrect usage of NFCB dTbPoll / DdFWT
  • -
  • Added compile switch for Listen Mode
  • -
  • Low power Listen Mode support added
  • -
  • Listen Mode aligned to NFC Forum Digital 2.1
  • -
  • Added handling for EMVCo 3.0 static FDTListen
  • -
  • Introduced SW Tag Detection
  • -
-
- -

V2.0.2 / 31-Oct-2018

-

Provided with ST25R3916 DISCO v0.9.4 (binary only)

-

Main Changes

-
    -
  • New T4T module added
  • -
  • Added support for T3T Check and Update commands
  • -
  • Improved NFC-V module and added Write Multiple Blocks support
  • -
  • New rfalWorker protection added for improved control in multi-thread environments
  • -
  • Added support for user defined Analog Config tables
  • -
  • Several driver improvements and protections added
  • -
-
- -

V2.0.0 / 28-Aug-2018

- -

Main Changes

-
    -
  • MISRA C 2012 compliant
  • -
  • ST25R3916 support added
  • -
  • ST25R95 support added
  • -
  • Fix unwanted Field Detector disable when entering Wake-up mode
  • -
  • Extended Analog Config to have specific events
  • -
  • Fixed NFC-DEP potential issue if DID used
  • -
  • Extended NFC-V commands
  • -
  • T2T module added
  • -
  • Improved initial Listen mode handling
  • -
  • Extended Wake-Up mode to support Capacitive measurement
  • -
-
- -

V1.3.6 / 08-May-2018

-

Provided with ST25R3911B DISCO v1.2.0

-

Main Changes

-
    -
  • Added ISO15693 x4 and x8 mode support
  • -
  • Added S(PARAMETERS) support
  • -
  • Interface changes for measurement, Wake-Up and DPO methods
  • -
  • Added further feature switches to enable/disable individual modules
  • -
  • Changed communication protection
  • -
  • Improved NFC-A anti-collision
  • -
  • Several driver improvements
  • -
-
-

V1.3.4 / 07-May-2018

- -

Main Changes

-
    -
  • Fixed NFC-V Read operation in addressed mode
  • -
-
-

V1.3.2 / 31-January-2018

- -

Main Changes

-
    -
  • Modified Wake-Up mode interface
  • -
  • Fixed SFGI calculation in ISO-DEP
  • -
-
-

V1.3.0 / 22-January-2018

- -

Main Changes

-
    -
  • Introduced a new IRQ status handling to read the registers only once
  • -
  • Several changes for supporting Linux platform
  • -
  • SPI Select/Deselect moved to platform.h
  • -
  • Additional protection of the IRQ status reading, new macros available: platformProtectST25R391xIrqStatus / platformUnprotectST25R391xIrqStatus
  • -
  • Renamed the IRQ Enable/Disable macros to platformProtectST25R391xComm / platformUnprotectST25R391xComm
  • -
  • Renamed SPI pins from chip specific to ST25R391X
  • -
  • Introduced a new option ST25R391X_COM_SINGLETXRX which executes SPI in one single exchange (additional buffer required)
  • -
  • Updated and added errata handlings to latest ST25R3911 Errata version
  • -
  • Fixed inconsistency on Analog settings for NFC-V
  • -
  • Fixed issue on NFC-V 1of256 decoding
  • -
  • Changed the default NFC-A FDT Listen to be more strict
  • -
  • Added Wake-Up mode support
  • -
  • Added RFAL version definition
  • -
-
-

V1.2.0 / 17-August-2017

-

Provided with ST25R3911B Disco v1.1.16

-

Main Changes

-
    -
  • Aligned Technology modules to NFC Activity v1.1 and EMVCo v2.6
  • -
  • Extended NFC-B Collision Resolution allowing user define Slots
  • -
  • Added feature switches to enable/disable individual modules
  • -
  • ISO-DEP Interface changes allowing more user configurations and further EMVCo alignment
  • -
  • Changed ST25TB detection to always perform Anti Collision loop regardeless of the result of the Poll
  • -
  • Fixed FIFO WL handling
  • -
  • Modified FDT Poll handling
  • -
  • changed rfalCalibrate() to not overwrite dynamic configs
  • -
  • Added adjustment for TR1PUTMIN
  • -
- -
-

V1.1.0 / 30-June-2017

-

Provided with ST25R3911B Disco v1.1.12

-

Main Changes

-
    -
  • EMD suppression enabled for ST25R3911B
  • -
- -
-

V1.0.0 / 16-May-2017

-

Provided with X-NUCLEO-NFC05A1 v1.0.0

-

Main Changes

-
    -
  • Added support for B', CTS and PicoPass/iClass mode
  • -
  • Several impromvements for NFC-V mode
  • -
  • Improved error detection during NFC-B collision resolution
  • -
  • Extended T1T module
  • -
- -
-

V0.9.0 / 02-March-2017

-

Provided with ST25R3911B Discovery Kit on Embedded World Conference (binary only)

-

Main Changes

- -
- -
-

-
-
-

 

-
- diff --git a/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html b/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html deleted file mode 100755 index e4ffaa9336..0000000000 --- a/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html +++ /dev/null @@ -1,8867 +0,0 @@ - - - - - -PRQA GEP/GCS/GRP Report - - - - -
-
-
-
-
- -This section targets to provide an overview of Guidelines Enforcement Plan (GEP).
-
-This document will only focus on STMicroelectronics NFC RF Abstraction Layer (RFAL).
-
-The project has been designed to comply with the standard ISO/IEC 9899:1999 ([C99]). -
-
-

1. Tools version

-The tool used for MISRA compliance is:
-
-PRQA Framework - v2.2.2
-

-It is composed of the following subcomponents: -

-
    -
  • Component: qacpp

  • -
      Version: 4.2.0
    -
      Target: C++
    -
  • Component: rcma

  • -
      Version: 1.6.0
    -
      Target: C_CPP
    -
  • Component: m3cm

  • -
      Version: 2.3.1
    -
      Target: C
    -
  • Component: qac

  • -
      Version: 9.3.1
    -
      Target: C
    -
      -
    • Options:
    • -
        -d : __schedule_barrier=_ignore_semi
      -
        -namelength : 63
      -
    -
-

2. Configuration

-This section targets to provide the main configuration options used for MISRA compliance.
-
-The project complies to [C99],
-the variables length has been consequently set to a dedicated value (cf 'namelength' option in table above). -
-
-Repository/components:
-
    -
  • MCU target:
  • -
      STM32

    -
  • Parent repository:
  • -
      ST25R3916_nucleo

    -
  • RFAL information:
  • -
      Path: .../ST25R3916_nucleo/rfal
    -
      Version: v2.1.2
    -
  • Project repositories SHA1:
  • -
      .../ST25R3916_nucleo: 959b80e
    -
      .../ST25R3916_nucleo/common: 09bc5ef
    -
      .../ST25R3916_nucleo/nucleo: 22a04ae
    -
      .../ST25R3916_nucleo/rfal: f08099c
    -
    -
-

3. Assistance/Enforcement

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GuidelineCategoryDescriptionAssistance/Enforcement Sub Rules
Dir-1.1RequiredAny implementation-defined behaviour on which the output of the program depends shall be documented and understood
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0202 [I] '-' character in '[]' conversion specification is implementation defined.
0284 [I] Multiple character constants have implementation defined values.
0285 [I] Character constant contains character which is not a member of the basic source character set.
0286 [I] String literal contains character which is not a member of the basic source character set.
0287 [I] Header name contains character which is not a member of the basic source character set.
0288 [I] Source file '%s' has comments containing characters which are not members of the basic source character set.
0289 [I] Source file '%s' has preprocessing tokens containing characters which are not members of the basic source character set.
0292 [I] Source file '%s' has comments containing one of the characters '$', '@' or '`'.
0299 [I] Source file '%s' includes #pragma directives containing characters which are not members of the basic source character set.
0314 [I] Cast from a pointer to object type to a pointer to void.
0315 [I] Implicit conversion from a pointer to object type to a pointer to void.
0371 [L] Nesting levels of blocks exceeds 127 - program does not conform strictly to ISO:C99.
0372 [L] More than 63 levels of nested conditional inclusion - program does not conform strictly to ISO:C99.
0375 [L] Nesting of parenthesized expressions exceeds 63 - program does not conform strictly to ISO:C99.
0380 [L] Number of macro definitions exceeds 4095 - program does not conform strictly to ISO:C99.
0388 [L] '#include "%s"' causes nesting to exceed 15 levels - program does not conform strictly to ISO:C99.
0390 [L] Number of members in 'struct' or 'union' exceeds 1023 - program does not conform strictly to ISO:C99.
0391 [L] Number of enumeration constants exceeds 1023 - program does not conform strictly to ISO:C99.
0392 [L] Nesting of 'struct' or 'union' types exceeds 63 - program does not conform strictly to ISO:C99.
0581 [I] Floating-point constant may be too small to be representable.
0634 [I] Bit-fields in this struct/union have not been declared explicitly as unsigned or signed.
2850 Constant: Implicit conversion to a signed integer type of insufficient size.
2851 Definite: Implicit conversion to a signed integer type of insufficient size.
2852 Apparent: Implicit conversion to a signed integer type of insufficient size.
2855 Constant: Casting to a signed integer type of insufficient size.
2856 Definite: Casting to a signed integer type of insufficient size.
2857 Apparent: Casting to a signed integer type of insufficient size.
2860 Constant: Implementation-defined value resulting from left shift operation on expression of signed type.
2861 Definite: Implementation-defined value resulting from left shift operation on expression of signed type.
2862 Apparent: Implementation-defined value resulting from left shift operation on expression of signed type.
2895 Constant: Negative value cast to an unsigned type.
2896 Definite: Negative value cast to an unsigned type.
2897 Apparent: Negative value cast to an unsigned type.
3116 Unrecognized #pragma arguments '%s' This #pragma directive has been ignored.
-
Dir-2.1RequiredAll source files shall compile without any compilation errorsUnassisted

-Remarks:
-Dedicated checks deployed in Jenkins.
Dir-3.1RequiredAll code shall be traceable to documented requirementsUnassisted

-Remarks:
-Limited management of requirements.
Dir-4.1RequiredRun-time failures shall be minimized
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2791 Definite: Right hand operand of shift operator is negative or too large.
2792 Apparent: Right hand operand of shift operator is negative or too large.
2801 Definite: Overflow in signed arithmetic operation.
2802 Apparent: Overflow in signed arithmetic operation.
2811 Definite: Dereference of NULL pointer.
2812 Apparent: Dereference of NULL pointer.
2821 Definite: Arithmetic operation on NULL pointer.
2822 Apparent: Arithmetic operation on NULL pointer.
2831 Definite: Division by zero.
2832 Apparent: Division by zero.
2841 Definite: Dereference of an invalid pointer value.
2842 Apparent: Dereference of an invalid pointer value.
2845 Constant: Maximum number of characters to be written is larger than the target buffer size.
2846 Definite: Maximum number of characters to be written is larger than the target buffer size.
2847 Apparent: Maximum number of characters to be written is larger than the target buffer size.
2871 Infinite loop identified.
2872 This loop, if entered, will never terminate.
2877 This loop will never be executed more than once.
-
Dir-4.10RequiredPrecautions shall be taken in order to prevent the contents of a header file being included more then once
- - - - - -
QacDescription
0883 Include file code is not protected against repeated inclusion
-
Dir-4.11RequiredThe validity of values passed to library functions shall be checkedUnassisted

-Remarks:
-No automated check deployed.
-Manual checks done by developers.
Dir-4.12RequiredDynamic memory allocation shall not be usedUnassisted

-Remarks:
-No memory allocation functions (malloc(), calloc(), realloc()) being called in RFAL.
Dir-4.13AdvisoryFunctions which are designed to provide operations on a resource should be called in an appropriate sequenceUnassisted
Dir-4.14RequiredThe validity of values received from external sources shall be checked
- - - - - -
QacDescription
2956 Definite: Using object '%s' with tainted value.
-
Dir-4.2AdvisoryAll usage of assembly language should be documented
- - - - - - - - - -
QacDescription
1003 [E] '#%s' is a language extension for in-line assembler. All statements located between #asm and #endasm will be ignored.
1006 [E] This in-line assembler construct is a language extension. The code has been ignored.
-
Dir-4.3RequiredAssembly language shall be encapsulated and isolated
- - - - - - - - - -
QacDescription
3006 This function contains a mixture of in-line assembler statements and C statements.
3008 This function contains a mixture of in-line assembler statements and C code.
-
Dir-4.4AdvisorySections of code should not be "commented out"Unassisted
Dir-4.5AdvisoryIdentifiers in the same name space with overlapping visibility should be typographically unambiguousUnassisted
Dir-4.6Advisorytypedefs that indicate size and signedness should be used in place of the basic numerical types
- - - - - -
QacDescription
5209 Use of basic type '%s'.
-
Dir-4.7RequiredIf a function returns error information, then that error information shall be testedUnassisted

-Remarks:
-Dir-4.7 is similar to Rule-17.7 which is currently dismissed.
-This directive is consequently considered as disapplied.
Dir-4.8AdvisoryIf a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hiddenUnassisted
Dir-4.9AdvisoryA function should be used in preference to a function-like macro where they are interchangeable
- - - - - -
QacDescription
3453 A function could probably be used instead of this function-like macro.
-
Rule-1.1RequiredThe program shall contain no violations of the standard C syntax and constraints, and shall not exceed the implementation's translation limits
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0232 [C] Value of hex escape sequence is not representable in type 'unsigned char'.
0233 [C] Value of octal escape sequence is not representable in type 'unsigned char'.
0244 [C] Value of character constant is not representable in type 'int'.
0268 [S] Comment open at end of translation unit.
0321 [C] Declaration within 'for' statement defines an identifier '%s' which is not an object.
0322 [C] Illegal storage class specifier used in 'for' statement declaration.
0338 [C] Octal or hex escape sequence value is too large for 'unsigned char' or 'wchar_t' type.
0422 [C] Function call contains fewer arguments than prototype specifies.
0423 [C] Function call contains more arguments than prototype specifies.
0426 [C] Called function has incomplete return type.
0427 [C] Object identifier used as if it were a function or a function pointer identifier.
0429 [C] Function argument is not of arithmetic type.
0430 [C] Function argument is not of compatible 'struct'/'union' type.
0431 [C] Function argument points to a more heavily qualified type.
0432 [C] Function argument is not of compatible pointer type.
0435 [C] The 'struct'/'union' member '%s' does not exist.
0436 [C] Left operand of '.' must be a 'struct' or 'union' object.
0437 [C] Left operand of '->' must be a pointer to a 'struct' or 'union' object.
0446 [C] Operand of ++/-- must have scalar (arithmetic or pointer) type.
0447 [C] Operand of ++/-- must be a modifiable object.
0448 [C] Operand of ++/-- must not be a pointer to an object of unknown size.
0449 [C] Operand of ++/-- must not be a pointer to a function.
0450 [C] An expression of array type cannot be cast.
0451 [C] Subscripting requires a pointer (or array lvalue).
0452 [C] Cannot subscript a pointer to an object of unknown size.
0453 [C] An array subscript must have integral type.
0454 [C] The address-of operator '&' cannot be applied to an object declared with 'register'.
0456 [C] This expression does not have an address - '&' may only be applied to an lvalue or a function designator.
0457 [C] The address-of operator '&' cannot be applied to a bit-field.
0458 [C] Indirection operator '*' requires operand of pointer type.
0460 [C] The keyword static is used in the declaration of the index of an array which is not a function parameter.
0461 [C] The keyword static is used in the declaration of an inner index of a multi-dimensional array.
0462 [C] A type qualifier (const, volatile or restrict) is used in the declaration of the index of an array which is not a function parameter.
0463 [C] A type qualifier (const, volatile or restrict) is used in the declaration of an inner index of a multi-dimensional array.
0466 [C] Unary '+' requires arithmetic operand.
0467 [C] Operand of '!' must have scalar (arithmetic or pointer) type.
0468 [C] Unary '-' requires arithmetic operand.
0469 [C] Bitwise not '~' requires integral operand.
0476 [C] 'sizeof' cannot be applied to a bit-field.
0477 [C] 'sizeof' cannot be applied to a function.
0478 [C] 'sizeof' cannot be applied to an object of unknown size.
0481 [C] Only scalar expressions may be cast to other types.
0482 [C] Expressions may only be cast to 'void' or scalar types.
0483 [C] A pointer to an object of unknown size cannot be the operand of an addition operator.
0484 [C] A pointer to an object of unknown size cannot be the operand of a subtraction operator.
0485 [C] Only integral expressions may be added to pointers.
0486 [C] Only integral expressions and compatible pointers may be subtracted from pointers.
0487 [C] If two pointers are subtracted, they must be pointers that address compatible types.
0493 [C] Type of left operand is not compatible with this operator.
0494 [C] Type of right operand is not compatible with this operator.
0495 [C] Left operand of '%', '<<', '>>', '&', '^' or '|' must have integral type.
0496 [C] Right operand of '%', '<<', '>>', '&', '^' or '|' must have integral type.
0513 [C] Relational operator used to compare pointers to incompatible types.
0514 [C] Relational operator used to compare a pointer with an incompatible operand.
0515 [C] Equality operator used to compare a pointer with an incompatible operand.
0536 [C] First operand of '&&', '||' or '?' must have scalar (arithmetic or pointer) type.
0537 [C] Second operand of '&&' or '||' must have scalar (arithmetic or pointer) type.
0540 [C] 2nd and 3rd operands of conditional operator '?' must have compatible types.
0541 [C] Argument no. %s does not have object type.
0542 [C] Controlling expression must have scalar (arithmetic or pointer) type.
0546 [C] 'enum %s' has unknown content. Use of an enum tag with undefined content is not permitted.
0547 [C] This declaration of tag '%s' conflicts with a previous declaration.
0550 [C] Left operand of '+=' or '-=' is a pointer to an object of unknown size.
0554 [C] 'static %s()' has been declared and called but no definition has been given.
0555 [C] Invalid assignment to object of void type or array type.
0556 [C] Left operand of assignment must be a modifiable object.
0557 [C] Right operand of assignment is not of arithmetic type.
0558 [C] Right operand of '+=' or '-=' must have integral type when left operand is a pointer.
0559 [C] Right operand of '<<=', '>>=', '&=', '|=', '^=' or '%=' must have integral type.
0560 [C] Left operand of '<<=', '>>=', '&=', '|=', '^=' or '%=' must have integral type.
0561 [C] Right operand of assignment is not of compatible 'struct'/'union' type.
0562 [C] Right operand of assignment points to a more heavily qualified type.
0563 [C] Right operand of assignment is not of compatible pointer type.
0564 [C] Left operand of assignment must be an lvalue (it must designate an object).
0565 [C] Left operand of '+=' or '-=' must be of arithmetic or pointer to object type.
0580 [C] Constant is too large to be representable.
0588 [C] Width of bit-field must be an integral constant expression.
0589 [C] Enumeration constant must be an integral constant expression.
0590 [C] Array bound must be an integral constant expression.
0591 [C] A 'case' label must be an integral constant expression.
0605 [C] A declaration must declare a tag or an identifier.
0616 [C] Illegal combination of type specifiers or storage class specifiers.
0619 [C] The identifier '%s' has already been defined in the current scope within the ordinary identifier namespace.
0620 [C] Cannot initialize '%s' because it has unknown size.
0621 [C] The struct/union '%s' cannot be initialized because it has unknown size.
0622 [C] The identifier '%s' has been declared both with and without linkage in the same scope.
0627 [C] '%s' has different type to previous declaration in the same scope.
0628 [C] '%s' has different type to previous declaration at wider scope.
0629 [C] More than one definition of '%s' (with internal linkage).
0631 [C] More than one declaration of '%s' (with no linkage).
0638 [C] Duplicate member name '%s' in 'struct' or 'union'.
0640 [C] '%s' in 'struct' or 'union' type may not have 'void' type.
0641 [C] '%s' in 'struct' or 'union' type may not have function type.
0642 [C] '%s' in 'struct' or 'union' type may not be an array of unknown size.
0643 [C] '%s' in 'struct' or 'union' type may not be a 'struct' or 'union' with unknown content.
0644 [C] Width of bit-field must be no bigger than the width of an 'int'.
0645 [C] A zero width bit-field cannot be given a name.
0646 [C] Enumeration constants must have values representable as 'int's.
0649 [C] K&R style declaration of parameters is not legal after a function header that includes a parameter list.
0650 [C] Illegal storage class specifier on named function parameter.
0651 [C] Missing type specifiers in function declaration.
0653 [C] Duplicate definition of 'struct', 'union' or 'enum' tag '%s'.
0655 [C] Illegal storage class specifier on unnamed function parameter.
0656 [C] Function return type cannot be function or array type, or an incomplete struct/union (for function definition).
0657 [C] Unnamed parameter specified in function definition.
0659 [C] The identifier '%s' was not given in the parameter list.
0664 [C] Parameter specified with type 'void'.
0665 [C] Two parameters have been declared with the same name '%s'.
0669 [C] The restrict qualifier can only be applied to pointer types derived from object or incomplete types.
0671 [C] Initializer for object of arithmetic type is not of arithmetic type.
0673 [C] Initializer points to a more heavily qualified type.
0674 [C] Initializer for pointer is of incompatible type.
0675 [C] Initializer is not of compatible 'struct'/'union' type.
0677 [C] Array size is negative, or unrepresentable.
0682 [C] Initializer for object of a character type is a string literal.
0683 [C] Initializer for object of a character type is a wide string literal.
0684 [C] Too many initializers.
0685 [C] Initializer for any object with static storage duration must be a constant expression.
0690 [C] String literal contains too many characters to initialize object.
0698 [C] String literal used to initialize an object of incompatible type.
0699 [C] String literal used to initialize a pointer of incompatible type.
0708 [C] No definition found for the label '%s' in this function.
0709 [C] Initialization of locally declared 'extern %s' is illegal.
0736 [C] 'case' label does not have unique value within this 'switch' statement.
0737 [C] More than one 'default' label found in 'switch' statement.
0738 [C] Controlling expression in a 'switch' statement must have integral type.
0746 [C] 'return exp;' found in '%s()' whose return type is 'void'.
0747 [C] 'return exp;' found in '%s()' whose return type is qualified 'void'.
0755 [C] 'return' expression is not of arithmetic type.
0756 [C] 'return' expression is not of compatible 'struct'/'union' type.
0757 [C] 'return' expression points to a more heavily qualified type.
0758 [C] 'return' expression is not of compatible pointer type.
0766 [C] 'continue' statement found outside an iteration statement.
0767 [C] 'break' statement found outside a 'switch' or iteration statement.
0768 [C] 'case' or 'default' found outside a 'switch' statement.
0774 [C] 'auto' may not be specified on global declaration of '%s'.
0775 [C] 'register' may not be specified on global declaration of '%s'.
0801 [C] The '##' operator may not be the first token in a macro replacement list.
0802 [C] The '##' operator may not be the last token in a macro replacement list.
0803 [C] The '#' operator may only appear before a macro parameter.
0804 [C] Macro parameter '%s' is not unique.
0811 [C] The glue operator '##' may only appear in a '#define' preprocessing directive.
0812 [C] Header name token '' found outside '#include' preprocessing directive.
0817 [S] Closing quote or bracket '>' missing from include filename.
0818 [Q] Cannot find '%s' - Perhaps the appropriate search path was not given ?
0821 [C] '#include' does not identify a header or source file that can be processed.
0834 [C] Function-like macro '%s()' is being redefined as an object-like macro.
0835 [C] Macro '%s' is being redefined with different parameter names.
0844 [C] Macro '%s' is being redefined with a different replacement list.
0845 [C] Object-like macro '%s' is being redefined as a function-like macro.
0851 [C] More arguments in macro call than specified in definition.
0852 [S] Unable to find the ')' that marks the end of the macro call.
0866 [C] The string literal in a '#line' directive cannot be a 'wide string literal'.
0873 [C] Preprocessing token cannot be converted to an actual token.
0877 [C] '#if' and '#elif' expressions may contain only integral constants.
0940 [C] Illegal usage of a variably modified type.
0941 [C] A variable length array may not be initialized.
0943 [C] Jump to label '%s' is a jump into the scope of an identifier with variably modified type.
0944 [C] The label '%s' is inside the scope of an identifier with variably modified type.
1023 [C] Using '__alignof__' on function types is illegal.
1024 [C] Using '__alignof__' on incomplete types is illegal.
1025 [C] Using '__alignof__' on bit-fields is illegal.
1033 [C] The identifier __VA_ARGS__ may only be used in the replacement list of a variadic macro.
1047 [C] Function is being declared with default argument syntax after a previous call to the function. This is not allowed.
1048 [C] Default argument values are missing for some parameters in this function declaration. This is not allowed.
1050 [C] Nested functions cannot be 'extern' or 'static'.
1061 [C] Structure '%1s' with flexible array member '%2s' cannot be used in the declaration of structure member '%3s'.
1062 [C] Structure '%1s' with flexible array member '%2s' cannot be used in the declaration of array elements.
3236 [C] 'inline' may not be applied to function 'main'.
3237 [C] inline function '%1s' has external linkage and is defining an object, '%2s', with static storage duration.
3238 [C] inline function '%1s' has external linkage and is referring to an object, '%2s', with internal linkage.
3244 [C] 'inline' may only be used in the declaration of a function identifier.
-
Rule-1.2AdvisoryLanguage extensions should not be used
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0240 [E] This file contains the control-M character at the end of a line.
0241 [E] This file contains the control-Z character - was this transferred from a PC?
0246 [E] Binary integer constants are a language extension.
0551 [E] Cast may not operate on the left operand of the assignment operator.
0601 [E] Function 'main()' is not of type 'int (void)' or 'int (int, char *[])'.
0633 [E] Empty structures and unions are a language extension.
0635 [E] Bit-fields in this struct/union have been declared with types other than int, signed int, unsigned int or _Bool.
0660 [E] Defining an unnamed member in a struct or union. This is a language extension.
0662 [E] Accessing a member of an unnamed struct or union member in this way is a language extension.
0830 [E] Unrecognized text encountered after a preprocessing directive.
0831 [E] Use of '\\' in this '#include' line is a PC extension - this usage is non-portable.
0840 [E] Extra tokens at end of #include directive.
0883 Include file code is not protected against repeated inclusion
0899 [E] Unrecognized preprocessing directive has been ignored - assumed to be a language extension.
0981 [E] Redundant semicolon in 'struct' or 'union' member declaration list is a language extension.
1001 [E] '#include %s' is a VMS extension.
1002 [E] '%s' is not a legal identifier in ISO C.
1003 [E] '#%s' is a language extension for in-line assembler. All statements located between #asm and #endasm will be ignored.
1006 [E] This in-line assembler construct is a language extension. The code has been ignored.
1008 [E] '#%s' is not a legal ISO C preprocessing directive.
1012 [E] Use of a C++ reference type ('type &') will be treated as a language extension.
1014 [E] Non-standard type specifier - this will be treated as a language extension.
1015 [E] '%s' is not a legal keyword in ISO C - this will be treated as a language extension.
1019 [E] '@ address' is not supported in ISO C - this will be treated as a language extension.
1020 [E] '__typeof__' is not supported in ISO C, and is treated as a language extension.
1021 [E] A statement expression is not supported in ISO C, and is treated as a language extension.
1022 [E] '__alignof__' is not supported in ISO C, and is treated as a language extension.
1026 [E] The indicated @word construct has been ignored.
1028 [E] Use of the sizeof operator in a preprocessing directive is a language extension.
1029 [E] Whitespace encountered between backslash and new-line has been ignored.
1034 [E] Macro defined with named variable argument list. This is a language extension.
1035 [E] No macro arguments supplied for variable argument list. This is a language extension.
1036 [E] Comma before ## ignored in expansion of variadic macro. This is a language extension.
1037 [E] Arrays of length zero are a language extension.
1038 [E] The sequence ", ##__VA_ARGS__" is a language extension.
1039 [E] Treating array of length one as potentially flexible member.
1041 [E] Empty aggregate initializers are a language extension.
1042 [E] Using I64 or UI64 as an integer constant suffix. This is a language extension.
1043 [E] Defining an anonymous union object. This is a language extension.
1044 [E] Defining an anonymous struct object. This is a language extension.
1045 [E] Use of the #include_next preprocessing directive is a language extension.
1046 [E] Function is being declared with default argument syntax. This is a language extension.
1049 [E] Nested functions are a language extension.
3445 [E] Conditional expression with middle operand omitted is a language extension.
3664 [E] Using a dot operator to access an individual bit is a language extension.
-
Rule-1.3RequiredThere shall be no occurrence of undefined or critical unspecified behaviour
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0160 [U] Using unsupported conversion specifier number %s.
0161 [U] Unknown length modifier used with 'i' or 'd' conversion specifier, number %s.
0162 [U] Unknown length modifier used with 'o' conversion specifier, number %s.
0163 [U] Unknown length modifier used with 'u' conversion specifier, number %s.
0164 [U] Unknown length modifier used with 'x' conversion specifier, number %s.
0165 [U] Unknown length modifier used with 'X' conversion specifier, number %s.
0166 [U] Unknown length modifier used with 'f' conversion specifier, number %s.
0167 [U] Unknown length modifier used with 'e' conversion specifier, number %s.
0168 [U] Unknown length modifier used with 'E' conversion specifier, number %s.
0169 [U] Unknown length modifier used with 'g' conversion specifier, number %s.
0170 [U] Unknown length modifier used with 'G' conversion specifier, number %s.
0171 [U] Unknown length modifier used with 'c' conversion specifier, number %s.
0172 [U] Unknown length modifier used with '%%' conversion specifier, number %s.
0173 [U] Unknown length modifier used with 's' conversion specifier, number %s.
0174 [U] Unknown length modifier used with 'n' conversion specifier, number %s.
0175 [U] Unknown length modifier used with 'p' conversion specifier, number %s.
0176 [U] Incomplete conversion specifier, number %s.
0177 [U] Field width of format conversion specifier exceeds 509 characters.
0178 [U] Precision of format conversion specifier exceeds 509 characters.
0179 [U] Argument type does not match conversion specifier number %s.
0184 [U] Insufficient arguments to satisfy conversion specifier, number %s.
0185 [U] Call contains more arguments than conversion specifiers.
0186 [U] A call to this function must include at least one argument.
0190 [U] Using unsupported conversion specifier number %s.
0191 [U] Unknown length modifier used with 'd/i/n' conversion specifier, number %s.
0192 [U] Unknown length modifier used with 'o' conversion specifier, number %s.
0193 [U] Unknown length modifier used with 'u' conversion specifier, number %s.
0194 [U] Unknown length modifier used with 'x/X' conversion specifier, number %s.
0195 [U] Unknown length modifier used with 'e/E/f/F/g/G' conversion specifier, number %s.
0196 [U] Unknown length modifier used with 's' conversion specifier, number %s.
0197 [U] Unknown length modifier used with 'p' conversion specifier, number %s.
0198 [U] Unknown length modifier used with '%%' conversion specifier, number %s.
0199 [U] Unknown length modifier used with '[' conversion specifier, number %s.
0200 [U] Unknown length modifier used with 'c' conversion specifier, number %s.
0201 [U] Incomplete conversion specifier, number %s.
0203 [U] Value of character prior to '-' in '[]' is greater than following character.
0204 [U] Field width of format conversion specifier exceeds 509 characters.
0206 [U] Argument type does not match conversion specifier number %s.
0207 [U] 'scanf' expects address of objects being stored into.
0208 [U] Same character occurs in scanset more than once.
0235 [U] Unknown escape sequence.
0275 [U] Floating value is out of range for conversion to destination type.
0301 [u] Cast between a pointer to object and a floating type.
0302 [u] Cast between a pointer to function and a floating type.
0304 [U] The address of an array declared 'register' may not be computed.
0307 [u] Cast between a pointer to object and a pointer to function.
0309 [U] Integral type is not large enough to hold a pointer value.
0327 [I] Cast between a pointer to void and an floating type.
0337 [U] String literal has undefined value. This may be a result of using '#' on \\.
0400 [U] '%s' is modified more than once between sequence points - evaluation order unspecified.
0401 [U] '%s' may be modified more than once between sequence points - evaluation order unspecified.
0402 [U] '%s' is modified and accessed between sequence points - evaluation order unspecified.
0403 [U] '%s' may be modified and accessed between sequence points - evaluation order unspecified.
0404 More than one read access to volatile objects between sequence points.
0405 More than one modification of volatile objects between sequence points.
0475 [u] Operand of 'sizeof' is an expression designating a bit-field.
0543 [U] 'void' expressions have no value and may not be used in expressions.
0544 [U] The value of an incomplete 'union' may not be used.
0545 [U] The value of an incomplete 'struct' may not be used.
0602 [U] The identifier '%s' is reserved for use by the library.
0603 [U] The macro identifier '%s' is reserved.
0623 [U] '%s' has incomplete type and no linkage - this is undefined.
0625 [U] '%s' has been declared with both internal and external linkage - the behaviour is undefined.
0626 [U] '%s' has different type to previous declaration (which is no longer in scope).
0630 [U] More than one definition of '%s' (with external linkage).
0632 [U] Tentative definition of '%s' with internal linkage cannot have unknown size.
0636 [U] There are no named members in this 'struct' or 'union'.
0654 [U] Using 'const' or 'volatile' in a function return type is undefined.
0658 [U] Parameter cannot have 'void' type.
0661 [U] '%s()' may not have a storage class specifier of 'static' when declared at block scope.
0667 [U] '%s' is declared as a typedef and may not be redeclared as an object at an inner scope without an explicit type specifier.
0668 [U] '%s' is declared as a typedef and may not be redeclared as a member of a 'struct' or 'union' without an explicit type specifier.
0672 [U] The initializer for a 'struct', 'union' or array is not enclosed in braces.
0676 [u] Array element is of function type. Arrays cannot be constructed from function types.
0678 [u] Array element is array of unknown size. Arrays cannot be constructed from incomplete types.
0680 [u] Array element is 'void' or an incomplete 'struct' or 'union'. Arrays cannot be constructed from incomplete types.
0706 [U] Label '%s' is not unique within this function.
0745 [U] 'return;' found in '%s()', which has been defined with a non-'void' return type.
0777 [U] External identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
0779 [U] Identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
0813 [U] Using any of the characters ' " or /* in '#include <%s>' gives undefined behaviour.
0814 [U] Using the characters ' or /* in '#include "%s"' gives undefined behaviour.
0836 [U] Definition of macro named 'defined'.
0837 [U] Use of '#undef' to remove the operator 'defined'.
0840 [E] Extra tokens at end of #include directive.
0848 [U] Attempting to #undef '%s', which is a predefined macro name.
0853 [U] Macro arguments contain a sequence of tokens that has the form of a preprocessing directive.
0854 [U] Attempting to #define '%s', which is a predefined macro name.
0864 [U] '#line' directive specifies line number which is not in the range 1 to 32767.
0865 [U] '#line' directive is badly formed.
0867 [U] '#line' has not been followed by a line number.
0872 [U] Result of '##' operator is not a legal preprocessing token.
0874 [U] Character string literal and wide character string literal are adjacent.
0885 [U] The token 'defined' is generated in the expansion of this macro.
0887 [U] Use of 'defined' must match either 'defined(identifier)' or 'defined identifier'.
0888 [U] 'defined' requires an identifier as an argument.
0914 [U] Source file does not end with a newline character.
0915 [U] Source file ends with a backslash character followed by a newline.
0942 [U] A * can only be used to specify array size within function prototype scope.
1331 Type or number of arguments doesn't match previous use of the function.
1332 Type or number of arguments doesn't match prototype found later.
1333 Type or number of arguments doesn't match function definition found later.
2800 Constant: Overflow in signed arithmetic operation.
2810 Constant: Dereference of NULL pointer.
2820 Constant: Arithmetic operation on NULL pointer.
2830 Constant: Division by zero.
2840 Constant: Dereference of an invalid pointer value.
3113 [U] 'return' statement includes no expression but function '%s()' is implicitly of type 'int'.
3114 [U] Function '%s()' is implicitly of type 'int' but ends without returning a value.
3239 [U] inline function '%1s' has external linkage, but is not defined within this translation unit.
3311 [u] An earlier jump to this statement will bypass the initialization of local variables.
3312 [u] This goto statement will jump into a previous block and bypass the initialization of local variables.
3319 [U] Function called with number of arguments which differs from number of parameters in definition.
3320 Type of argument no. %s differs from its type in definition of function.
3437 [u] The assert macro has been suppressed to call a function of that name.
3438 [U] #undef'ing the assert macro to call a function of that name causes undefined behaviour.
1509 '%1s' has external linkage and has multiple definitions.
1510 '%1s' has external linkage and has incompatible declarations.
-
Rule-10.1RequiredOperands shall not be of an inappropriate essential type.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
3101 Unary '-' applied to an operand of type unsigned int or unsigned long gives an unsigned result.
3102 Unary '-' applied to an operand whose underlying type is unsigned.
4500 An expression of 'essentially Boolean' type (%1s) is being used as an array subscript.
4501 An expression of 'essentially Boolean' type (%1s) is being used as the %2s operand of this arithmetic operator (%3s).
4502 An expression of 'essentially Boolean' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
4503 An expression of 'essentially Boolean' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
4504 An expression of 'essentially Boolean' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
4505 An expression of 'essentially Boolean' type (%1s) is being used as the %2s operand of this relational operator (%3s).
4507 An expression of 'essentially Boolean' type (%1s) is being used as the operand of this increment/decrement operator (%2s).
4510 An expression of 'essentially character' type (%1s) is being used as an array subscript.
4511 An expression of 'essentially character' type (%1s) is being used as the %2s operand of this arithmetic operator (%3s).
4512 An expression of 'essentially character' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
4513 An expression of 'essentially character' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
4514 An expression of 'essentially character' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
4518 An expression of 'essentially character' type (%1s) is being used as the %2s operand of this logical operator (%3s).
4519 An expression of 'essentially character' type (%1s) is being used as the first operand of this conditional operator (%2s).
4521 An expression of 'essentially enum' type (%1s) is being used as the %2s operand of this arithmetic operator (%3s).
4522 An expression of 'essentially enum' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
4523 An expression of 'essentially enum' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
4524 An expression of 'essentially enum' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
4527 An expression of 'essentially enum' type is being used as the operand of this increment/decrement operator.
4528 An expression of 'essentially enum' type (%1s) is being used as the %2s operand of this logical operator (%3s).
4529 An expression of 'essentially enum' type (%1s) is being used as the first operand of this conditional operator (%2s).
4532 An expression of 'essentially signed' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
4533 An expression of 'essentially signed' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
4534 An expression of 'essentially signed' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
4538 An expression of 'essentially signed' type (%1s) is being used as the %2s operand of this logical operator (%3s).
4539 An expression of 'essentially signed' type (%1s) is being used as the first operand of this conditional operator (%2s).
4542 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
4543 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
4548 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the %2s operand of this logical operator (%3s).
4549 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the first operand of this conditional operator (%2s).
4558 An expression of 'essentially unsigned' type (%1s) is being used as the %2s operand of this logical operator (%3s).
4559 An expression of 'essentially unsigned' type (%1s) is being used as the first operand of this conditional operator (%2s).
4568 An expression of 'essentially floating' type (%1s) is being used as the %2s operand of this logical operator (%3s).
4569 An expression of 'essentially floating' type (%1s) is being used as the first operand of this conditional operator (%2s).
-
Rule-10.2RequiredExpressions of essentially character type shall not be used inappropriately in addition and subtraction operations
- - - - - - - - - - - - - - - - - -
QacDescription
1810 An operand of 'essentially character' type is being added to another operand of 'essentially character' type.
1811 An operand of 'essentially character' type is being subtracted from an operand of 'essentially signed' type.
1812 An operand of 'essentially character' type is being subtracted from an operand of 'essentially unsigned' type.
1813 An operand of 'essentially character' type is being balanced with an operand of 'essentially floating' type in this arithmetic operation.
-
Rule-10.3RequiredThe value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0570 This switch case label of 'essential type' '%1s', is not consistent with a controlling expression of essential type '%2s'.
0572 This switch case label of 'essential type' '%1s' is not consistent with a controlling expression which has an essential type of lower rank (%2s).
1257 An integer constant suffixed with L or LL is being converted to a type of lower rank on assignment.
1264 A suffixed floating constant is being converted to a different floating type on assignment.
1265 An unsuffixed floating constant is being converted to a different floating type on assignment.
1266 A floating constant is being converted to integral type on assignment.
1291 An integer constant of 'essentially unsigned' type is being converted to signed type on assignment.
1292 An integer constant of 'essentially signed' type is being converted to type char on assignment.
1293 An integer constant of 'essentially unsigned' type is being converted to type char on assignment.
1294 An integer constant of 'essentially signed' type is being converted to type _Bool on assignment.
1295 An integer constant of 'essentially unsigned' type is being converted to type _Bool on assignment.
1296 An integer constant of 'essentially signed' type is being converted to enum type on assignment.
1297 An integer constant of 'essentially unsigned' type is being converted to enum type on assignment.
1298 An integer constant of 'essentially signed' type is being converted to floating type on assignment.
1299 An integer constant of 'essentially unsigned' type is being converted to floating type on assignment.
2850 Constant: Implicit conversion to a signed integer type of insufficient size.
2890 Constant: Negative value implicitly converted to an unsigned type.
2900 Constant: Positive integer value truncated by implicit conversion to a smaller unsigned type.
4401 An expression of 'essentially Boolean' type (%1s) is being converted to character type, '%2s' on assignment.
4402 An expression of 'essentially Boolean' type (%1s) is being converted to enum type, '%2s' on assignment.
4403 An expression of 'essentially Boolean' type (%1s) is being converted to signed type, '%2s' on assignment.
4404 An expression of 'essentially Boolean' type (%1s) is being converted to unsigned type, '%2s' on assignment.
4405 An expression of 'essentially Boolean' type (%1s) is being converted to floating type, '%2s' on assignment.
4410 An expression of 'essentially character' type (%1s) is being converted to Boolean type, '%2s' on assignment.
4412 An expression of 'essentially character' type (%1s) is being converted to enum type, '%2s' on assignment.
4413 An expression of 'essentially character' type (%1s) is being converted to signed type, '%2s' on assignment.
4414 An expression of 'essentially character' type (%1s) is being converted to unsigned type, '%2s' on assignment.
4415 An expression of 'essentially character' type (%1s) is being converted to floating type, '%2s' on assignment.
4420 An expression of 'essentially enum' type (%1s) is being converted to Boolean type, '%2s' on assignment.
4421 An expression of 'essentially enum' type (%1s) is being converted to character type, '%2s' on assignment.
4422 An expression of 'essentially enum' type (%1s) is being converted to a different enum type, '%2s' on assignment.
4423 An expression of 'essentially enum' type (%1s) is being converted to signed type, '%2s' on assignment.
4424 An expression of 'essentially enum' type (%1s) is being converted to unsigned type, '%2s' on assignment.
4425 An expression of 'essentially enum' type (%1s) is being converted to floating type, '%2s' on assignment.
4430 An expression of 'essentially signed' type (%1s) is being converted to Boolean type, '%2s' on assignment.
4431 An expression of 'essentially signed' type (%1s) is being converted to character type, '%2s' on assignment.
4432 An expression of 'essentially signed' type (%1s) is being converted to enum type, '%2s' on assignment.
4434 A non-constant expression of 'essentially signed' type (%1s) is being converted to unsigned type, '%2s' on assignment.
4435 A non-constant expression of 'essentially signed' type (%1s) is being converted to floating type, '%2s' on assignment.
4437 A constant expression of 'essentially signed' type (%1s) is being converted to floating type, '%2s' on assignment.
4440 An expression of 'essentially unsigned' type (%1s) is being converted to Boolean type, '%2s' on assignment.
4441 An expression of 'essentially unsigned' type (%1s) is being converted to character type, '%2s' on assignment.
4442 An expression of 'essentially unsigned' type (%1s) is being converted to enum type, '%2s' on assignment.
4443 A non-constant expression of 'essentially unsigned' type (%1s) is being converted to a wider signed type, '%2s' on assignment.
4445 An expression of 'essentially unsigned' type (%1s) is being converted to floating type, '%2s' on assignment.
4446 A non-constant expression of 'essentially unsigned' type (%1s) is being converted to signed type, '%2s' on assignment.
4447 A constant expression of 'essentially unsigned' type (%1s) is being converted to signed type, '%2s' on assignment.
4450 An expression of 'essentially floating' type (%1s) is being converted to Boolean type, '%2s' on assignment.
4451 An expression of 'essentially floating' type (%1s) is being converted to character type, '%2s' on assignment.
4452 An expression of 'essentially floating' type (%1s) is being converted to enum type, '%2s' on assignment.
4453 An expression of 'essentially floating' type (%1s) is being converted to signed type, '%2s' on assignment.
4454 An expression of 'essentially floating' type (%1s) is being converted to unsigned type, '%2s' on assignment.
4460 A non-constant expression of 'essentially signed' type (%1s) is being converted to narrower signed type, '%2s' on assignment.
4461 A non-constant expression of 'essentially unsigned' type (%1s) is being converted to narrower unsigned type, '%2s' on assignment.
4462 A non-constant expression of 'essentially floating' type (%1s) is being converted to narrower floating type, '%2s' on assignment.
4463 A constant expression of 'essentially signed' type (%1s) is being converted to narrower signed type, '%2s' on assignment.
4464 A constant expression of 'essentially unsigned' type (%1s) is being converted to narrower unsigned type, '%2s' on assignment.
4465 A constant expression of 'essentially floating' type (%1s) is being converted to narrower floating type, '%2s' on assignment.
-
Rule-10.4RequiredBoth operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type category
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
1800 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this arithmetic operation.
1802 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this relational operation.
1803 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this equality operation.
1804 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this conditional operation.
1820 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this arithmetic operation.
1821 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this bitwise operation.
1822 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this relational operation.
1823 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this equality operation.
1824 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this conditional operation.
1830 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this arithmetic operation.
1831 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this bitwise operation.
1832 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this relational operation.
1833 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this equality operation.
1834 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this conditional operation.
1840 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this arithmetic operation.
1841 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this bitwise operation.
1842 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this relational operation.
1843 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this equality operation.
1844 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this conditional operation.
1850 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this arithmetic operation.
1851 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this bitwise operation.
1852 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this relational operation.
1853 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this equality operation.
1854 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this conditional operation.
1860 The operands of this arithmetic operator are of different 'essential signedness' but will generate a result of type 'signed int'.
1861 The operands of this bitwise operator are of different 'essential signedness' but will generate a result of type 'signed int'.
1862 The operands of this relational operator are of different 'essential signedness' but will both be promoted to 'signed int' for comparison.
1863 The operands of this equality operator are of different 'essential signedness' but will both be promoted to 'signed int' for comparison.
1864 The 2nd and 3rd operands of this conditional operator are of different 'essential signedness'. The result will be in the promoted type 'signed int'.
1880 The operands of this relational operator are expressions of different 'essential type' categories (%1s and %2s).
1881 The operands of this equality operator are expressions of different 'essential type' categories (%1s and %2s).
1882 The 2nd and 3rd operands of this conditional operator are expressions of different 'essential type' categories (%1s and %2s).
-
Rule-10.5AdvisoryThe value of an expression should not be cast to an inappropriate essential type
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
4301 An expression of 'essentially Boolean' type (%1s) is being cast to character type '%2s'.
4302 An expression of 'essentially Boolean' type (%1s) is being cast to enum type '%2s'.
4303 An expression of 'essentially Boolean' type (%1s) is being cast to signed type '%2s'.
4304 An expression of 'essentially Boolean' type (%1s) is being cast to unsigned type '%2s'.
4305 An expression of 'essentially Boolean' type (%1s) is being cast to floating type '%2s'.
4310 An expression of 'essentially character' type (%1s) is being cast to Boolean type, '%2s'.
4312 An expression of 'essentially character' type (%1s) is being cast to enum type, '%2s'.
4315 An expression of 'essentially character' type (%1s) is being cast to floating type, '%2s'.
4320 An expression of 'essentially enum' type (%1s) is being cast to Boolean type, '%2s'.
4322 An expression of 'essentially enum' type (%1s) is being cast to a different enum type, '%2s'.
4330 An expression of 'essentially signed' type (%1s) is being cast to Boolean type '%2s'.
4332 An expression of 'essentially signed' type (%1s) is being cast to enum type, '%2s'.
4340 An expression of 'essentially unsigned' type (%1s) is being cast to Boolean type '%2s'.
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
4350 An expression of 'essentially floating' type (%1s) is being cast to Boolean type '%2s'.
4351 An expression of 'essentially floating' type (%1s) is being cast to character type '%2s'.
4352 An expression of 'essentially floating' type (%1s) is being cast to enum type, '%2s'.
-
Rule-10.6RequiredThe value of a composite expression shall not be assigned to an object with wider essential type
- - - - - - - - - - - - - - - - - -
QacDescription
4490 A composite expression of 'essentially signed' type (%1s) is being converted to wider signed type, '%2s' on assignment.
4491 A composite expression of 'essentially unsigned' type (%1s) is being converted to wider unsigned type, '%2s' on assignment.
4492 A composite expression of 'essentially floating' type (%1s) is being converted to wider floating type, '%2s' on assignment.
4499 An expression which is the result of a ~ or << operation has been converted to a wider essential type on assignment.
-
Rule-10.7RequiredIf a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential type
- - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
1890 A composite expression of 'essentially signed' type (%1s) is being implicitly converted to a wider signed type, '%2s'.
1891 A composite expression of 'essentially unsigned' type (%1s) is being implicitly converted to a wider unsigned type, '%2s'.
1892 A composite expression of 'essentially floating' type (%1s) is being implicitly converted to a wider floating type, '%2s'.
1893 The 2nd and 3rd operands of this conditional operator are both 'essentially signed' ('%1s' and '%2s') but one is a composite expression of a narrower type than the other.
1894 The 2nd and 3rd operands of this conditional operator are both 'essentially unsigned' ('%1s' and '%2s') but one is a composite expression of a narrower type than the other.
1895 The 2nd and 3rd operands of this conditional operator are both 'essentially floating' ('%1s' and '%2s') but one is a composite expression of a narrower type than the other.
-
Rule-10.8RequiredThe value of a composite expression shall not be cast to a different essential type category or a wider essential type
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
4389 A composite expression of 'essentially char' type (%1s) is being cast to a different type category, '%2s'.
4390 A composite expression of 'essentially signed' type (%1s) is being cast to a wider signed type, '%2s'.
4391 A composite expression of 'essentially unsigned' type (%1s) is being cast to a wider unsigned type, '%2s'.
4392 A composite expression of 'essentially floating' type (%1s) is being cast to a wider floating type, '%2s'.
4393 A composite expression of 'essentially signed' type (%1s) is being cast to a different type category, '%2s'.
4394 A composite expression of 'essentially unsigned' type (%1s) is being cast to a different type category, '%2s'.
4395 A composite expression of 'essentially floating' type (%1s) is being cast to a different type category, '%2s'.
4398 An expression which is the result of a ~ or << operation has been cast to a different essential type category.
4399 An expression which is the result of a ~ or << operation has been cast to a wider type.
-
Rule-11.1RequiredConversions shall not be performed between a pointer to a function and any other type
- - - - - - - - - - - - - - - - - -
QacDescription
0302 [u] Cast between a pointer to function and a floating type.
0305 [I] Cast between a pointer to function and an integral type.
0307 [u] Cast between a pointer to object and a pointer to function.
0313 Casting to different function pointer type.
-
Rule-11.2RequiredConversions shall not be performed between a pointer to an incomplete type and any other type
- - - - - - - - - - - - - - - - - -
QacDescription
0308 Non-portable cast involving pointer to an incomplete type.
0323 [u] Cast between a pointer to incomplete type and a floating type.
0324 [u] Cast between a pointer to incomplete type and an integral type.
0325 [u] Cast between a pointer to incomplete type and a pointer to function.
-
Rule-11.3RequiredA cast shall not be performed between a pointer to object type and a pointer to a different object type
- - - - - - - - - -
QacDescription
0310 Casting to different object pointer type.
3305 Pointer cast to stricter alignment.
-
Rule-11.4AdvisoryA conversion should not be performed between a pointer to object and an integer type
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
0303 [I] Cast between a pointer to volatile object and an integral type.
0306 [I] Cast between a pointer to object and an integral type.
0360 An expression of pointer type is being converted to type _Bool on assignment.
0361 An expression of pointer type is being cast to type _Bool.
0362 An expression of essentially Boolean type is being cast to a pointer.
-
Rule-11.5AdvisoryA conversion should not be performed from pointer to void into pointer to object
- - - - - - - - - -
QacDescription
0316 [I] Cast from a pointer to void to a pointer to object type.
0317 [I] Implicit conversion from a pointer to void to a pointer to object type.
-
Rule-11.6RequiredA cast shall not be performed between pointer to void and an arithmetic type
- - - - - - - - - -
QacDescription
0326 [I] Cast between a pointer to void and an integral type.
0327 [I] Cast between a pointer to void and an floating type.
-
Rule-11.7RequiredA cast shall not be performed between pointer to object and a non-integer arithmetic type
- - - - - - - - - -
QacDescription
0301 [u] Cast between a pointer to object and a floating type.
0328 [u] Cast between a pointer to object and an essential type other than signed/unsigned.
-
Rule-11.8RequiredA cast shall not remove any const or volatile qualification from the type pointed to by a pointer
- - - - - - - - - -
QacDescription
0311 Dangerous pointer cast results in loss of const qualification.
0312 Dangerous pointer cast results in loss of volatile qualification.
-
Rule-11.9RequiredThe macro NULL shall be the only permitted form of integer null pointer constant
- - - - - - - - - -
QacDescription
3003 This character constant is being interpreted as a NULL pointer constant.
3004 This integral constant expression is being interpreted as a NULL pointer constant.
-
Rule-12.1AdvisoryThe precedence of operators within expressions should be made explicit
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
3389 Extra parentheses recommended to clarify the ordering of a % operator and another arithmetic operator (* / % + -).
3391 Extra parentheses recommended. A conditional operation is the operand of another conditional operator.
3392 Extra parentheses recommended. A shift, relational or equality operation is the operand of a second identical operator.
3394 Extra parentheses recommended. A shift, relational or equality operation is the operand of a different operator with the same precedence.
3395 Extra parentheses recommended. A * or / operation is the operand of a + or - operator.
3396 Extra parentheses recommended. A binary operation is the operand of a conditional operator.
3397 Extra parentheses recommended. A binary operation is the operand of a binary operator with different precedence.
-
Rule-12.2RequiredThe right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operand
- - - - - - - - - - - - - - - - - -
QacDescription
0499 Right operand of shift operator is greater than or equal to the width of the essential type of the left operand.
2790 Constant: Right hand operand of shift operator is negative or too large.
2791 Definite: Right hand operand of shift operator is negative or too large.
2792 Apparent: Right hand operand of shift operator is negative or too large.
-
Rule-12.3AdvisoryThe comma operator should not be used
- - - - - - - - - -
QacDescription
3417 The comma operator has been used outside a 'for' statement.
3418 The comma operator has been used in a 'for' statement.
-
Rule-12.4AdvisoryEvaluation of constant expressions should not lead to unsigned integer wrap-around
- - - - - -
QacDescription
2910 Constant: Wraparound in unsigned arithmetic operation.
-
Rule-12.5MandatoryThe sizeof operator shall not have an operand which is a function parameter declared as 'array of type'
- - - - - -
QacDescription
1321 Operand of sizeof is a function parameter of array type.
-
Rule-13.1RequiredInitializer lists shall not contain persistent side-effects
- - - - - -
QacDescription
3421 Expression with possible side effects is used in an initializer list.
-
Rule-13.2RequiredThe value of an expression and its persistent side-effects shall be the same under all permitted evaluation orders
- - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0400 [U] '%s' is modified more than once between sequence points - evaluation order unspecified.
0401 [U] '%s' may be modified more than once between sequence points - evaluation order unspecified.
0402 [U] '%s' is modified and accessed between sequence points - evaluation order unspecified.
0403 [U] '%s' may be modified and accessed between sequence points - evaluation order unspecified.
0404 More than one read access to volatile objects between sequence points.
0405 More than one modification of volatile objects between sequence points.
-
Rule-13.3AdvisoryA full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operator
- - - - - -
QacDescription
3440 Using the value resulting from a ++ or -- operation.
-
Rule-13.4AdvisoryThe result of an assignment operator should not be used
- - - - - - - - - -
QacDescription
3226 The result of an assignment is being used in an arithmetic operation or another assigning operation.
3326 The result of an assignment is being used in a logical operation.
-
Rule-13.5RequiredThe right hand operand of a logical && or || operator shall not contain persistent side effects
- - - - - -
QacDescription
3415 Right hand operand of '&&' or '||' is an expression with possible side effects.
-
Rule-13.6MandatoryThe operand of the sizeof operator shall not contain any expression which has potential side-effects
- - - - - - - - - -
QacDescription
0945 [C99] Operand of sizeof is an expression of variable length array type with side effects.
3307 The operand of 'sizeof' is an expression with implied side effects, but they will not be evaluated.
-
Rule-14.1RequiredA loop counter shall not have essentially floating type
- - - - - - - - - - - - - -
QacDescription
3339 Floating point variable used as 'while' loop control variable.
3340 Floating point variable used as 'for' loop control variable.
3342 Controlling expression of 'for' loop is a floating point comparison.
-
Rule-14.2RequiredA for loop shall be well-formed
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2461 Loop control variable in this 'for' statement, %s, has file scope.
2462 The variable initialized in the first expression of this 'for' statement is not the variable identified as the 'loop control variable' (%s).
2463 The variable incremented in the third expression of this 'for' statement is not the variable identified as the 'loop control variable' (%s).
2464 Loop control variable, %s, modified twice in for-loop header.
2467 Loop control variable in this 'for' statement, %s, is not modified inside loop.
2468 Loop control variable in this 'for' statement, %s, is not modified inside loop but has file scope.
2469 Loop control variable in this 'for' statement, %s, is modified in the body of the loop.
2471 Unable to identify a loop control variable.
2472 More than one possible loop control variable.
-
Rule-14.3RequiredControlling expressions shall not be invariant
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2741 This 'if' controlling expression is a constant expression and its value is 'true'.
2742 This 'if' controlling expression is a constant expression and its value is 'false'.
2990 The value of this loop controlling expression is always 'true'.
2991 The value of this 'if' controlling expression is always 'true'.
2992 The value of this 'if' controlling expression is always 'false'.
2993 The value of this 'do - while' loop controlling expression is always 'false'. The loop will only be executed once.
2994 The value of this 'while' or 'for' loop controlling expression is always 'false'. The loop will not be entered.
2997 The first operand of this conditional operator is always 'true'.
2998 The first operand of this conditional operator is always 'false'.
3493 The first operand of this conditional operator is always constant 'true'.
3494 The first operand of this conditional operator is always constant 'false'.
-
Rule-14.4RequiredThe controlling expression of an if-statement and the controlling expression of an iteration-statement shall have essentially Boolean type
- - - - - -
QacDescription
3344 Controlling expression is not an 'essentially Boolean' expression.
-
Rule-15.1AdvisoryThe goto statement should not be used
- - - - - -
QacDescription
2001 A 'goto' statement has been used.
-
Rule-15.2RequiredThe goto statement shall jump to a label declared later in the same function
- - - - - -
QacDescription
3310 This 'goto' statement involves a backward jump.
-
Rule-15.3RequiredAny label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statement
- - - - - -
QacDescription
3327 This goto statement references a label that is declared in a separate block.
-
Rule-15.4AdvisoryThere should be no more than one break or goto statement used to terminate any iteration statement
- - - - - -
QacDescription
0771 More than one 'break' statement has been used to terminate this iteration statement.
-
Rule-15.5AdvisoryA function should have a single point of exit at the end
- - - - - -
QacDescription
2889 This function has more than one 'return' path.
-
Rule-15.6RequiredThe body of an iteration-statement or a selection-statement shall be a compound-statement
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
2212 Body of control statement is not enclosed within braces.
2214 Body of control statement is on the same line and is not enclosed within braces.
2218 Body of switch statement is not enclosed within braces.
2219 Body of switch statement is on the same line and is not enclosed within braces.
3402 Braces are needed to clarify the structure of this 'if'-'if'-'else' statement.
-
Rule-15.7RequiredAll if ... else if constructs shall be terminated with an else statement
- - - - - - - - - -
QacDescription
2004 No concluding 'else' exists in this 'if'-'else'-'if' statement.
2013 This 'if .. else if ' construct 'else' statement is empty.
-
Rule-16.1RequiredAll switch statements shall be well-formed
- - - - - - - - - -
QacDescription
2008 Code statements precede the first label in this 'switch' construct.
3234 Declarations precede the first label in this 'switch' construct.
-
Rule-16.2RequiredA switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statement
- - - - - -
QacDescription
2019 'Switch' label is located within a nested code block.
-
Rule-16.3RequiredAn unconditional break statement shall terminate every switch-clause
- - - - - - - - - -
QacDescription
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
2020 Final 'switch' clause does not end with an explicit 'jump' statement.
-
Rule-16.4RequiredEvery switch statement shall have a default label
- - - - - - - - - -
QacDescription
2002 No 'default' label found in this 'switch' statement.
2016 This 'switch' statement 'default' clause is empty.
-
Rule-16.5RequiredA default label shall appear as either the first or the last switch label of a switch statement
- - - - - -
QacDescription
2012 This 'default' label is neither the first nor the last label within the 'switch' block.
-
Rule-16.6RequiredEvery switch statement shall have at least two switch-clauses
- - - - - -
QacDescription
3315 This 'switch' statement is redundant.
-
Rule-16.7RequiredA switch-expression shall not have essentially Boolean type
- - - - - -
QacDescription
0735 Switch expression is of essentially Boolean type.
-
Rule-17.1RequiredThe features of shall not be used
- - - - - - - - - -
QacDescription
5130 Use of standard header file .
1337 Function defined with a variable number of parameters.
-
Rule-17.2RequiredFunctions shall not call themselves, either directly or indirectly
- - - - - - - - - -
QacDescription
3670 Recursive call to function containing this call.
1520 Functions are indirectly recursive.
-
Rule-17.3MandatoryA function shall not be declared implicitly
- - - - - -
QacDescription
3335 No function declaration. Implicit declaration inserted: 'extern int %s();'.
-
Rule-17.4MandatoryAll exit paths from a function with non-void return type shall have an explicit return statement with an expression
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
0745 [U] 'return;' found in '%s()', which has been defined with a non-'void' return type.
2887 Function 'main' ends with an implicit 'return' statement.
2888 This function has been declared with a non-void 'return' type but ends with an implicit 'return ;' statement.
3113 [U] 'return' statement includes no expression but function '%s()' is implicitly of type 'int'.
3114 [U] Function '%s()' is implicitly of type 'int' but ends without returning a value.
-
Rule-17.5AdvisoryThe function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elements
- - - - - - - - - - - - - - - - - -
QacDescription
2781 Definite: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
2782 Apparent: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
2783 Suspicious: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
2784 Possible: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
-
Rule-17.6MandatoryThe declaration of an array parameter shall not contain the static keyword between the [ ]
- - - - - -
QacDescription
1058 [C99] The keyword 'static' is used in the declaration of a function parameter of array type.
-
Rule-17.7RequiredThe value returned by a function having non-void return type shall be used
- - - - - -
QacDescription
3200 '%s' returns a value which is not being used.
-
Rule-17.8AdvisoryA function parameter should not be modified
- - - - - - - - - - - - - -
QacDescription
1338 The parameter '%s' is being modified.
1339 Evaluating the address of the parameter '%s'.
1340 Storing the address of the parameter '%s' in a constant pointer.
-
Rule-18.1RequiredA pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operand
- - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2840 Constant: Dereference of an invalid pointer value.
2841 Definite: Dereference of an invalid pointer value.
2842 Apparent: Dereference of an invalid pointer value.
2930 Constant: Computing an invalid pointer value.
2931 Definite: Computing an invalid pointer value.
2932 Apparent: Computing an invalid pointer value.
-
Rule-18.2RequiredSubtraction between pointers shall only be applied to pointers that address elements of the same array
- - - - - - - - - - - - - -
QacDescription
2668 Subtraction of a pointer to an array and a pointer to a non array.
2761 Definite: Subtracting pointers that address different objects.
2762 Apparent: Subtracting pointers that address different objects.
-
Rule-18.3RequiredThe relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same object
- - - - - - - - - - - - - -
QacDescription
2669 Comparison of a pointer to an array and a pointer to a non array.
2771 Definite: Comparing pointers that address different objects.
2772 Apparent: Comparing pointers that address different objects.
-
Rule-18.4AdvisoryThe +, -, += and -= operators should not be applied to an expression of pointer type
- - - - - -
QacDescription
0488 Performing pointer arithmetic.
-
Rule-18.5AdvisoryDeclarations should contain no more than two levels of pointer nesting
- - - - - - - - - - - - - - - - - -
QacDescription
3260 Typedef defined with more than 2 levels of indirection.
3261 Member of struct/union defined with more than 2 levels of indirection.
3262 Object defined or declared with more than 2 levels of indirection.
3263 Function defined or declared with a return type which has more than 2 levels of indirection.
-
Rule-18.6RequiredThe address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to exist
- - - - - - - - - - - - - - - - - -
QacDescription
3217 Address of automatic object exported to a pointer with linkage or wider scope.
3225 Address of automatic object exported using a function parameter.
3230 Address of automatic object assigned to local pointer with static storage duration.
4140 Address of automatic object exported in function return value.
-
Rule-18.7RequiredFlexible array members shall not be declared
- - - - - -
QacDescription
1060 [C99] A flexible array member has been declared.
-
Rule-18.8RequiredVariable-length array types shall not be used
- - - - - - - - - -
QacDescription
1051 [C99] A variable length array has been declared.
1052 [C99] A variable length array of unspecified size has been declared.
-
Rule-19.1MandatoryAn object shall not be assigned or copied to an overlapping object
- - - - - - - - - - - - - -
QacDescription
0681 [U] Assignment between two incompatible members of the same union.
2776 Definite: Copy between overlapping objects.
2777 Apparent: Copy between overlapping objects.
-
Rule-19.2AdvisoryThe union keyword should not be used
- - - - - - - - - -
QacDescription
0750 A union type specifier has been defined.
0759 An object of union type has been defined.
-
Rule-2.1RequiredA project shall not contain unreachable code
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0594 Negative 'case' label expression is incompatible with unsigned controlling expression in 'switch' statement.
1460 'Switch' label value, %s, not contained in enum type.
2744 This 'while' or 'for' loop controlling expression is a constant expression and its value is 'false'. The loop will not be entered.
2880 This code is unreachable.
2882 This 'switch' statement will bypass the initialization of local variables.
3219 Static function '%s()' is not used within this translation unit.
1503 The function '%1s' is defined but is not used within this project.
-
Rule-2.2RequiredThere shall be no dead code
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2980 The value of this function parameter is never used before being modified.
2981 This initialization is redundant. The value of this object is never used before being modified.
2982 This assignment is redundant. The value of this object is never used before being modified.
2983 This assignment is redundant. The value of this object is never subsequently used.
2985 This operation is redundant. The value of the result is always that of the left-hand operand.
2986 This operation is redundant. The value of the result is always that of the right-hand operand.
2987 This function call produces no side effects and is redundant.
2995 The result of this logical operation is always 'true'.
2996 The result of this logical operation is always 'false'.
3110 The left-hand operand of this ',' has no side effects.
3112 This statement has no side-effect - it can be removed.
3404 Statement contains a redundant * operator at top level. *p++ means *(p++) not (*p)++.
3422 Statement contains a redundant operator at top level.
3423 Statement contains a redundant cast at top level.
3424 Statement contains a redundant & or | at top level.
3425 One branch of this conditional operation is a redundant expression.
3426 Right hand side of comma expression has no side effect and its value is not used.
3427 Right hand side of logical operator has no side effect and its value is not used.
-
Rule-2.3AdvisoryA project should not contain unused type declarations
- - - - - -
QacDescription
3205 The identifier '%s' is not used and could be removed.
-
Rule-2.4AdvisoryA project should not contain unused tag declarations
- - - - - - - - - -
QacDescription
3213 The tag '%s' is not used and could be removed.
1755 The tag '%1s' is declared but not used within this project.
-
Rule-2.5AdvisoryA project should not contain unused macro declarations
- - - - - -
QacDescription
3214 The macro '%s' is not used and could be removed.
-
Rule-2.6AdvisoryA function should not contain unused label declarations
- - - - - -
QacDescription
3202 The label '%s:' is not used in this function and could be removed.
-
Rule-2.7AdvisoryThere should be no unused parameters in functions
- - - - - -
QacDescription
3206 The parameter '%s' is not used in this function.
-
Rule-20.1Advisory#include directives should only be preceded by preprocessor directives or comments
- - - - - -
QacDescription
5087 Use of #include directive after code fragment.
-
Rule-20.10AdvisoryThe # and ## preprocessor operators should not be used
- - - - - - - - - -
QacDescription
0341 Using the stringify operator '#'.
0342 Using the glue operator '##'.
-
Rule-20.11RequiredA macro parameter immediately following a # operator shall not immediately be followed by a ## operator
- - - - - -
QacDescription
0892 This macro parameter is preceded by '#' and followed by '##'.
-
Rule-20.12RequiredA macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operators
- - - - - -
QacDescription
0893 Macro parameter '%s' is inconsistently subject to macro replacement.
-
Rule-20.13RequiredA line whose first token is # shall be a valid preprocessing directive
- - - - - -
QacDescription
3115 Unrecognized preprocessing directive has been ignored because of conditional inclusion directives.
-
Rule-20.14RequiredAll #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are related
- - - - - - - - - -
QacDescription
3317 '#if...' not matched by '#endif' in included file. This is probably an error.
3318 '#else'/'#elif'/'#endif' in included file matched '#if...' in parent file. This is probably an error.
-
Rule-20.2RequiredThe ', " or \ characters and the /* or // character sequences shall not occur in a header file name
- - - - - - - - - - - - - -
QacDescription
0813 [U] Using any of the characters ' " or /* in '#include <%s>' gives undefined behaviour.
0814 [U] Using the characters ' or /* in '#include "%s"' gives undefined behaviour.
0831 [E] Use of '\\' in this '#include' line is a PC extension - this usage is non-portable.
-
Rule-20.3RequiredThe #include directive shall be followed by either a or "filename" sequence
- - - - - - - - - - - - - -
QacDescription
0817 [S] Closing quote or bracket '>' missing from include filename.
0821 [C] '#include' does not identify a header or source file that can be processed.
0840 [E] Extra tokens at end of #include directive.
-
Rule-20.4RequiredA macro shall not be defined with the same name as a keyword
- - - - - - - - - -
QacDescription
3439 Macro redefines a keyword.
3468 The name of this macro is a reserved identifier in C90 and a keyword in C99.
-
Rule-20.5Advisory#undef should not be used
- - - - - -
QacDescription
0841 Using '#undef'.
-
Rule-20.6RequiredTokens that look like a preprocessing directive shall not occur within a macro argument
- - - - - -
QacDescription
0853 [U] Macro arguments contain a sequence of tokens that has the form of a preprocessing directive.
-
Rule-20.7RequiredExpressions resulting from the expansion of macro parameters shall be enclosed in parentheses
- - - - - - - - - -
QacDescription
3430 Macro argument expression may require parentheses.
3432 Simple macro argument expression is not parenthesized.
-
Rule-20.8RequiredThe controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1
- - - - - -
QacDescription
0894 '#%s' directive controlling expression does not evaluate to zero or one.
-
Rule-20.9RequiredAll identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluation
- - - - - -
QacDescription
3332 The macro '%s' used in this '#if' or '#elif' expression is not defined.
-
Rule-21.1Required#define and #undef shall not be used on a reserved identifier or reserved macro name
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0603 [U] The macro identifier '%s' is reserved.
0836 [U] Definition of macro named 'defined'.
0848 [U] Attempting to #undef '%s', which is a predefined macro name.
0854 [U] Attempting to #define '%s', which is a predefined macro name.
4600 The macro '%1s' is also defined in '<%2s>'.
4601 The macro '%1s' is the name of an identifier in '<%2s>'.
4620 The macro '%1s' may also be defined as a macro in '<%2s>'.
4621 The macro '%1s' may also be defined as a typedef in '<%2s>'.
-
Rule-21.10RequiredThe Standard Library time and date functions shall not be used
- - - - - -
QacDescription
5127 Use of standard header file .
-
Rule-21.11RequiredThe standard header file shall not be used
- - - - - -
QacDescription
5131 Use of standard header file .
-
Rule-21.12AdvisoryThe exception handling features of should not be used
- - - - - -
QacDescription
5136 Use of exception handling identifier: feclearexcept, fegetexceptflag, feraiseexcept, fesetexceptflag or fetestexcept.
-
Rule-21.13MandatoryAny value passed to a function in shall be representable as an unsigned char or be the value EOF
- - - - - - - - - -
QacDescription
2796 Definite: Calling a standard library character handling function with an invalid character value.
2797 Apparent: Calling a standard library character handling function with an invalid character value.
-
Rule-21.14RequiredThe Standard Library function memcmp shall not be used to compare null terminated strings
- - - - - - - - - -
QacDescription
2785 Constant: Null terminated string is being passed as argument to Standard Library function memcmp.
2786 Definite: Null terminated string is being passed as argument to Standard Library function memcmp.
-
Rule-21.15RequiredThe pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible types
- - - - - - - - - - - - - -
QacDescription
1487 Comparing the representations of objects of different types.
1495 Destination and source objects have incompatible types.
1496 Destination and source objects may have incompatible types.
-
Rule-21.16RequiredThe pointer arguments to the Standard Library function memcpy shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum type
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
1488 Comparison of a struct object representation.
1489 Comparison of a union object representation.
1490 Comparison of a floating point object representation.
1491 Comparison of an object representation.
1497 Comparison of a string object representation.
-
Rule-21.17MandatoryUse of the string handling functions from shall not result in accesses beyond the bounds of the objects referenced by their pointer parameters
- - - - - - - - - -
QacDescription
2835 Constant: Non null terminated string used in a string function.
2836 Definite: Non null terminated string used in a string function.
-
Rule-21.18MandatoryThe size_t argument passed to any function in shall have an appropriate value
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2840 Constant: Dereference of an invalid pointer value.
2841 Definite: Dereference of an invalid pointer value.
2842 Apparent: Dereference of an invalid pointer value.
2865 Constant: Using 0 as size parameter of a function call.
2866 Definite: Using 0 as size parameter of a function call.
2867 Apparent: Using 0 as size parameter of a function call.
2868 Suspicious: Using 0 as size parameter of a function call.
2869 Possible: Using 0 as size parameter of a function call.
-
Rule-21.19MandatoryThe pointers returned by the Standard Library functions lovaleconv, getenv, setlocale or strerror shall only be used as if they have pointer to const-qualified type
- - - - - - - - - - - - - - - - - -
QacDescription
1492 The result of library function '%s' is used to modify the referenced object.
1493 The result of library function '%s' is used as a pointer to a modifiable object.
1494 The result of library function '%s' might be modified.
1498 The string referenced by type 'struct lconv' member '%s' is being modified.
-
Rule-21.2RequiredA reserved identifier or macro name shall not be declared
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
0602 [U] The identifier '%s' is reserved for use by the library.
4602 The identifier '%1s' is declared as a macro in '<%2s>'.
4603 The object/function '%1s'is being defined with the same name as an ordinary identifier defined in '<%2s>'.
4604 The object/function '%1s' is being declared with the same name as an ordinary identifier defined in '<%2s>'.
4605 The typedef '%1s' is also defined in '<%2s>'.
4606 The typedef '%1s' has the same name as another ordinary identifier in '<%2s>'.
4607 The enum constant '%1s' has the same name as another ordinary identifier in '<%2s>'.
4608 The tag '%1s' is also defined in '<%2s>'.
-
Rule-21.20MandatoryThe pointer returned by the Standard Library functions asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale, or strerror shall not be used following a subsequent call to the same function
- - - - - - - - - -
QacDescription
2681 Definite: Using an invalidated value '%s' returned from a Standard Library function.
2682 Apparent: Using an invalidated value '%s' returned from a Standard Library function.
-
Rule-21.3RequiredThe memory allocation and deallocation functions of shall not be used
- - - - - -
QacDescription
5118 Use of memory allocation or deallocation function: calloc, malloc, realloc or free.
-
Rule-21.4RequiredThe standard header file shall not be used
- - - - - -
QacDescription
5132 Use of standard header file .
-
Rule-21.5RequiredThe standard header file shall not be used
- - - - - -
QacDescription
5123 Use of standard header file .
-
Rule-21.6RequiredThe Standard Library input/output functions shall not be used
- - - - - -
QacDescription
5124 The Standard Library input/output functions shall not be used
-
Rule-21.7RequiredThe atof, atoi, atol and atoll functions of shall not be used
- - - - - -
QacDescription
5125 Use of function: atof, atoi, atol or atoll.
-
Rule-21.8RequiredThe library functions abort, exit and system of shall not be used
- - - - - - - - - -
QacDescription
5126 Use of function: abort, exit or system.
5128 Use of function: getenv.
-
Rule-21.9RequiredThe library functions bsearch and qsort of shall not be used
- - - - - -
QacDescription
5135 Use of function: bsearch or qsort.
-
Rule-22.1RequiredAll resources obtained dynamically by means of Standard Library functions shall be explicitly released
- - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2701 Definite: Opened file is not closed.
2702 Apparent: Opened file is not closed.
2706 Definite: Allocated memory is not deallocated.
2707 Apparent: Allocated memory is not deallocated.
2736 Definite: Created resource is not destroyed.
2737 Apparent: Created resource is not destroyed.
-
Rule-22.10RequiredThe value of errno shall only be tested when the last function to be called was an errno-setting-function
- - - - - -
QacDescription
2503 Testing of 'errno' is not immediately preceded by a call to an 'errno' setting function.
-
Rule-22.2MandatoryA block of memory shall only be freed if it was allocated by means of a Standard Library function
- - - - - - - - - -
QacDescription
2721 Definite: Deallocation of non dynamic memory.
2722 Apparent: Deallocation of non dynamic memory.
-
Rule-22.3RequiredThe same file shall not be open for read and write access at the same time on different streams
- - - - - - - - - - - - - -
QacDescription
2691 Definite: The same file will be open with write access and another mode.
2692 Apparent: The same file will be open with write access and another mode.
2693 Suspicious: The same file will be open with write access and another mode.
-
Rule-22.4MandatoryThere shall be no attempt to write to a stream which has been opened as read-only
- - - - - - - - - - - - - -
QacDescription
2686 Definite: Writing to a file opened for reading.
2687 Apparent: Writing to a file opened for reading.
2688 Suspicious: Writing to a file opened for reading.
-
Rule-22.5MandatoryA pointer to a FILE object shall not be dereferenced
- - - - - - - - - -
QacDescription
1485 A pointer to a FILE object is dereferenced.
1486 A pointer to a FILE object is converted to a different type.
-
Rule-22.6MandatoryThe value of a pointer to a FILE shall not be used after the associated stream has been closed
- - - - - - - - - -
QacDescription
2696 Definite: Attempt to access a file which has been closed.
2697 Apparent: Attempt to access a file which has been closed.
-
Rule-22.7RequiredThe macro EOF shall on ly be compared with the unmodified return value from any Standard Library function capable of returning EOF
- - - - - - - - - -
QacDescription
2671 Definite: The value being compared with macro EOF does not originate from an EOF returning function.
2676 Definite: The value originating from an EOF returning function was modified before being compared with macro EOF.
-
Rule-22.8RequiredThe value of errno shall be set to zero prior to a call to an errno-setting-function
- - - - - -
QacDescription
2500 Call to '%s' is not immediately preceded by the zeroing of 'errno'.
-
Rule-22.9RequiredThe value of errno shall be tested against zero after calling an errno-setting-function
- - - - - -
QacDescription
2501 Call to '%s' is not immediately followed by the testing of 'errno'.
-
Rule-3.1RequiredThe character sequences /* and // shall not be used within a comment.
- - - - - -
QacDescription
3108 Nested comments are not recognized in the ISO standard.
-
Rule-3.2RequiredLine-splicing shall not be used in // comments.
- - - - - -
QacDescription
5134 C++ style comment uses line splicing.
-
Rule-4.1RequiredOctal and hexadecimal escape sequences shall be terminated
- - - - - - - - - -
QacDescription
3636 Octal escape sequence '%s' is not terminated.
3637 Hexadecimal escape sequence '%s' is not terminated.
-
Rule-4.2AdvisoryTrigraphs should not be used
- - - - - -
QacDescription
3601 Trigraphs (??x) are an ISO feature.
-
Rule-5.1RequiredExternal identifiers shall be distinct
- - - - - -
QacDescription
0777 [U] External identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
-
Rule-5.2RequiredIdentifiers declared in the same scope and name space shall be distinct
- - - - - -
QacDescription
0779 [U] Identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
-
Rule-5.3RequiredAn identifier declared in an inner scope shall not hide an identifier declared in an outer scope
- - - - - - - - - - - - - -
QacDescription
0795 Identifier matches other identifier(s) (e.g. '%s') in an outer scope within the specified number of significant characters.
2547 This declaration of tag '%s' hides a more global declaration.
3334 This declaration of '%s' hides a more global declaration.
-
Rule-5.4RequiredMacro identifiers shall be distinct
- - - - - - - - - -
QacDescription
0788 This identifier, '%s', is used as both a macro name and a function-like macro parameter name.
0791 [U] Macro identifier does not differ from other macro identifier(s) (e.g. '%s') within the specified number of significant characters.
-
Rule-5.5RequiredIdentifiers shall be distinct from macro names
- - - - - - - - - - - - - - - - - -
QacDescription
0784 Identifier '%s' is also used as a macro name.
0785 Identifier matches other macro name(s) (e.g. '%s') in first 31 characters.
0786 Identifier matches other macro name(s) (e.g. '%s') in first 63 characters.
0787 Identifier does not differ from other macro name(s) (e.g. '%s') within the specified number of significant characters.
-
Rule-5.6RequiredA typedef name shall be a unique identifier
- - - - - - - - - - - - - -
QacDescription
1506 The identifier '%1s' is declared as a typedef and is used elsewhere for a different kind of declaration.
1507 '%1s' is used as a typedef for different types.
1508 The typedef '%1s' is declared in more than one location.
-
Rule-5.7RequiredA tag name shall be a unique identifier
- - - - - - - - - -
QacDescription
2547 This declaration of tag '%s' hides a more global declaration.
1750 '%1s' has multiple definitions.
-
Rule-5.8RequiredIdentifiers that define objects or functions with external linkage shall be unique
- - - - - - - - - - - - - -
QacDescription
1525 Object/function with external linkage has same identifier as another object/function with internal linkage.
1526 Object with no linkage has same identifier as another object/function with external linkage.
1756 External identifier '%1s' shall be unique.
-
Rule-5.9AdvisoryIdentifiers that define objects or functions with internal linkage should be unique
- - - - - - - - - - - - - -
QacDescription
1525 Object/function with external linkage has same identifier as another object/function with internal linkage.
1527 Object/function with internal linkage has same identifier as another object/function with internal linkage.
1528 Object with no linkage has same identifier as another object/function with internal linkage.
-
Rule-6.1RequiredBit-fields shall only be declared with an appropriate type
- - - - - - - - - -
QacDescription
0634 [I] Bit-fields in this struct/union have not been declared explicitly as unsigned or signed.
0635 [E] Bit-fields in this struct/union have been declared with types other than int, signed int, unsigned int or _Bool.
-
Rule-6.2RequiredSingle-bit named bit fields shall not be of a signed type
- - - - - -
QacDescription
3660 Named bit-field consisting of a single bit declared with a signed type.
-
Rule-7.1RequiredOctal constants shall not be used
- - - - - - - - - -
QacDescription
0336 Macro defined as an octal constant.
0339 Octal constant used.
-
Rule-7.2RequiredA "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned type
- - - - - -
QacDescription
1281 Integer literal constant is of an unsigned type but does not include a "U" suffix.
-
Rule-7.3RequiredThe lowercase character "l" shall not be used in a literal suffix
- - - - - -
QacDescription
1280 A lowercase letter L (l) has been used in an integer or floating suffix.
-
Rule-7.4RequiredA string literal shall not be assigned to an object unless the object's type is "pointer to const-qualified char"
- - - - - - - - - -
QacDescription
0752 String literal passed as argument to function whose parameter is not a 'pointer to const'.
0753 String literal assigned to pointer which is not a 'pointer to const'.
-
Rule-8.1RequiredTypes shall be explicitly specified
- - - - - - - - - - - - - -
QacDescription
2050 The 'int' type specifier has been omitted from a function declaration.
2051 The 'int' type specifier has been omitted from an object declaration.
1525 Object/function with external linkage has same identifier as another object/function with internal linkage.
-
Rule-8.10RequiredAn inline function shall be declared with the static storage class
- - - - - - - - - -
QacDescription
3240 inline function '%s' is being defined with external linkage.
3243 inline function '%s' is also an 'external definition'.
-
Rule-8.11AdvisoryWhen an array with external linkage is declared, its size should be explicitly specified
- - - - - -
QacDescription
3684 Array declared with unknown size.
-
Rule-8.12RequiredWithin an enumerator list, the value of an implicitly-specified enumeration constant shall be unique
- - - - - -
QacDescription
0724 The value of this implicitly-specified enumeration constant is not unique.
-
Rule-8.13AdvisoryA pointer should point to a const-qualified type whenever possible
- - - - - -
QacDescription
3673 The object addressed by the pointer parameter '%s' is not modified and so the pointer could be of type 'pointer to const'.
-
Rule-8.14RequiredThe restrict type qualifier shall not be used
- - - - - -
QacDescription
1057 [C99] The keyword 'restrict' has been used.
-
Rule-8.2RequiredFunction types shall be in prototype form with named parameters
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
1335 Parameter identifiers missing in function prototype declaration.
1336 Parameter identifiers missing in declaration of a function type.
3001 Function has been declared with an empty parameter list.
3002 Defining '%s()' with an identifier list and separate parameter declarations is an obsolescent feature.
3007 "void" has been omitted when defining a function with no parameters.
-
Rule-8.3RequiredAll declarations of an object or function shall use the same names and type qualifiers
- - - - - - - - - - - - - -
QacDescription
0624 Function '%s' is declared using typedefs which are different to those in a previous declaration.
1330 The parameter identifiers in this function declaration differ from those in a previous declaration.
3675 Function parameter declared with type qualification which differs from previous declaration.
-
Rule-8.4RequiredA compatible declaration shall be visible when an object or function with external linkage is defined
- - - - - -
QacDescription
3408 '%s' has external linkage and is being defined without any previous declaration.
-
Rule-8.5RequiredAn external object or function shall be declared once in one and only one file
- - - - - - - - - - - - - -
QacDescription
3449 Multiple declarations of external object or function.
3451 The global identifier '%s' has been declared in more than one file.
1513 Identifier '%1s' with external linkage has separate non-defining declarations in more than one location.
-
Rule-8.6RequiredAn identifier with external linkage shall have exactly one external definition
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
0630 [U] More than one definition of '%s' (with external linkage).
3406 Object/function '%s', with external linkage, has been defined in a header file.
1509 '%1s' has external linkage and has multiple definitions.
1752 The object '%1s' with external linkage is declared but not defined within this project.
1753 The function '%1s' with external linkage is declared but not defined within this project.
-
Rule-8.7AdvisoryFunctions and objects should not be defined with external linkage if they are referenced in only one translation unit
- - - - - - - - - - - - - - - - - -
QacDescription
1504 The object '%1s' is only referenced in the translation unit where it is defined.
1505 The function '%1s' is only referenced in the translation unit where it is defined.
1531 The object '%1s' is referenced in only one translation unit - but not the one in which it is defined.
1532 The function '%1s' is only referenced in one translation unit - but not the one in which it is defined.
-
Rule-8.8RequiredThe static storage class specifier shall be used in all declarations of objects and functions that have internal linkage
- - - - - -
QacDescription
3224 This identifier has previously been declared with internal linkage but is not declared here with the static storage class specifier.
-
Rule-8.9AdvisoryAn object should be defined at block scope if its identifier only appears in a single function
- - - - - - - - - - - - - -
QacDescription
3218 File scope static, '%s', is only accessed in one function.
1514 The object '%1s' is only referenced by function '%2s', in the translation unit where it is defined
1533 The object '%1s' is only referenced by function '%2s'.
-
Rule-9.1MandatoryThe value of an object with automatic storage duration shall not be read before it has been set
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
2883 This 'goto' statement will always bypass the initialization of local variables.
2961 Definite: Using value of uninitialized automatic object '%s'.
2962 Apparent: Using value of uninitialized automatic object '%s'.
2971 Definite: Passing address of uninitialized object '%s' to a function parameter declared as a pointer to const.
2972 Apparent: Passing address of uninitialized object '%s' to a function parameter declared as a pointer to const.
-
Rule-9.2RequiredThe initializer for an aggregate or union shall be enclosed in braces
- - - - - - - - - -
QacDescription
0693 Struct initializer is missing the optional {.
0694 Array initializer is missing the optional {.
-
Rule-9.3RequiredArrays shall not be partially initialized
- - - - - -
QacDescription
0686 Array has fewer initializers than its declared size. Default initialization is applied to the remainder of the array elements.
-
Rule-9.4RequiredAn element of an object shall not be initialized more than once
- - - - - - - - - - - - - -
QacDescription
1397 Array element '%s' has already been initialized.
1398 Structure member '%s' has already been initialized.
1399 A union member has already been initialized.
-
Rule-9.5RequiredWhere designated initializers are used to initialize an array object the size of the array shall be specified explicitly
- - - - - -
QacDescription
3676 Designators are used to initialize an array of unspecified size.
-
-
-
- -This section targets to provide an overview of Guidelines Recategorization Plan. -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GuidelineDescriptionCategoryRevised Category
Dir-1.1Any implementation-defined behaviour on which the output of the program depends shall be documented and understoodRequiredRequired
Dir-2.1All source files shall compile without any compilation errorsRequiredDisapplied
Dir-3.1All code shall be traceable to documented requirementsRequiredDisapplied
Dir-4.1Run-time failures shall be minimizedRequiredRequired
Dir-4.10Precautions shall be taken in order to prevent the contents of a header file being included more then onceRequiredRequired
Dir-4.11The validity of values passed to library functions shall be checkedRequiredDisapplied
Dir-4.12Dynamic memory allocation shall not be usedRequiredDisapplied
Dir-4.13Functions which are designed to provide operations on a resource should be called in an appropriate sequenceAdvisoryDisapplied
Dir-4.14The validity of values received from external sources shall be checkedRequiredRequired
Dir-4.2All usage of assembly language should be documentedAdvisoryAdvisory
Dir-4.3Assembly language shall be encapsulated and isolatedRequiredRequired
Dir-4.4Sections of code should not be "commented out"AdvisoryDisapplied
Dir-4.5Identifiers in the same name space with overlapping visibility should be typographically unambiguousAdvisoryDisapplied
Dir-4.6typedefs that indicate size and signedness should be used in place of the basic numerical typesAdvisoryAdvisory
Dir-4.7If a function returns error information, then that error information shall be testedRequiredDisapplied
Dir-4.8If a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hiddenAdvisoryDisapplied
Dir-4.9A function should be used in preference to a function-like macro where they are interchangeableAdvisoryDisapplied
Rule-1.1The program shall contain no violations of the standard C syntax and constraints, and shall not exceed the implementation's translation limitsRequiredRequired
Rule-1.2Language extensions should not be usedAdvisoryAdvisory
Rule-1.3There shall be no occurrence of undefined or critical unspecified behaviourRequiredRequired
Rule-10.1Operands shall not be of an inappropriate essential type.RequiredRequired
Rule-10.2Expressions of essentially character type shall not be used inappropriately in addition and subtraction operationsRequiredRequired
Rule-10.3The value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category.RequiredRequired
Rule-10.4Both operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type categoryRequiredRequired
Rule-10.5The value of an expression should not be cast to an inappropriate essential typeAdvisoryAdvisory
Rule-10.6The value of a composite expression shall not be assigned to an object with wider essential typeRequiredRequired
Rule-10.7If a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential typeRequiredRequired
Rule-10.8The value of a composite expression shall not be cast to a different essential type category or a wider essential typeRequiredRequired
Rule-11.1Conversions shall not be performed between a pointer to a function and any other typeRequiredRequired
Rule-11.2Conversions shall not be performed between a pointer to an incomplete type and any other typeRequiredRequired
Rule-11.3A cast shall not be performed between a pointer to object type and a pointer to a different object typeRequiredRequired
Rule-11.4A conversion should not be performed between a pointer to object and an integer typeAdvisoryAdvisory
Rule-11.5A conversion should not be performed from pointer to void into pointer to objectAdvisoryAdvisory
Rule-11.6A cast shall not be performed between pointer to void and an arithmetic typeRequiredRequired
Rule-11.7A cast shall not be performed between pointer to object and a non-integer arithmetic typeRequiredRequired
Rule-11.8A cast shall not remove any const or volatile qualification from the type pointed to by a pointerRequiredRequired
Rule-11.9The macro NULL shall be the only permitted form of integer null pointer constantRequiredDisapplied
Rule-12.1The precedence of operators within expressions should be made explicitAdvisoryAdvisory
Rule-12.2The right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operandRequiredRequired
Rule-12.3The comma operator should not be usedAdvisoryAdvisory
Rule-12.4Evaluation of constant expressions should not lead to unsigned integer wrap-aroundAdvisoryAdvisory
Rule-12.5The sizeof operator shall not have an operand which is a function parameter declared as 'array of type'MandatoryMandatory
Rule-13.1Initializer lists shall not contain persistent side-effectsRequiredRequired
Rule-13.2The value of an expression and its persistent side-effects shall be the same under all permitted evaluation ordersRequiredRequired
Rule-13.3A full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operatorAdvisoryDisapplied
Rule-13.4The result of an assignment operator should not be usedAdvisoryAdvisory
Rule-13.5The right hand operand of a logical && or || operator shall not contain persistent side effectsRequiredRequired
Rule-13.6The operand of the sizeof operator shall not contain any expression which has potential side-effectsMandatoryMandatory
Rule-14.1A loop counter shall not have essentially floating typeRequiredRequired
Rule-14.2A for loop shall be well-formedRequiredRequired
Rule-14.3Controlling expressions shall not be invariantRequiredRequired
Rule-14.4The controlling expression of an if-statement and the controlling expression of an iteration-statement shall have essentially Boolean typeRequiredRequired
Rule-15.1The goto statement should not be usedAdvisoryAdvisory
Rule-15.2The goto statement shall jump to a label declared later in the same functionRequiredRequired
Rule-15.3Any label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statementRequiredRequired
Rule-15.4There should be no more than one break or goto statement used to terminate any iteration statementAdvisoryAdvisory
Rule-15.5A function should have a single point of exit at the endAdvisoryDisapplied
Rule-15.6The body of an iteration-statement or a selection-statement shall be a compound-statementRequiredRequired
Rule-15.7All if ... else if constructs shall be terminated with an else statementRequiredRequired
Rule-16.1All switch statements shall be well-formedRequiredRequired
Rule-16.2A switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statementRequiredRequired
Rule-16.3An unconditional break statement shall terminate every switch-clauseRequiredRequired
Rule-16.4Every switch statement shall have a default labelRequiredRequired
Rule-16.5A default label shall appear as either the first or the last switch label of a switch statementRequiredRequired
Rule-16.6Every switch statement shall have at least two switch-clausesRequiredRequired
Rule-16.7A switch-expression shall not have essentially Boolean typeRequiredRequired
Rule-17.1The features of shall not be usedRequiredRequired
Rule-17.2Functions shall not call themselves, either directly or indirectlyRequiredRequired
Rule-17.3A function shall not be declared implicitlyMandatoryMandatory
Rule-17.4All exit paths from a function with non-void return type shall have an explicit return statement with an expressionMandatoryMandatory
Rule-17.5The function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elementsAdvisoryAdvisory
Rule-17.6The declaration of an array parameter shall not contain the static keyword between the [ ]MandatoryMandatory
Rule-17.7The value returned by a function having non-void return type shall be usedRequiredDisapplied
Rule-17.8A function parameter should not be modifiedAdvisoryAdvisory
Rule-18.1A pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operandRequiredRequired
Rule-18.2Subtraction between pointers shall only be applied to pointers that address elements of the same arrayRequiredRequired
Rule-18.3The relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same objectRequiredRequired
Rule-18.4The +, -, += and -= operators should not be applied to an expression of pointer typeAdvisoryAdvisory
Rule-18.5Declarations should contain no more than two levels of pointer nestingAdvisoryAdvisory
Rule-18.6The address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to existRequiredRequired
Rule-18.7Flexible array members shall not be declaredRequiredRequired
Rule-18.8Variable-length array types shall not be usedRequiredRequired
Rule-19.1An object shall not be assigned or copied to an overlapping objectMandatoryMandatory
Rule-19.2The union keyword should not be usedAdvisoryAdvisory
Rule-2.1A project shall not contain unreachable codeRequiredRequired
Rule-2.2There shall be no dead codeRequiredRequired
Rule-2.3A project should not contain unused type declarationsAdvisoryDisapplied
Rule-2.4A project should not contain unused tag declarationsAdvisoryAdvisory
Rule-2.5A project should not contain unused macro declarationsAdvisoryDisapplied
Rule-2.6A function should not contain unused label declarationsAdvisoryAdvisory
Rule-2.7There should be no unused parameters in functionsAdvisoryAdvisory
Rule-20.1#include directives should only be preceded by preprocessor directives or commentsAdvisoryAdvisory
Rule-20.10The # and ## preprocessor operators should not be usedAdvisoryAdvisory
Rule-20.11A macro parameter immediately following a # operator shall not immediately be followed by a ## operatorRequiredRequired
Rule-20.12A macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operatorsRequiredRequired
Rule-20.13A line whose first token is # shall be a valid preprocessing directiveRequiredRequired
Rule-20.14All #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are relatedRequiredRequired
Rule-20.2The ', " or \ characters and the /* or // character sequences shall not occur in a header file nameRequiredRequired
Rule-20.3The #include directive shall be followed by either a or "filename" sequenceRequiredRequired
Rule-20.4A macro shall not be defined with the same name as a keywordRequiredRequired
Rule-20.5#undef should not be usedAdvisoryAdvisory
Rule-20.6Tokens that look like a preprocessing directive shall not occur within a macro argumentRequiredRequired
Rule-20.7Expressions resulting from the expansion of macro parameters shall be enclosed in parenthesesRequiredRequired
Rule-20.8The controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1RequiredRequired
Rule-20.9All identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluationRequiredRequired
Rule-21.1#define and #undef shall not be used on a reserved identifier or reserved macro nameRequiredRequired
Rule-21.10The Standard Library time and date functions shall not be usedRequiredRequired
Rule-21.11The standard header file shall not be usedRequiredRequired
Rule-21.12The exception handling features of should not be usedAdvisoryAdvisory
Rule-21.13Any value passed to a function in shall be representable as an unsigned char or be the value EOFMandatoryMandatory
Rule-21.14The Standard Library function memcmp shall not be used to compare null terminated stringsRequiredRequired
Rule-21.15The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible typesRequiredRequired
Rule-21.16The pointer arguments to the Standard Library function memcpy shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum typeRequiredRequired
Rule-21.17Use of the string handling functions from shall not result in accesses beyond the bounds of the objects referenced by their pointer parametersMandatoryMandatory
Rule-21.18The size_t argument passed to any function in shall have an appropriate valueMandatoryMandatory
Rule-21.19The pointers returned by the Standard Library functions lovaleconv, getenv, setlocale or strerror shall only be used as if they have pointer to const-qualified typeMandatoryMandatory
Rule-21.2A reserved identifier or macro name shall not be declaredRequiredRequired
Rule-21.20The pointer returned by the Standard Library functions asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale, or strerror shall not be used following a subsequent call to the same functionMandatoryMandatory
Rule-21.3The memory allocation and deallocation functions of shall not be usedRequiredRequired
Rule-21.4The standard header file shall not be usedRequiredRequired
Rule-21.5The standard header file shall not be usedRequiredRequired
Rule-21.6The Standard Library input/output functions shall not be usedRequiredRequired
Rule-21.7The atof, atoi, atol and atoll functions of shall not be usedRequiredRequired
Rule-21.8The library functions abort, exit and system of shall not be usedRequiredRequired
Rule-21.9The library functions bsearch and qsort of shall not be usedRequiredRequired
Rule-22.1All resources obtained dynamically by means of Standard Library functions shall be explicitly releasedRequiredRequired
Rule-22.10The value of errno shall only be tested when the last function to be called was an errno-setting-functionRequiredRequired
Rule-22.2A block of memory shall only be freed if it was allocated by means of a Standard Library functionMandatoryMandatory
Rule-22.3The same file shall not be open for read and write access at the same time on different streamsRequiredRequired
Rule-22.4There shall be no attempt to write to a stream which has been opened as read-onlyMandatoryMandatory
Rule-22.5A pointer to a FILE object shall not be dereferencedMandatoryMandatory
Rule-22.6The value of a pointer to a FILE shall not be used after the associated stream has been closedMandatoryMandatory
Rule-22.7The macro EOF shall on ly be compared with the unmodified return value from any Standard Library function capable of returning EOFRequiredRequired
Rule-22.8The value of errno shall be set to zero prior to a call to an errno-setting-functionRequiredRequired
Rule-22.9The value of errno shall be tested against zero after calling an errno-setting-functionRequiredRequired
Rule-3.1The character sequences /* and // shall not be used within a comment.RequiredRequired
Rule-3.2Line-splicing shall not be used in // comments.RequiredRequired
Rule-4.1Octal and hexadecimal escape sequences shall be terminatedRequiredRequired
Rule-4.2Trigraphs should not be usedAdvisoryAdvisory
Rule-5.1External identifiers shall be distinctRequiredRequired
Rule-5.2Identifiers declared in the same scope and name space shall be distinctRequiredRequired
Rule-5.3An identifier declared in an inner scope shall not hide an identifier declared in an outer scopeRequiredRequired
Rule-5.4Macro identifiers shall be distinctRequiredRequired
Rule-5.5Identifiers shall be distinct from macro namesRequiredRequired
Rule-5.6A typedef name shall be a unique identifierRequiredRequired
Rule-5.7A tag name shall be a unique identifierRequiredRequired
Rule-5.8Identifiers that define objects or functions with external linkage shall be uniqueRequiredRequired
Rule-5.9Identifiers that define objects or functions with internal linkage should be uniqueAdvisoryAdvisory
Rule-6.1Bit-fields shall only be declared with an appropriate typeRequiredRequired
Rule-6.2Single-bit named bit fields shall not be of a signed typeRequiredRequired
Rule-7.1Octal constants shall not be usedRequiredRequired
Rule-7.2A "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned typeRequiredRequired
Rule-7.3The lowercase character "l" shall not be used in a literal suffixRequiredRequired
Rule-7.4A string literal shall not be assigned to an object unless the object's type is "pointer to const-qualified char"RequiredRequired
Rule-8.1Types shall be explicitly specifiedRequiredRequired
Rule-8.10An inline function shall be declared with the static storage classRequiredRequired
Rule-8.11When an array with external linkage is declared, its size should be explicitly specifiedAdvisoryAdvisory
Rule-8.12Within an enumerator list, the value of an implicitly-specified enumeration constant shall be uniqueRequiredRequired
Rule-8.13A pointer should point to a const-qualified type whenever possibleAdvisoryAdvisory
Rule-8.14The restrict type qualifier shall not be usedRequiredRequired
Rule-8.2Function types shall be in prototype form with named parametersRequiredRequired
Rule-8.3All declarations of an object or function shall use the same names and type qualifiersRequiredRequired
Rule-8.4A compatible declaration shall be visible when an object or function with external linkage is definedRequiredRequired
Rule-8.5An external object or function shall be declared once in one and only one fileRequiredRequired
Rule-8.6An identifier with external linkage shall have exactly one external definitionRequiredRequired
Rule-8.7Functions and objects should not be defined with external linkage if they are referenced in only one translation unitAdvisoryDisapplied
Rule-8.8The static storage class specifier shall be used in all declarations of objects and functions that have internal linkageRequiredRequired
Rule-8.9An object should be defined at block scope if its identifier only appears in a single functionAdvisoryAdvisory
Rule-9.1The value of an object with automatic storage duration shall not be read before it has been setMandatoryMandatory
Rule-9.2The initializer for an aggregate or union shall be enclosed in bracesRequiredRequired
Rule-9.3Arrays shall not be partially initializedRequiredRequired
Rule-9.4An element of an object shall not be initialized more than onceRequiredRequired
Rule-9.5Where designated initializers are used to initialize an array object the size of the array shall be specified explicitlyRequiredRequired
-
-
- -This section targets to provide an overview of Guidelines Compliance Summary. -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GuidelineCategoryDescriptionCompliance
Dir-1.1RequiredAny implementation-defined behaviour on which the output of the program depends shall be documented and understoodCompliant

with deviations:
-
- - - - - - - - - - - - - -
QacDescription
0292 [I] Source file '%s' has comments containing one of the characters '$', '@' or '`'.
0315 [I] Implicit conversion from a pointer to object type to a pointer to void.
0380 [L] Number of macro definitions exceeds 4095 - program does not conform strictly to ISO:C99.
-
Dir-2.1RequiredAll source files shall compile without any compilation errorsDisapplied
Dir-3.1RequiredAll code shall be traceable to documented requirementsDisapplied
Dir-4.1RequiredRun-time failures shall be minimizedCompliant
Dir-4.10RequiredPrecautions shall be taken in order to prevent the contents of a header file being included more then onceCompliant
Dir-4.11RequiredThe validity of values passed to library functions shall be checkedDisapplied
Dir-4.12RequiredDynamic memory allocation shall not be usedDisapplied
Dir-4.13AdvisoryFunctions which are designed to provide operations on a resource should be called in an appropriate sequenceDisapplied
Dir-4.14RequiredThe validity of values received from external sources shall be checkedCompliant
Dir-4.2AdvisoryAll usage of assembly language should be documentedCompliant
Dir-4.3RequiredAssembly language shall be encapsulated and isolatedCompliant
Dir-4.4AdvisorySections of code should not be "commented out"Disapplied
Dir-4.5AdvisoryIdentifiers in the same name space with overlapping visibility should be typographically unambiguousDisapplied
Dir-4.6Advisorytypedefs that indicate size and signedness should be used in place of the basic numerical typesCompliant
Dir-4.7RequiredIf a function returns error information, then that error information shall be testedDisapplied
Dir-4.8AdvisoryIf a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hiddenDisapplied
Dir-4.9AdvisoryA function should be used in preference to a function-like macro where they are interchangeableDisapplied
Rule-1.1RequiredThe program shall contain no violations of the standard C syntax and constraints, and shall not exceed the implementation's translation limitsCompliant
Rule-1.2AdvisoryLanguage extensions should not be usedCompliant
Rule-1.3RequiredThere shall be no occurrence of undefined or critical unspecified behaviourCompliant
Rule-10.1RequiredOperands shall not be of an inappropriate essential type.Compliant
Rule-10.2RequiredExpressions of essentially character type shall not be used inappropriately in addition and subtraction operationsCompliant
Rule-10.3RequiredThe value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category.Compliant
Rule-10.4RequiredBoth operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type categoryCompliant
Rule-10.5AdvisoryThe value of an expression should not be cast to an inappropriate essential typeCompliant
Rule-10.6RequiredThe value of a composite expression shall not be assigned to an object with wider essential typeCompliant
Rule-10.7RequiredIf a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential typeCompliant
Rule-10.8RequiredThe value of a composite expression shall not be cast to a different essential type category or a wider essential typeCompliant
Rule-11.1RequiredConversions shall not be performed between a pointer to a function and any other typeCompliant
Rule-11.2RequiredConversions shall not be performed between a pointer to an incomplete type and any other typeCompliant
Rule-11.3RequiredA cast shall not be performed between a pointer to object type and a pointer to a different object typeCompliant
Rule-11.4AdvisoryA conversion should not be performed between a pointer to object and an integer typeCompliant

with deviations:
-
- - - - - -
QacDescription
0306 [I] Cast between a pointer to object and an integral type.
-
Rule-11.5AdvisoryA conversion should not be performed from pointer to void into pointer to objectCompliant
Rule-11.6RequiredA cast shall not be performed between pointer to void and an arithmetic typeCompliant
Rule-11.7RequiredA cast shall not be performed between pointer to object and a non-integer arithmetic typeCompliant
Rule-11.8RequiredA cast shall not remove any const or volatile qualification from the type pointed to by a pointerCompliant
Rule-11.9RequiredThe macro NULL shall be the only permitted form of integer null pointer constantDisapplied
Rule-12.1AdvisoryThe precedence of operators within expressions should be made explicitCompliant
Rule-12.2RequiredThe right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operandCompliant
Rule-12.3AdvisoryThe comma operator should not be usedCompliant
Rule-12.4AdvisoryEvaluation of constant expressions should not lead to unsigned integer wrap-aroundCompliant
Rule-12.5MandatoryThe sizeof operator shall not have an operand which is a function parameter declared as 'array of type'Compliant
Rule-13.1RequiredInitializer lists shall not contain persistent side-effectsCompliant
Rule-13.2RequiredThe value of an expression and its persistent side-effects shall be the same under all permitted evaluation ordersCompliant
Rule-13.3AdvisoryA full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operatorDisapplied
Rule-13.4AdvisoryThe result of an assignment operator should not be usedCompliant
Rule-13.5RequiredThe right hand operand of a logical && or || operator shall not contain persistent side effectsCompliant
Rule-13.6MandatoryThe operand of the sizeof operator shall not contain any expression which has potential side-effectsCompliant
Rule-14.1RequiredA loop counter shall not have essentially floating typeCompliant
Rule-14.2RequiredA for loop shall be well-formedCompliant
Rule-14.3RequiredControlling expressions shall not be invariantCompliant

with deviations:
-
- - - - - - - - - - - - - - - - - - - - - -
QacDescription
2991 The value of this 'if' controlling expression is always 'true'.
2992 The value of this 'if' controlling expression is always 'false'.
2998 The first operand of this conditional operator is always 'false'.
3493 The first operand of this conditional operator is always constant 'true'.
3494 The first operand of this conditional operator is always constant 'false'.
-
Rule-14.4RequiredThe controlling expression of an if-statement and the controlling expression of an iteration-statement shall have essentially Boolean typeCompliant
Rule-15.1AdvisoryThe goto statement should not be usedCompliant
Rule-15.2RequiredThe goto statement shall jump to a label declared later in the same functionCompliant
Rule-15.3RequiredAny label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statementCompliant
Rule-15.4AdvisoryThere should be no more than one break or goto statement used to terminate any iteration statementCompliant
Rule-15.5AdvisoryA function should have a single point of exit at the endDisapplied
Rule-15.6RequiredThe body of an iteration-statement or a selection-statement shall be a compound-statementCompliant
Rule-15.7RequiredAll if ... else if constructs shall be terminated with an else statementCompliant
Rule-16.1RequiredAll switch statements shall be well-formedCompliant
Rule-16.2RequiredA switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statementCompliant
Rule-16.3RequiredAn unconditional break statement shall terminate every switch-clauseCompliant
Rule-16.4RequiredEvery switch statement shall have a default labelCompliant
Rule-16.5RequiredA default label shall appear as either the first or the last switch label of a switch statementCompliant
Rule-16.6RequiredEvery switch statement shall have at least two switch-clausesCompliant
Rule-16.7RequiredA switch-expression shall not have essentially Boolean typeCompliant
Rule-17.1RequiredThe features of shall not be usedCompliant
Rule-17.2RequiredFunctions shall not call themselves, either directly or indirectlyCompliant
Rule-17.3MandatoryA function shall not be declared implicitlyCompliant
Rule-17.4MandatoryAll exit paths from a function with non-void return type shall have an explicit return statement with an expressionCompliant
Rule-17.5AdvisoryThe function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elementsCompliant
Rule-17.6MandatoryThe declaration of an array parameter shall not contain the static keyword between the [ ]Compliant
Rule-17.7RequiredThe value returned by a function having non-void return type shall be usedDisapplied
Rule-17.8AdvisoryA function parameter should not be modifiedCompliant
Rule-18.1RequiredA pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operandCompliant
Rule-18.2RequiredSubtraction between pointers shall only be applied to pointers that address elements of the same arrayCompliant
Rule-18.3RequiredThe relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same objectCompliant
Rule-18.4AdvisoryThe +, -, += and -= operators should not be applied to an expression of pointer typeCompliant
Rule-18.5AdvisoryDeclarations should contain no more than two levels of pointer nestingCompliant
Rule-18.6RequiredThe address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to existCompliant
Rule-18.7RequiredFlexible array members shall not be declaredCompliant
Rule-18.8RequiredVariable-length array types shall not be usedCompliant
Rule-19.1MandatoryAn object shall not be assigned or copied to an overlapping objectCompliant
Rule-19.2AdvisoryThe union keyword should not be usedCompliant
Rule-2.1RequiredA project shall not contain unreachable codeCompliant

with deviations:
-
- - - - - -
QacDescription
1503 The function '%1s' is defined but is not used within this project.
-
Rule-2.2RequiredThere shall be no dead codeCompliant

with deviations:
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2982 This assignment is redundant. The value of this object is never used before being modified.
2983 This assignment is redundant. The value of this object is never subsequently used.
2985 This operation is redundant. The value of the result is always that of the left-hand operand.
2986 This operation is redundant. The value of the result is always that of the right-hand operand.
2995 The result of this logical operation is always 'true'.
2996 The result of this logical operation is always 'false'.
3112 This statement has no side-effect - it can be removed.
-
Rule-2.3AdvisoryA project should not contain unused type declarationsDisapplied
Rule-2.4AdvisoryA project should not contain unused tag declarationsCompliant
Rule-2.5AdvisoryA project should not contain unused macro declarationsDisapplied
Rule-2.6AdvisoryA function should not contain unused label declarationsCompliant
Rule-2.7AdvisoryThere should be no unused parameters in functionsCompliant
Rule-20.1Advisory#include directives should only be preceded by preprocessor directives or commentsCompliant
Rule-20.10AdvisoryThe # and ## preprocessor operators should not be usedCompliant
Rule-20.11RequiredA macro parameter immediately following a # operator shall not immediately be followed by a ## operatorCompliant
Rule-20.12RequiredA macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operatorsCompliant
Rule-20.13RequiredA line whose first token is # shall be a valid preprocessing directiveCompliant
Rule-20.14RequiredAll #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are relatedCompliant
Rule-20.2RequiredThe ', " or \ characters and the /* or // character sequences shall not occur in a header file nameCompliant
Rule-20.3RequiredThe #include directive shall be followed by either a or "filename" sequenceCompliant
Rule-20.4RequiredA macro shall not be defined with the same name as a keywordCompliant
Rule-20.5Advisory#undef should not be usedCompliant
Rule-20.6RequiredTokens that look like a preprocessing directive shall not occur within a macro argumentCompliant
Rule-20.7RequiredExpressions resulting from the expansion of macro parameters shall be enclosed in parenthesesCompliant
Rule-20.8RequiredThe controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1Compliant
Rule-20.9RequiredAll identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluationCompliant
Rule-21.1Required#define and #undef shall not be used on a reserved identifier or reserved macro nameCompliant
Rule-21.10RequiredThe Standard Library time and date functions shall not be usedCompliant
Rule-21.11RequiredThe standard header file shall not be usedCompliant
Rule-21.12AdvisoryThe exception handling features of should not be usedCompliant
Rule-21.13MandatoryAny value passed to a function in shall be representable as an unsigned char or be the value EOFCompliant
Rule-21.14RequiredThe Standard Library function memcmp shall not be used to compare null terminated stringsCompliant
Rule-21.15RequiredThe pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible typesCompliant
Rule-21.16RequiredThe pointer arguments to the Standard Library function memcpy shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum typeCompliant
Rule-21.17MandatoryUse of the string handling functions from shall not result in accesses beyond the bounds of the objects referenced by their pointer parametersCompliant
Rule-21.18MandatoryThe size_t argument passed to any function in shall have an appropriate valueCompliant
Rule-21.19MandatoryThe pointers returned by the Standard Library functions lovaleconv, getenv, setlocale or strerror shall only be used as if they have pointer to const-qualified typeCompliant
Rule-21.2RequiredA reserved identifier or macro name shall not be declaredCompliant
Rule-21.20MandatoryThe pointer returned by the Standard Library functions asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale, or strerror shall not be used following a subsequent call to the same functionCompliant
Rule-21.3RequiredThe memory allocation and deallocation functions of shall not be usedCompliant
Rule-21.4RequiredThe standard header file shall not be usedCompliant
Rule-21.5RequiredThe standard header file shall not be usedCompliant
Rule-21.6RequiredThe Standard Library input/output functions shall not be usedCompliant
Rule-21.7RequiredThe atof, atoi, atol and atoll functions of shall not be usedCompliant
Rule-21.8RequiredThe library functions abort, exit and system of shall not be usedCompliant

with deviations:
-
- - - - - -
QacDescription
5128 Use of function: getenv.
-
Rule-21.9RequiredThe library functions bsearch and qsort of shall not be usedCompliant
Rule-22.1RequiredAll resources obtained dynamically by means of Standard Library functions shall be explicitly releasedCompliant
Rule-22.10RequiredThe value of errno shall only be tested when the last function to be called was an errno-setting-functionCompliant
Rule-22.2MandatoryA block of memory shall only be freed if it was allocated by means of a Standard Library functionCompliant
Rule-22.3RequiredThe same file shall not be open for read and write access at the same time on different streamsCompliant
Rule-22.4MandatoryThere shall be no attempt to write to a stream which has been opened as read-onlyCompliant
Rule-22.5MandatoryA pointer to a FILE object shall not be dereferencedCompliant
Rule-22.6MandatoryThe value of a pointer to a FILE shall not be used after the associated stream has been closedCompliant
Rule-22.7RequiredThe macro EOF shall on ly be compared with the unmodified return value from any Standard Library function capable of returning EOFCompliant
Rule-22.8RequiredThe value of errno shall be set to zero prior to a call to an errno-setting-functionCompliant
Rule-22.9RequiredThe value of errno shall be tested against zero after calling an errno-setting-functionCompliant
Rule-3.1RequiredThe character sequences /* and // shall not be used within a comment.Compliant
Rule-3.2RequiredLine-splicing shall not be used in // comments.Compliant
Rule-4.1RequiredOctal and hexadecimal escape sequences shall be terminatedCompliant
Rule-4.2AdvisoryTrigraphs should not be usedCompliant
Rule-5.1RequiredExternal identifiers shall be distinctCompliant
Rule-5.2RequiredIdentifiers declared in the same scope and name space shall be distinctCompliant
Rule-5.3RequiredAn identifier declared in an inner scope shall not hide an identifier declared in an outer scopeCompliant
Rule-5.4RequiredMacro identifiers shall be distinctCompliant
Rule-5.5RequiredIdentifiers shall be distinct from macro namesCompliant
Rule-5.6RequiredA typedef name shall be a unique identifierCompliant
Rule-5.7RequiredA tag name shall be a unique identifierCompliant
Rule-5.8RequiredIdentifiers that define objects or functions with external linkage shall be uniqueCompliant
Rule-5.9AdvisoryIdentifiers that define objects or functions with internal linkage should be uniqueCompliant
Rule-6.1RequiredBit-fields shall only be declared with an appropriate typeCompliant
Rule-6.2RequiredSingle-bit named bit fields shall not be of a signed typeCompliant
Rule-7.1RequiredOctal constants shall not be usedCompliant
Rule-7.2RequiredA "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned typeCompliant
Rule-7.3RequiredThe lowercase character "l" shall not be used in a literal suffixCompliant
Rule-7.4RequiredA string literal shall not be assigned to an object unless the object's type is "pointer to const-qualified char"Compliant
Rule-8.1RequiredTypes shall be explicitly specifiedCompliant
Rule-8.10RequiredAn inline function shall be declared with the static storage classCompliant
Rule-8.11AdvisoryWhen an array with external linkage is declared, its size should be explicitly specifiedCompliant
Rule-8.12RequiredWithin an enumerator list, the value of an implicitly-specified enumeration constant shall be uniqueCompliant
Rule-8.13AdvisoryA pointer should point to a const-qualified type whenever possibleCompliant
Rule-8.14RequiredThe restrict type qualifier shall not be usedCompliant
Rule-8.2RequiredFunction types shall be in prototype form with named parametersCompliant
Rule-8.3RequiredAll declarations of an object or function shall use the same names and type qualifiersCompliant
Rule-8.4RequiredA compatible declaration shall be visible when an object or function with external linkage is definedCompliant
Rule-8.5RequiredAn external object or function shall be declared once in one and only one fileCompliant
Rule-8.6RequiredAn identifier with external linkage shall have exactly one external definitionCompliant
Rule-8.7AdvisoryFunctions and objects should not be defined with external linkage if they are referenced in only one translation unitDisapplied
Rule-8.8RequiredThe static storage class specifier shall be used in all declarations of objects and functions that have internal linkageCompliant
Rule-8.9AdvisoryAn object should be defined at block scope if its identifier only appears in a single functionCompliant
Rule-9.1MandatoryThe value of an object with automatic storage duration shall not be read before it has been setCompliant
Rule-9.2RequiredThe initializer for an aggregate or union shall be enclosed in bracesCompliant
Rule-9.3RequiredArrays shall not be partially initializedCompliant
Rule-9.4RequiredAn element of an object shall not be initialized more than onceCompliant
Rule-9.5RequiredWhere designated initializers are used to initialize an array object the size of the array shall be specified explicitlyCompliant
-
-
- -This section targets to provide an overview of Deviation Permits.
-All the rules corresponding to the deviation permits are disabled inside PRQA and will not cause any violation or deviation in the Deviation records section below. -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GuidelineCategoryDescriptionRatioSub RulesCharacteristicsReason
Dir-1.1RequiredAny implementation-defined behaviour on which the output of the program depends shall be documented and understood3/34
- - - - - - - - - - - - - -
QacDescription
0292 [I] Source file '%s' has comments containing one of the characters '$', '@' or '`'.
0315 [I] Implicit conversion from a pointer to object type to a pointer to void.
0380 [L] Number of macro definitions exceeds 4095 - program does not conform strictly to ISO:C99.
-
Maintainability / Analysability0292: Invalid characters in comments: Doxygen comments are used.
-0315: Library string.h functions (memcpy, etc.) are used and trigger this implicit conversion.
-0380: Already CMSIS and STM32HAL trigger this.
-
Dir-4.9AdvisoryA function should be used in preference to a function-like macro where they are interchangeable1/1
- - - - - -
QacDescription
3453 A function could probably be used instead of this function-like macro.
-
Performance / Resource utilizationSuppressed due to code optimization and efficiency.
Rule-11.4AdvisoryA conversion should not be performed between a pointer to object and an integer type1/5
- - - - - -
QacDescription
0306 [I] Cast between a pointer to object and an integral type.
-
Maintainability / ModifiabilityUsing STM32 HAL already creates many violations. Also needed to do pointer arithmetic, calculating offsets inside a buffer.
Rule-11.9RequiredThe macro NULL shall be the only permitted form of integer null pointer constant1/2
- - - - - -
QacDescription
3004 This integral constant expression is being interpreted as a NULL pointer constant.
-
Keil stddef.h: "define NULL 0" causes violations. PRQA acknowledged this as a false positive.
Rule-13.3AdvisoryA full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operator1/1
- - - - - -
QacDescription
3440 Using the value resulting from a ++ or -- operation.
-
Maintainability / AnalysabilityRFAL uses the increment often for building buffers (array[i++] = 42; ...). Splitting this would decrease readability.
Rule-14.3RequiredControlling expressions shall not be invariant6/11
- - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
3440 Using the value resulting from a ++ or -- operation.
2991 The value of this 'if' controlling expression is always 'true'.
2992 The value of this 'if' controlling expression is always 'false'.
2998 The first operand of this conditional operator is always 'false'.
3493 The first operand of this conditional operator is always constant 'true'.
3494 The first operand of this conditional operator is always constant 'false'.
-
Portability / AdaptabilityRFAL is configurable through compile time switches. This causes some ifs to have invariant conditions at the used configuration. Suppress 14.3 for if statements.
Rule-15.5AdvisoryA function should have a single point of exit at the end1/1
- - - - - -
QacDescription
2889 This function has more than one 'return' path.
-
Maintainability / AnalysabilitySuppressed due to readability and simplicity of code logic.
Rule-17.7RequiredThe value returned by a function having non-void return type shall be used1/1
- - - - - -
QacDescription
3200 '%s' returns a value which is not being used.
-
Maintainability / AnalysabilityTreating the return codes of functions in all places without exception handling would makes the code hard to read and maintain. Error checking has been reduced to the places where needed.
Rule-2.1RequiredA project shall not contain unreachable code1/7
- - - - - -
QacDescription
1503 The function '%1s' is defined but is not used within this project.
-
Maintainability / ModularityRFAL provides many functions - some are not used within the checked project.
Rule-2.2RequiredThere shall be no dead code7/18
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QacDescription
2982 This assignment is redundant. The value of this object is never used before being modified.
2983 This assignment is redundant. The value of this object is never subsequently used.
2985 This operation is redundant. The value of the result is always that of the left-hand operand.
2986 This operation is redundant. The value of the result is always that of the right-hand operand.
2996 The result of this logical operation is always 'false'.
2997 The first operand of this conditional operator is always 'true'.
3112 This statement has no side-effect - it can be removed.
-
Usability / User error protectionAll the violations were checked and fixing the violation would deteriorate robustness: Removing checks which are unnecessary at the given position, removing trailing iterator increment, etc.
Rule-2.3AdvisoryA project should not contain unused type declarations1/1
- - - - - -
QacDescription
3205 The identifier '%s' is not used and could be removed.
-
Compatibility / InteroperabilityRFAL defines enums for all identifiers available in NFC Forum - some are unused.
Rule-2.5AdvisoryA project should not contain unused macro declarations1/1
- - - - - -
QacDescription
3214 The macro '%s' is not used and could be removed.
-
Compatibility / InteroperabilityRFAL defines macros for all identifiers of NFC Forum and RF chip register map - some are not used.
Rule-8.7AdvisoryFunctions and objects should not be defined with external linkage if they are referenced in only one translation unit4/4
- - - - - - - - - - - - - - - - - -
QacDescription
1504 The object '%1s' is only referenced in the translation unit where it is defined.
1505 The function '%1s' is only referenced in the translation unit where it is defined.
1531 The object '%1s' is referenced in only one translation unit - but not the one in which it is defined.
1532 The function '%1s' is only referenced in one translation unit - but not the one in which it is defined.
-
Maintainability / ModularityRFAL defines functions which could be called by the user but are not called in the current project.
-
-
- -This section targets to provide an overview of Deviation Records. -
-
-
- -

File: .../ST25R3916_nucleo/rfal/source/rfal_isoDep.c

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LinesCountSuppressed QacsComment
2266-22671
- - - - -
0310 Casting to different object pointer type.
-
MISRA 11.3 - Intentional safe cast to avoiding buffer duplication
421-4211
- - - - -
0750 A union type specifier has been defined.
-
MISRA 19.2 - Members of the union will not be used concurrently, only one frame at a time
797-7971
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
2519-25191
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created
2693-26931
- - - - -
0310 Casting to different object pointer type.
-
MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
1351-13511
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
1028-10281
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
2756-27561
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
2615-26151
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created
2602-26021
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created
2175-21761
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed within 4bit range
2526-25261
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created
1391-13932
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of enum rfalBitRate and above masks guarantee no invalid enum values to be created
-

File: .../ST25R3916_nucleo/rfal/source/rfal_nfc.c

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
LinesCountSuppressed QacsComment
1612-16121
- - - - -
0310 Casting to different object pointer type.
-
MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
81-811
- - - - -
0750 A union type specifier has been defined.
-
MISRA 19.2 - Members of the union will not be used concurrently, only one interface at a time
190-1901
- - - - -
2880 This code is unreachable.
-
MISRA 2.1 - Unreachable code due to configuration option being set/unset
1828-18281
- - - - -
0310 Casting to different object pointer type.
-
MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
-

File: .../ST25R3916_nucleo/rfal/source/rfal_nfcDep.c

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LinesCountSuppressed QacsComment
1901-19032
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of enum rfalBitRate and definition of rfalNfcDepBRS2DSI guarantee no invalid enum values to be created
2595-25951
- - - - -
0310 Casting to different object pointer type.
-
MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
1589-15891
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
902-9021
- - - - -
2880 This code is unreachable.
-
MISRA 2.1 - Guard code to prevent unexpected behavior
1661-16611
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
2654-26541
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
1269-12691
- - - - -
2880 This code is unreachable.
-
MISRA 2.1 - Guard code to prevent unexpected behavior
-

File: .../ST25R3916_nucleo/rfal/source/rfal_nfca.c

-
- - - - - - - - - - - - - - - -
LinesCountSuppressed QacsComment
278-2781
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
637-6381
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Guaranteed that no invalid enum values are created: see guard_eq_RFAL_NFCA_T2T, ....
-

File: .../ST25R3916_nucleo/rfal/source/rfal_nfcb.c

-
- - - - - - - - - -
LinesCountSuppressed QacsComment
391-3921
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Layout of rfalNfcbSlots and above loop guarantee that no invalid enum values are created.
-

File: .../ST25R3916_nucleo/rfal/source/st25r3916/rfal_rfst25r3916.c

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LinesCountSuppressed QacsComment
3344-33441
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
3108-31081
- - - - -
0759 An object of union type has been defined.
-
MISRA 19.2 - Allocating Union where members are of the same type, just different names. Thus no problem can occur.
227-2271
- - - - -
0750 A union type specifier has been defined.
-
MISRA 19.2 - Both members are of the same type, just different names. Thus no problem can occur.
2046-20461
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
3364-33641
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.
2179-21791
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
1867-18671
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
1851-18511
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
2447-24471
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
1972-19721
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
1837-18371
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
2341-23411
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
2254-22541
- - - - -
2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
-
MISRA 16.3 - Intentional fall through
3563-35631
- - - - -
4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
-
MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.
1494-14941
- - - - -
5209 Use of basic type '%s'.
-
MISRA 4.9 - External function (sqrt()) requires double
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileRequiredAdvisoryTotal
.../ST25R3916_nucleo/rfal/include/rfal_nfcv.h011
.../ST25R3916_nucleo/rfal/include/rfal_nfcDep.h011
.../ST25R3916_nucleo/rfal/include/rfal_isoDep.h011
.../ST25R3916_nucleo/rfal/include/rfal_nfc.h033
.../ST25R3916_nucleo/rfal/include/rfal_analogConfig.h101
.../ST25R3916_nucleo/rfal/source/rfal_nfca.c112
.../ST25R3916_nucleo/rfal/source/rfal_nfc.c314
.../ST25R3916_nucleo/rfal/source/rfal_nfcDep.c628
.../ST25R3916_nucleo/rfal/source/rfal_isoDep.c6814
.../ST25R3916_nucleo/rfal/source/st25r3916/rfal_rfst25r3916.c10515
.../ST25R3916_nucleo/rfal/source/st25r3916/rfal_analogConfigTbl.h112
.../ST25R3916_nucleo/rfal/source/rfal_nfcb.c011
Total282553
-
-
- - -There are no duplicated suppressions. - -

File: .../ST25R3916_nucleo/rfal/source/rfal_isoDep.c

-
- - - - - - - -
LineUnused QacsComment
1414
- - - - -
2880 This code is unreachable.
-
MISRA 2.1 - Unreachable code due to configuration option being set/unset above (RFAL_SUPPORT_BR_CE_A_xxx)
-
-
- -There are no continuous suppressions by file. -
-
- -Active Diagnostics refers to diagnostics that are not suppressed (note: no suppressed diagnostics have been taken into account for the calculation of information in this document). -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilesActive DiagnosticsViolated RulesViolation CountCompliance Index
.../ST25R3916_nucleo/rfal/include/rfal_analogConfig.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_chip.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_isoDep.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_nfc.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_nfcDep.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_nfca.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_nfcb.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_nfcf.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_nfcv.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_rf.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_st25tb.h000100.00
.../ST25R3916_nucleo/rfal/include/rfal_t1t.h000100.00
.../ST25R3916_nucleo/rfal/source/rfal_analogConfig.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_crc.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_crc.h000100.00
.../ST25R3916_nucleo/rfal/source/rfal_iso15693_2.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_iso15693_2.h000100.00
.../ST25R3916_nucleo/rfal/source/rfal_isoDep.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_nfc.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_nfcDep.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_nfca.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_nfcb.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_nfcf.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_nfcv.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_st25tb.c000100.00
.../ST25R3916_nucleo/rfal/source/rfal_t1t.c000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/rfal_analogConfigTbl.h000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/rfal_features.h000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/rfal_rfst25r3916.c000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25R3916_irq.h000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916.c000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916.h000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_com.c000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_com.h000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_irq.c000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_irq.h000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_led.c000100.00
.../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_led.h000100.00
Total000100.00
- -

-Nota: Calculation of Compliance Index
-The Compliance Index is the percentage of groups which have no messages in them.
-For each file it is calculated as follows:
-
-( Ntotal - Nerror ) / Ntotal x 100
-
-Ntotal is the total number of enforced rules (i.e. the number of rules that have at least one message mapped to it directly).
-Nerror is the number of rules for which messages appear in that file.
-The File Compliance Index is the mean of all the individual file compliances.
- -
-
-
-
- - diff --git a/lib/ST25RFAL002/doc/_htmresc/st_logo.png b/lib/ST25RFAL002/doc/_htmresc/st_logo.png deleted file mode 100755 index 8b80057fd3a454a97de1c9d732b7fede82c83227..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18616 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 diff --git a/lib/ST25RFAL002/doc/rfal.chm b/lib/ST25RFAL002/doc/rfal.chm deleted file mode 100755 index d417709895d27efd4723173d57f95e14a3f95515..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2338282 zcmeGFc{tSH`@oNnec#HyWheW-RkDSI$i9xj*tcvc`z|3%O14DV*FvIDq>b!Lh!ACM zAqnL-;3uu{`g+k@A_TM>(axV`#$%5&V82WIp_KGe4RGXSHpooAl8TiocOY5uf>TPVfur z-_r*Ax=4`$;)fWSMhSsnBY^zDMSe$okmcG&eDDw?LL@q;U|R~acT;8m1Z6_zMat11 zU2S!(vxqe02bqSnk0pZmhfsv`w5o}Q>N%{awoISgo$m(9h$0|lL?WE}2Io%esOw`z zjpb@Zjguj?^-c8+PH98ZrEgOsz8Rd=J*|Qew>A1i%;_Q$49{sH@?b^1P9sgeq6iWU zl(n#;Uffj;|Ez&9X?$8o^{lZzR^&#da+ns02$$Mvdmkrln3FqJRLuvXs}Ed=M7Yi> zYo6B8H$bGk(M&r@Bf@2DtmN(OVe16*aq@#}Ik~%dBTLV6pg`Bl!xQf1a}k+a4VMb3 za|V9#f`^ygpNzEo5|s_S;BZ8ts>$znYwbAPlSdWi1H+1{2s4YW!KU7qSsUhwa8_%P zuF0@ZhFKaOHYyP%cbKb(gNlc{y_18mx1R%62;>&8+RQPv{ zi`&|W+S*A;NJ~rF$jVB{$Vo`UWF#cuFd11%NgEgp?JtwH=8(|;qa08>xF@JX_bgB1 z3E&1MV<&ARZf7G7leV{&kd=~kAxUG+qhr741Gg5bg>;^A~2;3j$ z=IIKDdfCHVq3-s!FsQ7p5GoNAFD!k)DD0{l*Irdm4_8;X*M8-jGtALfb;;4nr~wjD zx_Kcfgha&N$rU|jBH~u0-kl;BvG?&4w{eBJyP$^62N@%T9wO1!6_v7aCgK$7PLiF6 zEh=q>EXtW?C(Yi&)ei2pXXMRah;%%RAwW2A&qPy+dD34YP^8;mf^FW#SAsd01ERfZ~(2G5esv@E( zKohM>M&JTZo_~3|d?*YKJb7YRh`SI52i5TQbanEvfq4N#4F8m;xVEj<7MdhnFw~z9>QXb;bfQOo1O-@aW9#Kx&K@^n-b!-9&_+dge|0mkiy+ z5fLwBtIqC|qS`qk;wM2Z`93MCu_GcYXr8F;lcHKYBJ#6&?#X>pRI^7!B{9}e?USO~ zJ|e13poDv$6x9JDqLy`w1ouf%y&xj`df-FOeNt3ch=}e!QnJ6ksQwTUz2RO=x-T!P zQ$)n%UwVeKPm1ap5iy^F?kMh)qPs_I!inbnQgk1Q?Ff42zb`Mk1uv)gHj?aT{+_{%7KD$h=LeICIJkP)z+BO# zywZIsPwx;q5pjjGM@#Z!u(JfH_eh}Vi09`E-3S;f*f1x>pX#AYab@L9hR<#?+E#qx$;&+_chk{k(k6nV8u@mM=5x^#J(6yf`4utm zN-)Out?M+`VF-yA%+nF-3P-E}&=c{>bDb(ys=bn(i%i!e&eaemguuhs6AFdGb{54@ zTd*qLxgLa#e>)wWqRsdpI!G&#^Pdh~W<@#%R%{)eP#ryNCW}C6^(H^ z{6}C1^@O6vPT1^#XIL_1*B+38Tt69NJa)_C1oeh`2tnwv@|kJ&i$UHc_E`RXc~Mu$upb`{Bf4n!<+a(l z4o5D2KNHKbTXvLR%(8FRxx^#{?v{Oz3h@8SEf%`ZKlKs*w+T-h|3ptTMNiQX=mtSiFX^{3yLmi zLOI#hZf(jxK}Je;axC4TjLV@%^^%Yi zLvKKe+R5%f=>KHKRM;`F)E-zYa^xU6Vj{8iKuvt%72}*yDJHD{Ll0Qn5QwAT_zx{R zFT`mBYRU{RnkS1|rDgx$s=($Kfj9{Fe<(V+`@p@td_7SY&hYzR0>X>jx&BY>9m~s+ z@^L%|1y348KD_RxQ<3OF!IK8fww!3)q;gcdZ4d};g52CVscg34k*^HCMeR7 zLfp}V0*KVx6TrHs3{m6e|K%K|3T$?A#WDVuq7&2$dLC8X)+aLIpM?Ialt2?$oOFnz z`!9>Gh-n;s#cD6M>nHm!gFq1&TnLUMp!+*Ka#E;*wf{Pwe|wqY5_X43hUQw4k!6 zy-Zkc3~NNB2K#>~L7`6W=mU`QtFD!@hf#{i1=p^T{44hFoapz8_GE45sxUZ&mV4P^ z?tSG>?sNAF)xw@qv+Pz9 zDck4cLI05?YILVU8Nh6lx-G7u01O8m#{_Bp6exzu2Y;Q%P@*5JVh zi3)WL2ll%T9wv&1y%)l8fRycX@UT%K=@5nkptS4YVZ3{VE`|fYT?Y-z(C~UA3Rjv+>wo#QbP9?l+Ld0hB*EV zavGFvZ^Tm02crSna@l_wp@-EU;XW5l?@lJ^qX#I4+OC(T=!=7Dm|pxbg7hr=LRjqu z%=|l2%udp@d?+fN7d@VQ#C&!`+}`KjhlL(aK(eoZZMBE5lc0waT4%R(ZP9WSW`B%} zV;Fy4;PHU-PsSYyX0h5{vsPIA$ta1Lar{tN0@t6MJEA=~e_opLU6$ev= zJsjra>jn4Tt>4|m!0;;qhcHzC48Ru)(&5{_bbCaYPM%FT$1!1wk*n)n%dZy%!rKCw zF|woNdu+_(#d)(}>ik*%?pk_N#T!Ezj}e+fIL7z%@7v}AARnOc+G zAR!jTK#Xak{T4%<`yLZ}0^JwE8PSedyrNqhVFSqbb=3A+!Yk+YI72J$_SW_if-6{i zoH==&->vT8OZl))!98+UxZSRN__}?yIvXepV)^)={jNBbG5n?$|Nl_jSNe)%Sj0Dd z%0F5DbkyM&LNCo}Fw*w~uf;45Z&$dtkG@SIjNImD{FC)h;4tFx3R)>lh0uT^Zp(T= z9X-4*p%z@v1+z(zy~oQJai0RcZt!%xh%kb@c?U#_lD=+1#2 z2uAw`S<-RQ|IXnBcYwm|?7X0EFmD&=pQFz3zB0oKUS$0LctVb8xVsNxJq&L9^o;}- zdhlUtAY#3`9f`c?V-p8E)2aL=$|Dm&^-E|`ab?pFMp^f%nOD-2pEVr8c~$` zf0E&VdOv@{a@zwrqW+ZsPw9M6XQtr;A88+&p__jvnix8JiZYn%*+k2Eur(hqCmX1( z8+so)IDy~p^}kdRvpgz-LwEh}XdN68pB<~n3j$_DhF+4sTb^7ODLYJ#^EIWYJ`c z99T&&*q{D0@=!HTg6klssAhvBPK;1(Ju-Tsg#SR@{T+Wan*Ed0o-vCK9)|7#L3o*= zl^C+8&%1(Y3~^MBeu884GjX;4f9Rm^+m7X0=CJ;U4*EcLtSR6w?VhoN-bcWEy!JHg zb|zwGYHW3_6>sRMDaq415JV}0ovqr9r>4# zJ%a^pa?fD-P)JthxM#5JRXd!W(p%pK(UJh`dJw0o71lFUvXH20BhT#NNsE=jbQ?z@=)B(B&73ec0(THh(fiULK zPl?CEB8U!%CsOAilfR6@yR`TBU?*=|55%eUo+{2ItA<4#5!vT|kM03J-gI&nxc}_} zaZdzHzI%H0*axaM*S|gdrFwvem&Io?+5Ya?FsQek9maI~t|%xU_rH|Tqwie@)*Zxd zA7k!DG!4QM+!fQ(7nw?@|58F*8qa0R(ciO5Ls@c1UIYGUHF@7T_jhb;4vfwC^r4>Orw-b16Ra_k3c5sFAz34MssGuF2H1{ym>ys2yZ^S^5^m-(wLo3(V6KYKM5{ z5!LM{It)w5h5m{yroN|_hmQwh)rdHj`*ZQ0ShKA>{`VlXhuXM$*t+b52?}?GqgQG3 z*KPBB|Bj~pfzh0QXi`l6cQpS}J)q8SR;dy$B6m@LuI-3cwTB{~9YZWPF$UuNlzook z-(k1k3A;T;*cZZhUb6iicBGn@A7&jf7hVgm)M_1M_7&m4EC| z&0?0n8^~{OTOL%sWQ~MVd-@CNFz7%ZH4epwdj>M{z5vwA2{~_|tG6_MyqIuL9DEQ* zDYnp^odssYEXSz!LHG3UzZDNE?=7;#bNj4&9bkQw^S<&v>zIlMSucks?v1yP7tGz; z7LK?jiQH8B!2Qu%qR)x&7W+LN_5#cW4n;g!f%!m1_~))O+sDx@h(1gZgCYjRzJ2PK zE`kbAbW@;JrFV`le0<#z4?Q6FSLi5z`E_BI_kSoO+QZisUDmH3Snm`5T?pv|3-Rp+ zPlX-&y1Y{e#DN21Neo5Y*Yp1?1mD_;M;y@I!3T=42TsPM5xv0yUC@=>^97bp*&J4M71gCFAIb`SKUfBoSu z)9mPtKZ*zK|BWbqkh7DkkwHoQNm7Xmi3SM25s(uQ;)~$f<0j%1W52;#f>`eC|1HRX z8RWfx_g z4|yj7v8me|jQ4}OEy|eX-LH9&{W85QZ)$mY89-261LS`hV&pisXJ8 zakYfl5Q_Yt$_B`>k30tv*(v{V9_arUSpsC#RK#WFBt?bfR3*fO#Kcq)zbT8$2#Ja+ ziK@tnosv_LQ3(<`?e6UZbGL;fYk>Ug3a9^wo+IfBS#bUl9G<{~xe4diihv z5B|dJ-~RvK{vWZp__zPxdsOgm|Ns9l`~N1aSqLd||9?7&V&`xE|JP&xdu|@>c`{+& zH3S=TJC5GD?QY!n=_8*w`1ko2fqxPB7lD5f_!ohH5%?E@e-Zc>fqxPB7lD5f_!ohH z5%_;E0=N)X#M=W&A%+llh%Lke;s)`ActPM0Z^VCah#kT)DMTBh4^cT#766ffs3BzH z5Lt*EL=qwj5kgQEA^{OY{3eF@PZg178dASg0er;2T4p9H=rMX$bORj)sA%M$?fNQycvekf! zt$-i;00UpjiRny=p+4CcsBsfK~l~%rk(A z%Yb#i0I%bN(-~`8Kt(>lbJBo}N`TlpfTCuA>yCgmet;P5lg%^M&Zvi!CQ1W#^%3pxL$&l~ggg}a@0a^J0d1O)40pv0TRCWU-ya;H2 z74Z5kK$AMawN}8X5kTdSfE$~D)Og5I27xFu0Dj>H6qf{SPysa12Q0G#Eb#(79u6pf z4Uq3Pph*>AcQc@MH(>lQV9O$)%^Kh#0)m}#Fw+2z90lBx1Dw(V95w~?at9>#2Xu}B zl)V91Pyi@i1E~8NkaZj|ZUs=15IKWDAZE;f!n}a(vVaO|fEt#7>CS+RL4bCNfChH} z$?pNqw*&Te1GbL==1l;;{|eZ$1;|Q4v{Q~;WCUTOU_>|BCvT*|*mj~3;030*` zOmP70@CNj}40!w+ph*Fs@I4e?0t)s5Hhc#3BqZJO+s+QiFAw;^46w-?@Y7Ym&=SD7 zSAdKYfLoh@HDut?IeUJOj0?hRSydDL}dL8gnF(7RNpj9WJ{X0PHZ-Bbk;N8mfw16c%fNk=C@3jF<>;SVa z0J$ysE&)Q~02k8$$qNBL-3J_R2DIx2yz?HgYyq%+8!!-;ai?7M%yi@}Fhe@&D>MVAWCPo$vF8fRTZK z?xldtZGgi=fcI05}d0hvz#)@uTing9yI0Y7;GehUM{z6v;; z19-dy@K8M<=@UTrEjr(a=z0GlF}0Bc4)blN6Sb z6qc0{6-UUqcHTjb%!-BjLH@46aK5occo3Pi^TCFYKv3_{LA)IY@h8Z5kaKEES`d81 zFUUvQ5Ws`DA>IQHAwbw<8rr5OMZNDFF{@GfqxPB7lD5f_!ohH5%?E@e-Zc>fqxPB7lD5f`2VR0 zP@v8NC@HZKCkB#I>O2sLhwsIChHgbU9Ijs}nG?LXjj;rV>Scqf&D{?}E?k&dlW;gr zCuDq~Zu=Ld!BGf9Mo}I%L^YH)4BHx#2IYR(CiAt0&StD$DI%%OfvudLWH5|igp%o_ z;7kk7^k{0T9p%sx9*N^^-(^~Jh~n6otC7Zgzc<&fJkdRy%}Si-8E}G((xh$eSSNY? zSoR@oQfo*k1d`AJp;uwZ5(s;OeNr%ti_J3Z<5-hhE;blXRTbZ>pe1J=F^Yq`xjz1^+Y(_K?mONKXAA zH9aGM6Z5$}kL8RJdExO&%OLTa&rfcpsZ`I;brtx^==2}keEspT_jcc}%|mmetEHRG z6kigRk`n_$e%CwvxEMv_snh(*)S+HPAmbEo-x1EhwCd-#3WDYfhS)c`%PTgeje?Jz z{HkRfv%Gjx=J9IN$WNaD(gvb6i?yzmke<11skc9Wtdea!3@GV5esZ<3nnku@QQ+g} zw2f}Nu5E5hV#k~}59hv|_qpe7ao+aXExNe3uPgknH`;G}%3*1W7fF0ueB$J>8~Kaz zqkb3V)j1bBSUlaWn|@T&P}EmahQG&6SH7WTBd=C3M@cr6`k+0y-`IP#;}mi2ji%3M zjpKbyt!2)>3w#t~r8b@<%-~wCAw063IfY&MMmqaaMHU}<>Lm`oSc29fO_GdE{(G*k z8q_(hiG3`Iem`!AdVVeSH1<8gT6Pm2R~$xG(=2UKO?|_>3Wd1A9=6511a)VeEYrx1 zqYV9S)?d*Z!!dB5x0uOS8|$(ih%cXEl}(B5^u4dz$8Q@Byu{*^d??|O786+iOmo)jz57?Wsv9?&9;H@^1&lY z=*LG$I{GqC_EZ1TEY56E>+Jo|7IV5InImsAjb-f)<=BLK8Iz2`a|H627yBc*Ij|w6 zH7AaIre=e zn^2l9zt$2nU0dz>xj~B#{WWL#P=y+Flx!%-&dzqOhm9J)SL2R8EE8*!*sL>*|!TYmZzi%_DOix@LE)JkDoRyZ_x~ z=G>r6cbN~#d5jBqBVMr=^n`rdIp&K~@{eng^S@g3Iq{}uq+5izpoTtkG$e#j+EeuU z+O}$LoWkgI_vKq;V~wPchbgia$39n!F6aE_v380ZF2V66epO17A^VZwa>VToQ$AfO z7;mFfH^rmJY$-;MbD^dN!P?&=@T!NVRRa=9c^;`eoA}#MiJmW=D(^;J>6b?qUwM z$p<33n%`Ge;bpPT>iTu?vu&g;1EnfH{h9=}&gWrJu|9>Xd* zFNV_^k(qp}DoQxsq|3sdhh0nTY|0_HQGl7ZJYhh|Dc>uPa#p5W%KV}@tfe!mqugIV zTCu`9V-ly>zxc9?WP&Xy$XD%l_}ehna)SGZ@zv9QD~M@L1YUb`cqP$=H3vp3TG5}kG$1mrD*WPBgGfNGUUePLd&My7Q)HgcnZ&iMGNqO^!}D8LzinkY4pNim zWOn;9(;20e^pgZm-Xm;iqoy{hwJL^=-(Y;Q&h+)ITAK4=XXfQoiR5)$hFtYWB_fJe z$oophpl5r3!@@(!2PmA6GcnMnni{r>kYqZhJ~A-ELr+WM)cw;xh$3L`=mAJ9M*Cih`m3>9M}?V`oQ9rbgrKB*`seFS8*Ya zd0(_R=229`-QyS46h_k2AR>14F0)aWj-NV3Y=D?Btj6i<^-Y-zom||CXF?qAk1$@g z6g$m)!7XM^iVl0(*@(z;t2{W5DOGT+lHD?nltv?0^P8hfl{9Qx4sR~4){ne?e z`>v)-32oJsm*@h57WFx=DR+_kj~QDs>1wA%E@hA_1rB^VXKj~ML~F>@H{@8Aw9&^V z(ch)2oe?pOP36G3$||7UdYqK`)m;9T7I)GT5lP6{u?+u!&ttx~t=Yo~s-p<#ab9t6 z6etT_aBJJPqTOQNSiY=qgPpNQ_Sp)tip!*JB(qjr*t7}W1*X->FU74Jrf|=>u0y%4 zpQ(dS#rL>49-fhQzk9GZ6bt+ko9+crURWc0bmd=JYY z9T5##j{wwme z{u17@RY5H8Z(2pKO*1xHJK3HaM$x?dNm^{PiV*~Kw0KP z+OI{jpl4P~{k#K4qHwi## z^K%?d9V2HN0*evc4u11jvl`(LH${d!-}`7PAj~6!S3hN4;IE%~U^DUTwDqi&rj50* zH(gstUL0QY8CiUd4cTw}g-`OVYrFZEKG&+<*chK2_&ug4O#YNX^d@Ka@Ke0E&d$%V za0>J7v(JoF^)0KFYK&RBCcHiDbR&|SE1nFi`Fr!~Ykuxb#Wl~^$}znsz(vx#V`bXYc!uG&=jKv*)4m)s+zR?~G-9^vN$-1uRoAKhvt<>Q zcNrhx>pV?jns8+3`fkFY(K%kUB{z1NM!STCb~T)>JU37?(kd^E*8bHw8>%0@Icec5Cf;Fmj^Qh3KgPhNJ5bpmG^K&kgO)od^=YXJEztJrB1!LZqB4xJc9o z_FH#2K`f=?1pomII?~Nb5f$6`Yuk++~?zNg{;PI zafb)v+gKw02OknQv!}^L6@RFN7+b%JunY9YQG+Ln;E%^s^do@T^?*9vN!aBbyrJCME$?zSynVZ$+U_2_9c`=P}Pf@H{25~wy zxwC3E3mHAluV!CG;uSYP&6i=z5WV*3>ye*^u;K|OB7UM+lh*S?v*#G%UkOaT?6txP z&-It09-kBaNqySUvt@V^V*l*;#S_Mhg{N1Es{*%d%~jaP7i{kH_udn@vt~kSp~;si zd6N%kpv|0&=j+ng6`r7s_U7~c-cM`~<>lEN=M(m9Bo9ATbAFPTRPGq@uk@7!ug|en zO%K9b^qjdUXFT7ZspM>VAl%*kA!F0O!0;rk#vDy`<-L1)AKz%XDA&2PF+SzjZjNWJ zs+VgJH6>CDxSATM(8zi?yo&11SMfoI(K?6aeC8vi%7QD;jd#sO4t(5jGUU{rvna7kV;=A;6`PQVG{f^a5nLrLJg^HH0I%o>HkSG|<=)IBU}N1qXkyLAdlr5S6an3s{NRagDQ zmOl3YD^jCPR3Vaw75lv`bGl#VNrQ#oC%#z*SsJ(Do1P+(PjfpmsycpReG*@_xbAdq zJUnff`t=yW^>IT&YlgT9(v9Ek)mNl_ue#j6a@Y~)5`Q%Re75`=G%h~udWEnCi+nKa zYAPi2OYFK*{oxf=dLv&xO8h(TO=%mXmyb+~m<<&wT~kXGtM0Z{d{jvw$rXvCtUkvq zfOFAHPpr;h?a4d*6U7u%LU6P6Yn74TZUmG;z704s^9EvD1pFMoYxOBms$wCzE1<76 z|MOax2JQ(OfmGV4OE-<=9d8~_Y|ESBts-B@mE-rjb(MmEMpgEMfyaC86lHt|8^*(v z>CI%PUNY($3aYsbSn>qt@;!hn8r?Bpx7|wDbZkDFLwOxn!cVQQ&?O*#vMi>j>0PnR zS(7I#hfTi)Xj%ySr}Y?cl-+-qt1v)u63<47OjaT<{w4RVAAH<{y-z*`uUvR{-n4^f zfGEGtI`mSJEY8>R*0zjaW|r35nu=A9d6nje_7pJ~FU?k?!KHrrBRjxEQ{X-IHg!7H#a?tN7 zGv@Ku?Ax_heogUg%nGZulRcxkyd^6Bt7t+g_tV`&pVI#Z<_nR@*s_npgKs1n=p` z4W_hzU8278;#2fvPwn4t^}79tncSOx8C{PvDaltkr(9(VQ}Rd&r#@Qm?ro~+Vf5YT z$diW}awI1V3m<(*WnU(T*6}M&E(hRivi<@|Jj)F*1$4 z5*3MkDLZfQi`NucOtRGC`VG^XvJdH8&^~9QNcHO}3u3V)PIxb_7Qil6bkP&evsQh$ zLrP4_FKrYwSWug(wN@EPS!Y_8|C`r8;+Keayq?X8uXj3>;DcIa1NNy@EDgB%+^bJZ z5oa=61CRMtn7_JtT95Ma`wu(fia+Y+e3`WJ*^(;FfT`$()ugo-yewHoqPF*_w2zU3 zV7Y-uG3A4@+dauZw3LOoiV+JYnw4 zeunN53(S1N&PH8NVC!?jMBPt9%@e%DZ)Hzr$;!lUTx)o*B*VFox;jRk^=0`?k0rIf zFma`5W-9IZm>jx9&3e&{`QKk3^gVC+LR|u(ic(gs{x+vGvP#7~valMXR$16w5JXg}Uo>}O4>h!GF;3RMJD7n6E zosO9lds3*E(A#=?v`y*lOJz@?;NbJvv!O=|nyHsFJ(Gi@ax-s9BzdMckB9RNiPZCC?Wj7na`oHE?V(SMf?sowwqK zNn7a8PZ0U8}xHHpPq`% zG;mHFCSjn`I33pT)Oj^5;$=xog@BCiQR|rtcLoJMr!MkHGWHH;a;3!>bu*kh$rI%6 z8bdA9>7ButH1IXY=k4S-_K+iuz87_9ZO^CC#hmV;`yIs|Sn0N=jCC}~B+HKNm!5@R zqw)Do$+O4CB($Npt5*#*E(OD0TyT%r7OV(k<_>50?PN5U{BbKxXj!4Ilmkjtdxl}g zMp`Ac><7+(fz~ATXF-Flc*R;_Dx*GwVL!cKECus3dIijLN%kdgW(9+ir*s6)7B9tw zJl~|5RshLg|`$r&924czj!JeG1DM4 z5G?DX`K?B{u2ayU_qWNAh2^hc$8-=)bWhGM^lJ?3blxVG$gGrHHh)TbA)$OcYLcw9GH{F>YKM#JVB!J=impRce?lSsLC23_;*rtqbASV=UiZB*vd ziVt)KzTCLXL2c^RAKYD$#$lCvPg3vh7V$AU#Y1v>Z7;Gda&J1{{ve|m+doYD_A?fx zr0BQvYqTeH+l~;oP8aA^^FwaPWtHFbf3R`6V`I>~(Y-^?+IaX8K??M#^Q8>x7onsK zJn^*Hwu+shwh{Ip+6c{86{&vtLKE({FD&>zdG#nZ&nq7tE%a4tPHU~gG&2V5$=b*eL7=Ig3d zdmL|aU#(ce7?jiw&-ql3Rkt1d!{)L_B{w~V1_!NgZr_vG2oEk;O_|m5LLq@k!|xG~ zPD-^duB|&?%Y3lw+Oo0DZkP%D7`9%YS&dv@ygcGuomzC&s#m_FI{OCvVhfrWybI619HKJ>&F2C~R% z_2uBz;KN3pN;J#zcC4x-;WQT&g~pDBWgLw9)_mT+n zyV$M;_V55%iOwqyN%jqqM{eai2)-9jty)`wlep{(%wYGL5jgw z^dL{ItWyA=o+{DxrN^moWyQAnPkwaepGG@-)B`(iKY#A5{T=e9HGyo*K5>@xiyr=U z0R@iG;w!R@m+M$?f1Z!5b%QZ=HyHiGj=z4z_>4d71I+uk`XyHtxV~ocBJnq-^X{J_ zUw;r`^q&q`WTgLjCVqS2N?IdRVeBV|XD`~ut5OD1EoEk_1Zr5^Q*bA({ptf2B$(MM z1a0&xf+M)wlJ5wLIV7COktpe~^d8j0<-Pxn>iUo6M0Fv`BlPw@HbHU0Ot(jC>xn8| zYzd=V7Kh@QjOqCMonM}o!=fD5?-xyrSBms#<}}W+a$}IHpojZt(lrcZo#P2yfBWI1 zlOB!O&B(Z(D&J>HdpL6 zw(Wa!O#HfvC-@A=Ibmv!&OOrYUtW{nWXQl(VQ{aWTaruU!?jz9;hnqG@p{U^_55~9 z8na$)l()dZl-!`7xJQ51#q@^9FHR3B;M`0Z*VHaj&eO2+O|5xmJH5c_d_L%$h#>8m zjc+%mnPVj0b6aJsF56KOF;UUAKRccsGj&4$ve5Nn3mYmu+v~wEcpt10em_~gWN&g; zp^>^{9mi$G`GlGDDd{Mx3_+V)R?6mh7f8bL>kO{a6PVV8VI|nnCP>SMTWu`prm!T* zR2-chg-)e5;f>!7{yd0yDe$r~UbBF==;<)h;QIsESdR+ysir*fHou+5;%8|VpZFy< zYI$x{z$@OXq%rEJ1|z3`fQ`#BW2(2F(gFhYxcIl+g}yx<4nLpd^(ZdKc$yo7Ws=@^g6#>t1nCrb2ZCOKuP9utpyKINx)asT>+r%P9+QvsW3 za0Sn+u6xkY$4qkSDo4M)8M!szMdGa&xAtqj+>4;h`&3-mG3~MM zq%LY@~cyVe)8I?$yLm?pI%$(uhAUx#3C?o%_J@*lj?R*Fu1CQ&oNaQCIfmeuJM& z$*0emF){QT=leN&%$6u`nSI$5@apHTPlpc`(_iE|UN9fmb*(pG5@*Yw{r8i@RtZ@qcm;R(R&z=V z2Y%Y+NWF79Y)ozA^NDRZWhJ{OS@~*6(Nq5?shY=vug;kz`#Hb72E|>!;z4a7cvd;8 z^7q15XI_K(Yh+jKEIwwwsaT@QsT))-VC&Rc__{7oF3YJlexj}Ev#B_}?#tm6l@6bC z?aO0aXKBsx?vI(eSY74$Nc89!D;}22I!kBQyAScx_{&7=j(2V38$IVOlIM-6)IWJx zu58pc6fU=k#478)d^foAT;LHg_B}eMESqLk`#=ZECyQsux}P56y?k~`uAiOx#++G& z?8V~CTGpl;b^}BmSLQ<RP!{n2ks*zt}p z>Rzp}G~0&#){xNA;Qzrf#OVI@_3P#C+csar`e*a~9~`rTY?f!a)}OdZo5f{sz{^o_ zf2loO*T^~V$MM4Ha8@6|mbr(gg58tyHt-p}R~y!i$`3y`6QjJmmP7m0CGJVcmHKr? zWrph8c78u9*D5OLk~+|tLk6Y@F^t3nS6~k{d z(J(or2W0--{3Kn5h28rqencrZ=&UbUlN&#Nu91vRtbf_Mg1i!64CUi-@5PYj}1@@i9P=LZM->J-nP&`AhfC&<`%UuH~_Y$!M{L;c(5 zEY;1GVuc}Ag-GRq*A#bb2s)s(d~q+X-LsOpM=iNthIKfK;imSn<*fN;*e{JYRyE7z zQHB{}-|>Fan;*`GN_v0Pt7?33rMgPvP$AAW|6)101l(&W*Di=x3$YlUmpdj_`rfk2 zM#o{QdM&)=v#9xz_6j@#;^(&lrww1P6fcR^C~V+`(SJX2>S3UUIP@WW{qgflfmhnX zYb!*QTEpM<+JC``E_4rP2u&ItXGL@K%FfIRncR5R*KC*wlizD1I z;O;|uTU7^}M|are+8gd9Y)$A5m_5Y}NuK>QcuwQfC6}jZ(Gr{Q#0VNZYDBod;89u9 z=)QMYFeMU=`F8d~s7CoIj`Shl{;7?OAMN}@N55J?d00>NrN@vzGM}NTo$X+=@e%Nl z`zAr!I-mD>uG|2->lKH;XIeJVdspF^+S(O}j);Xa0ZYuyPvUiPwZGdnw^n2eUA=xA zf2lQNY79B`vF6T(d0;CqLjsTMP*T)t(PJNnmxtj$A}iM(yI@bjrB?K8xIDbonuW52 zAmY`h>TXsN`gdGr>-TlrY_*EZm8aj;IwtrTx3&+PSY)cLO2jD%xM=0#+vKISW_>0ndU6gc9&Wvw zbZux|;B!9vJ^EN(c1P8`(L~kRm;k5PSIzx-6AF-imLu-JT18(hIl=uQ2gDPon|{u=FuA=bZ;SqLf7xfsgS8FuT5^f{C@5-VWd)H&`DlBl_xPQ zG#B1Yp1MR~Vg5j+C8tNDoRH#uKqJZTBvo6&UoKJ{KAgHAd1KapkmE>>QN@0dc6P9K z;O6#W=(X>WoIffuzAjXwbLyh8shc-q#i7MzZE~EhMLNE1n8#(7YQDA(@{P0g{)VeS zCD(hc!j{Y31Jxdd4*c3Pu7rG1?~`EG3Nc%(+hdY=FYbAMax^&R?&NDHO7z0$_pAH% zvG_|5-VxozE)Be-F-Q4X_Em4u8S$Epp3c7(`*<-Mjb-FJO-2c(#8PpU8^WK{X|xr_^^aeikN0yk>=9~o zqO8bUv$MjkrcD*Qq&0d}JfQUM+R47TJbiZKY8Lqhr_g4e>z^#oBDN70!=Wc)j2bU} zHoW;PU%y$M(Boq1gOIh%2nr6f_<>_VuX=FIb)??qe0}cYQD^|s_sKGU`@B3~?HUZ$ zqUGE~N=h8W^H`Gls)(ns+zl&B zB(Vm5t{9w@nR2h!fhBu>-I^vLy_y=8`!JKe@%=e$Gxc}dRx`~I4pkf#Nr<5$gqXA* z4=0Igd~$x0bCSb?vk6!9kUFl`Q>jVpQpyR4cQK$@KjpP+lDABa#7doAWWm@=~4Z&lEQZW)sZwZ_j>t?aRpY1jN-7T?|AK(M7AIQ__Zmc z{!?N(*fyr{=e%8c(6eUsF@^P!@y90~Hka3~om$Rm2&&I~y*4yHG_sx>;vUpg+ibP| zW9dl9jk}r2sioPszvgqM@|>*ji}oWovBEQd@rXw6VuMu_eIB8YR1yY;}&OVjK}XO z>mwO*OqY(umdxM1d7C(t6Zia3P)k^lIhEEO4pQ}C>$#7U6u)G9u3aj0xjNcBG`Rj9 z*E=-e8?JsGQCHC2a4Um*YXjHno|fEA!_(~f{X*#YMTbTwCnv}3OO=aq3E97XS^OmJ ze7$LNsT?bPvqL8hdShhrOwL5D3dG?|2n{#vN9+&J+R)zY=0}C4=SS{zQ)_z0a6=UH&H@vlsT&bizIwW197W$I)qBLts=`LE&3#h*~_gLai zWbv4IaP~pwAPne zysiB3UETP7*YOKf@20UMat(W;a`GE14RKblkkGfdSw9N=DBN@FM4N$$PtAZ~3v>aC zP2p|jLc7P-+{@wjABTG9{JmN$%e1n8Ys94> zppc3w^wg^^!`7Ib&g1ZkK3{W@^j4xdv(CP6Zy0VE+9_vx9-E0lM0i%a6Zu_{My538tW50>7& zar@~F^3VbXns=jh_J)!v-01}+OKfK!88_?u3kG&TI$zafdH7imclQ$L8j{#P(k2SM zLhRW(y^QtXD2uty+^ZLiTO<#kt8h-WHd?`~LtwK*7K8PzKrr znHdhL=QzSg{AvU)4t|GU4mXK9A54{8Da z(%RqZw<8(OBXmNgMvbJ7q>V`8*AO;oPIpaT>EHb3>7CAFqp)w&MbgEUIzeRHBwyoI zyT}Pc^FV0Nx%u3=e(AJVee|>yay+PysK`J&D)8-S4M(*BeP873zkRrIzr_tv*Kdc> zzRTn&z+|edqF=oz#4ah`FRA~-)dRRCu7e;azbReiQe=Mkrl0*?j1BNNXmS@{f32+` zuh3V!Mmx}FNOQmp@CTw(o7G9?>YS;~>+{ZbXmNqnFUc!LBnkyKO43eHsmg--`>GV@s$4ni#pFhug+4p=4z_qKaM&K=&2vy4Kr= z@R5;s>vjFN4rsFPbrDI|YaDgWSWqCjvnwd)%8cL*y)t{5UgJOOrL%TbwH7sJcGPFp z)C0jU6i+3V5`pARsk^t27#F2=!EyhH_o3&ax^B5$dJ4p%-rWslZXc7G7)1jiw&M{( z9$xR5QJz~hw&tTsn^(Mw0xGMOXvkgwFDhvy<-rm{zY>f4+?T?KxE4NE?TJ@1j{$J( zU=QJN^W6luP04MG8xCF{4Z4yK+j-jfyv*YpKwfGmzAGHrw&!ukC$7buiv{4TYesvP zc7|rMRGk?Hc6oKGDiC6>&!Eq`+;N`b?P9_M5sxV1 zXBsyr17yl@^vj6XXD&DP@M>snHBVFH#*ivR=-ev5iFJFLT=dzSJD;K&zW&jMH4_M# zk`?-m)RS>bgD(I%E1xa7MRmAHyz8MuWd{a+UFi~3T?D_^kG}Cw9x-2T?h~o)@oSG? zUkkh`sS)EEjfa&tDC+LTxaqSuH&=i4qm@v-Vdn5a?y-oXS0eeZ%9DS%*PFWsohlYt zu#HTiuaE2o&^OXC#*jM4o`$bp+N1AYjxYn@#dvV_3BED9%;MGw=vKgj^m+jX{=nsm zlx~J{KQlN0_!-=xrKM``h60%N%gyi=;`fEh5!74^A~Fe+V`Lin{dRGZ$2qWZicgIa;g4|&&ocX`nC+AFL>C2`)=v|@O`#gdH17b~#2 zLGN1ct|yL3hs>`Zm7l^n#DR%cEWJ&tQS|P+s9HHL@U8FS_JA_G81F$zRp1&7JZt!F z{=uh(!CHB)@RILXnZe_DEiSaB=*O?jiykdbRP?5jhJg3IsB;!~mPj9srEkf$m{WOh zPLTyxr}?c+T)H=YiawL_YF=B6lcz1-%i3)6CYA&n=WQuY|9xl>?2jQ(4wjBR52kJ$Qh+Y~j3_=Z ztaHNOyC*-)PXCA9!`s?N(%rAU?XkDivs?LaCG~qo?gS=voEfhQH1AwR`0k&V0w;!6 z)@O2!^=q>cJA)*5csOP@=X5w%+U}KuHEVI5~9kS!L7XAOs#FGz77uWMntqw9J$ruZBb4G-WtUileejk z?p!z8%Ibx%WZ(}N*!BG}7uYwjWTg%Wx1sOQgKRraumRLite8z>w>aabu28b%pd*yp zt;m$!L>{b;Rfgquz%(YUy*(9BGp^h{J|Pb(d1V2duHj9ksTYC7?5~(2_jIrm~ zOXg|vZ#DkAOHOG5oPAzBEzUeCs1dda;mhRE7g{>_0I8dLR?MpxNe*u=ps%(QLCN=gsql#CuZWhW~;G1;xh z>9^E#Ouc&cN)&Z-zhzq=7&=5^$*+i15y#KB-%-UvQxq01)8X}%*G-uQJ-Wk3=9VBe zGWGTdkFBGZ@n2)&FC*__^3WSNK;e|xdJm#$ADDVDg7OR19+%-fKZ5anj?!?%zeFjM^53dQPgl$)1flJ_Ao<|f8ci$`g33$Tr=2@Kfw zMLXLrL*~Vfz03{&xPRYDp+h{c_dQLw<6aKb zB(o)~dVKAR>bVs9nonI91nQiaz6uOYCK`E+kr;Ci9PW9N$pkzFTF%(xrb_cW3{j&< z7-xRDLPyPf3Kk?VC&))Di}yd_&aPo9MGVI!!QFWjN~GKQhp<#~ur$(Ay7VIXj4~H{ z`of>6W``%o@TA_3s(#y~fBZyk#PP#$*Z$G_bsT)3d$zIWEpu2IF%zaLQB8Y6-W8+xK$Il-qeKh4uj#n(_W{x2{%>g5z>w%4%3!)5;rGuW@r6x zZZ!HE?;BpDp52pj=tTvN+DN@5PxYG8#^&O?}*Oj zUH8I!r@T%ZfF34`e#i@gz%rGn>E7rq^%O`j7j#HN?vD{y7eQQGj}SfQ-!EqvNnk1Mh432NYz-IRCe3F>4(O0J zgxnt?JY=&Z*U@$W% z{*>viBh2;88~c;X9=)PItftY#l5<}muG6fc*%faw93>h;;{#l;Fp}QB z#2%YI&V7R5#|@_#El`jMd+EuXM9W(zclP|Z+TeGyp32@`JEhO+m$f}?>()|H`&VAz znxT%8t!wW;C~ax+`0NNTXsJ1Rsi4ff8<)G zKPj;P+P`CS?Qc&NWlkQt#;$dq{?%sgTW_DS@`FlV^8cwUCauiZhOaYg>&2`j|H*IR zt;Wyf`%>gInoAFPcc>LBOW?hP9L_9v?ACTR4?`3XZ9^EB6(<{-F%$dZQ*T9i2Ln9| zj@~Q#8e)=O-PGp|MA}~cHNSmfVJ31!mQTTq{j1W!Aql16 zj#%C&Lpuv&lE{jV%haqunT%pP=}g2@!ZW=8Q_U}ps@nOvdRltg84jaPUZS0E&j_Pq zTn?<-W*H6x8C;7YjxMvI-KcmTew@Q7n4+r`#X98Bcr+ZoGrMG(?q1)o9(zCqGAoX?S4-lPJlGMotzo((TA zf(%_t)}9B3)Memn2!=dal@pqAuH#ODt06ERUS4`ix~vj?DERp~?dU+3t2y~v*%`UbU#HsV zJnWLQ_BC=eG#svNF~>lKscq5;y>RvBeO#V=j-HX6K8T? zh*py=gOUzm_~>D5T4?=qe<`V3LeP0EE6>l(x~+%~?Ers<6$t5JUwuyUQXL`#q$!=O zu$jVNWrd)7)ya3z?bC1B~bDxyfj$$D{=FbjK^!_(T%n=#sdiUxFsdF5LbSl3*$vooDJKT!D%S(Gx& zJ)dP57AK4`nY0V(vJ9QR*=qNwWyC^(LvKGVKML4m9di}a2tpBnNg|(f_A+#=GNMyw zNwCFMsG%W-x2NajC7!+OVAR-BQdY9FCH3Y-HHbX_#!kJaGa1x7v&M z`Hh6g(O1dAAJr6;pNpT)DYNWWU@cWzov_)1HrR+#xcPbXJA^jI3OSrC~Gnom;o6t*SUP;);U`(emWkr;578+K$tPes-4b zM-*W1I=LjwkN_v{R0TDz&=rt-RCH^UUmwtcq`gMPn&^>d=+5bH<*PgEc)z>nS~teY zgbP;M(W8bjd`Ga26WdS(%lyl@J!*0+2C*|MQA7`UD8-}2FuogU;GB#v@-k|xFFTbZ zFPpXGctd{?8^Q`dRaHgmxAF#6!G+qWscp3b7Y;4XmNbQoM#{%j_?1fb z@BHEUw%`2sUPotBS3A$gYcL|Jk2?Z~qQ|d(l-H#i;-6KlU2%-Ba`G>6>G`?+bRbWB zlF%rypm&76SxHemk$Q2Xq7%92FS|kVe%{~eq|R#`>n)mo>D)2s2F~c(fr{KO zG~5trt^836^2X{Gf3-i|4VFBxUd95EN2T-zguGX!hJUry9%qo^BK7AUi{QqM0UFB} z0jgV&1iV7hGr(nq*cksVr+_W5)b(StEIG5^)Ue+{e5?wMLr(D~6|@~7<1PUSg>Ut_ zP@ZGWFRh>IL7G!5S<(%W7p+2IW6x@z7>k9cFK%nzD&meG$-Q;f4(Fne=pfIBCje6q zX$>CyoL;h~%J!6UaOEG>w;#3qkOl=uAcw2Nnqu4ajt(^^$P#8gB|@KEQ+B_%YIaJc zelyz8<`t>q(6J)c`x28Qtuuken+cB4pO~G^ty#N~_Z>YLDfWn!(tNP6P|NIdDj^s@ z&XG^@c;L$)#)JJF;X9QppQyIUV)FPH#~?37i(kZ|ju9f&bZ8_bi`3D0x((bI6$)HO zWDjFbZU%w1>PIpCQ^!$s@u^r3B38dQcLgglhMV|wnyc=uH_+gnhAve*sj+!!$kNRm zLVH|eU^6$ZHZ`L9waF)Jp58?-3{1V%+x z*-sb@qFsRhda-Ryph@Uf*S*ahoi0tj?jXC|e-Q}(;+!xZCxryQ-%?6DS)k}bM|xYg z+3GduU&+Dt*qys?w8P>^HWfPPcT~-8?X{8+X;EMv{O;NR?b(_`&;Dp}Nsb(o%aET- zOQm4e8M{{Q8l<%)^Dp<-%o@$yo_y`aUtxXFVPkQ8n!Ju%^Ao7G$_1lH)FvKPhvUn! zMXF-pn+Z$Ptp$ty=$(3wYCTiyTK{z!d%(nNniE?hdQ~9KkM66(S=x)}Nl(Z|T8=U0 zdQS|4lj7z5Y$D_-#cTA%)gXoqF{#(dBi)8Vft=MGigG5U=ZOY7pA>G}ueHqMnhQhuAm!n2u)YQaaP zrx_mlPAfSe5MR-c#-d!*37a#HE`)vAAJ(B}?jLkJg{K%e3}}tk1vH)X)+p!(#)}=? z?l0oDd?IPo;oMm7nc&gF=&K<#W)nz#qD*C0HTd%A(*R`-H@cq$&%cf8vP9Yk3t9=> zAIfJLGtz6)nZ=e*vp;*iVy;d6=XNWpkyWinb=zg>l!73{rL1f8co#cDnK&d)8Mx?A z>rCid9ejN9kI>}U?*MIv;$ww>kk?KX|+~PmjHaD@}5elY}b|Lv6Vi5 zSrNTljHoVn@9Vik&RyG--9=>Y+I7bg`vlsX7mR1dc^xAsJN&=44T^% zg+)g=&gPdDw=~dq)YIon{Mrn&B{)vmFE9!&$7*5`g>~>6=h7G-@8?6+&+XX!?Xj8# zF8MZNk~S7w&5V}3c(;QD-%Z`pdyQUu)==-=+iJG^g3|rJNm2DaBBwx#Uv1bUT#1+X z<7^a-ook=>C;x@;BWvtxKR9*&Ar)Snvs1&Or7CKjK^Rwu?+>L|0&L~~bq~FZ)#K7e zXF29)-QC=58718Q7qh?;b4zRm4`}MEwh54r`P~xmm@EI~lK|&=4+oEMC3t{Q!h5DZ z!iB`Kpm$&9$vusHKS5d>Dg`1izFw6THDUz|zMIlk79ZdBBfCAkvugrQ2$!iSX!Gd= z9*v}U6+*3gsF73Jq7=96QKDV$wIzhat)GBI9@DPSxNZ}5{q`PnIoHMJ_44eTQBkHp z@40BPeC~JH+3)^K0UJ8cHt9F@FO9i*_r@`~ zniE~{pdLBIK{2_+m^Ow%=WzzRR~yYL-9^``!#koJjt?~XVSt#*z?R`cZXqiFK(D0w zBlsx+F5D7%n)7-5J{onsJLOt|X%N&0#%nkDPbql{(|!_vW=?g{nePQ_3d(MBp0@xe zG2m-&53FO7k`dVho z%Z+Rq#yUTImLL|iu41!^GW=*`<3gkiHOrGhg2JaSW$%<7F^k3=e0Q00bE?=FFua&QeJ^L<~lo)XY@h#oX1+RLDVKW`NZ4+TezKp7|ZoeSLE*b-tB5l!~hO-)z zJiUbbIEr_Sx&JH2$D>*@85Fcg>HPd>w?b6H`xK)QW=1j26~c@CO4|A!pBzW*Vw!cq zc#KR9W$DJ49Pi@TN5CDLkh)tib*q0mo+gl~KPwRvg&cQ)liy_-Jj*sd3B+8nx`aC} zK&n}|Tlp$WHj#MV;y^u<<8JdN{Vem6KCm3`zk$0dtK=wji)2aoKWMnYuBS~Cp7O@& z#UD@i%X9Tu!zM<(YqgBJE+td6;+Ph66!b_OV2(9^eR2BQzq-H!q1N5=vf>DpR<;V= zv-yok+KF6qSfG!B-mxk(d(6wnoc<0N*A2m~_=cRsr@j+)!J*wI0*&rzJleT8uk@Tk zkl9spluLX6`GwzfzvjSln?;6|^Yd{{LhLeTWjcZ})joNDAzqe2i0&6>GQMRc#I3LP zLNS5ZON}&>(18+>w`6&6raM&%f{;|&`^(8DdLt^pK zf6pJ^SK2*A1S(#}%2TQ{BW#o#l~!VhE#8?0fWPRiaI?#2GNupmbBv?Y)T87S$|*g|6;d-CxXisq6$BgTeNGVH|VX)mCS* z&Tz@|%Nc^Lr+(cZwMIjQmQ4wtJR(Q@l&<;_tOJjbc8JYnekw~zba4juQ{P-xYUyN~ zyzbWiABRLd+-ygK$-ZgSY!1d2#xZQFTOQ}mW^#49#CsQpc^zCA_I;ziV^PLEc z_X`#vje*MbEH6sj6_IYM3;C2`>!H=_r+8}mju;sjjF809R*rwP1F5+wu~B-mUS(1%ML>6_LjMYJ$i84yjJ=XFDy5TjH!!!+ z&_cZvkK3>LHfVrk`PBEM@_xK9aQ%@zn73H#Ee9u!M}gTFO9+>azhV5Y`RwMv^2f?3 zz0k+owd>-`qJXCG$!cFJKzE#(QF+YqOUdeO@}8!(w2P;TUTf~H1>*l(rrk;Z>z+)4 z;=c{SU;E##68l={bhz*V=d&?DmlcNwv!7uD9EO$p=aV*1>5EN&Z(rXP?5Z4_8$*nc zx37B)QJ`X%^%I%(K!eyx?`RI)UQH}pmip^vt(MO5eZKCZVU^x}!Zva26S*m?E4@5# z3G4SEu}Ou_>TIxSh5wHtXOw7aeKORWMOZYW2~r-u1@m8?ClC+=00aO-Lj)iI0AEzD zDro|h-~UtHe$`1pq{1YC+$J!;V;2Is`&@-f$c`kgn}7d^lnDS35tz{d05w1x@PMzp zlE(!+z)1K`_zBNm*Rca-00e>w&#)*#YsUdrz=2TqU)^Q7|Cd03-Iy>4BkQ0b_FBR| z1Pa>ii1n+f9C55=YzRJ zNSUA3k9F3way#+)897;e{cnG-AOH32UlsSVrCUGZx4ZbFub0j6{ddntOWeQJ&*$#E z_s?bj(zPL@8UOFzS9|ZT{rA@JRH_V?NY!^%PB_%oBWnm+XmUN^Um2kK9@oA1WcB?^ zy8!q)-cS58_Mf_sv7L+m`ufz}-?Icf0pv-=VwlRk@>D{*yP` zAKWaD+|7ga?4$F>ZNMo4urLlYq|=_^AxtT8*bwBHM~yLa-xfc<&10x>7p7>8p$?u3(ZGyVzK^@vY}{ zrY(qAc_M|<8yjI}#e&g?|2(?)t~opTc^%n&jH1sv^x(?jxV>gTNh2U;S?X%o3NU0C z)tnFPVvpsg_xN}JZ`eKZzJI&39PS-g=pDbCI^8+AjinrdcL$}tW(7H(u&ppK7R1<- zLkWI5XO6oD?IrP(@Su|R zCjAOW0N;T8gLmv*`ws1E=PQ6)p4zFWK-_K~qFTP@;hp^(E(i!NYwkj=5}aZ$_kzDD zRW`MQ6@Fj4>g+kRRQJ^l&QZ2g447AqKPmSKXVlotO;f0=I-yoL*D!=lr2iDxgVr^Y z{8?i3m~)IF9e%Ts93FsStH6K;ih zg+^uY^EE%)drOnZY8Cb1a_Z2?YUVplSZX@uJYN5?+X)g~^5FLA6Ufg3)8fIOT`Btd&HCY#HKm<4=~T{m9%7pZpo!72;{8^hta zlt3P=R=e3rUT23F4>=aO@u#J#X%rcF0O^J^F+pT1Pj?=r%5YaV*m_o;Egm~^ye)j@BIS4PP<qejL!SQ?7dWH*F#> z*nY>EIToIh+5bu2d#pJy75JNOH>%n~WK^(`1!MfX#uWuSWTrLjc?e1G(vAaEBq+%bRjD(qKT&KYfD-J0uTr-7^GS2_QXlkJ>MBi;-wy3z=M0 ztMfOhN1@ntcJw%mN6Wh8ALao`ovd)=m2Wsd zZXR7wXl6ZCzsH)u->;TeKywxTBtSvJB@rDu4e!qGar~ZSj5dQ24VtC95X|t^19YL0 zn;uN})nSVedD{j3*gUUu4O>i-p~!w$sXoWNSzt4-soE$Kz9Pc2jNU$bEtcB-0?VvZ zzez`K(WA%*+)`&E=^jNj)>9E`jtw_b6zqP%=SFH#*$Hm8QS zBFaEAHrZtPyLvs`^~)g#d9M6k(y=?a$PAAh;Tx zMYFN{ucw@6_@5t7S=;3wP2=ORUcTb)&AJHuY$B^%E&_*^eI=J_lOyTnZg~mQfveO`@V!rlHEgs;ciz9eh}Tr# zp_Wxx>#E!75?LEW*qOt{E|&WUZ1>h*$wza&OR}F@x-A;*&$m1G?&!V4jcjV=u+`XH zP1C*&S2H?#Y7l@)kts;m1ZH4gpq=me!kLxCoP7$j777H)oYKJi8tP(_^d6#!=k0anLVg{0}-xOap1pC(_uIBbke{Mc{T>;{!;B~>j*gnr)Py!Nb* zk9@sIeAQ*@$VBBTa0@BjjR=6dz+Mb4f^O8Y>W38f|u{M4t7@iS2uFKm;<}f_wMu!1qlS-foMOfl@xta&f~ZxD)=17 zS(lGArqnaQ&Wr+J0xxH{&LBsq_}|j*uhdqzBa94;oQ&+fPK@}va*nUL`B@nrG48Hh z9--tZqP`0cO$_oTz94_iX&sgR@a72dzkVuXG<)QjdYRaf*ybH?MSF>*(B3SPwQL*Q zJt{C{d#1?v6`~?z?2f}X%$Jn$%RAGJvu5SnkkCOc&fqNNS?`)y$aq(t#m4{YCfrje zkKX2khsDGghd7yMv8*(f3xYb7KAV_l4esH_@E!dINR@GqGC60~e)cBS%^~>0a;>G+ zsVpln@pnOA5}tb!?>=wi(SANQ-bXYv8~^5O5I(%TU6BTTak7(XbE;^c!H(rDbUB)R zs%7^B}dUROU46Y*`eJpE-_8#YS-=`(hL^g#5lD#+CD0MZ(a#?@rg zSB?=C8mQi;UFUV8Ym=Zd#2?TO1EPJ_I7|Y2fV8O{uadbxhOrFLg28c*WbXsP@lS zPFnoiv8?QQK#x|6mmZKZU7D1lUyXgk=4!^zv0d&3jyD``rAlGlApsMT zXM@wW0ZJ5APC_;^PPz7l-P%+GuO>6Kc{s=goAU70h*?Ye=e|>}HLK~3+U#uWdTxVE zTGYRMl^x`$EQ+iaQthricMj9X6KGsB$0rl9H^h;?EXLjY0rIqp&3}YdY&^P>Vu$Tk z#V77zA;WP+6#F^Pnht9@gsR(YZb~;l(KM;sy2Gi4*;+&T`m}BIOtt!IbSvMTvve2y zd-0{047JkOSVJIkCct8QDbco^$jdP=|MFk%Oo1~jtG+4$4>Vn#)cmzg5-y+yfmf44 zBS_@Uk3OA`L!FaY>}?;j)gIrPl}~@|D%NCOlE>P?E!(f~KeoJ+KhAE?P3WvJN7s}y z%XBu1cp?;D`ovrBY8RK>QZ6t^TscfEbz4&l3G9v0KZsoKm9e3u`QSwdIWdb5DxV*OjXIj!nS{aCNB zT?^$RXUjPSBKfxnsr)9B_$Z?(PnQ(jTdg%ETp7ukv$G=~YwfXX#3VWT$x+IqJ&%UB zFllMNTf1Nsq|MqK!kUq;ygchGyI@6m!pW0nV@Z7Es?=6}z-L)_i}A3Gn!WF>|7Z7NJ|n z+-Bq7=}|Cb_$W6mUZ)2G@QTF3X_ZZmx)m;nWQpW5{@jKrtR^#dob zey9{XL{_nlPCJV|n(Myqsiu9iYgSV*=w5|iBr<-nZlWUipt2uuWaUCHV?Xl%&OUX_ z{xff-Oak$*#!}X#;1F2-lMVF>4yqwnbN#rZm^lyZ?SIfM?^Id#E4HSnel$yU4N>-( z{zViT$W={H!s=0%&+*1Nz9RP%HhNrp)i@^f>of@u!?!ypM77y*cbjpK>6cZYozFb8 zL>wGJmWzU|gP?CZsWlrRwG&>~$PNk({x!&mA0mX+5ZMwK;uL4Iw6AA>ORL`535FMA zXWtjGZys^0iZK0WvdwYm<|0Ib*7ND0r$o?MDnGu`2 z3ccP1eP*YIh6+rG$+@*FvLA0oYJZF-mu**Ok-KGX<$=r=Yg}J>hPs4#bhH5*xJ|;o zudpnt_hqp_uCR`#u}YLtDMCCQLNq49%D>x@VJZqow+{Qw(%pgA&WPV5=53h*dlFM8 zv{e;GX>tk3YAZlba4!Geeru2My=m(om}2$CPP6>xC1jELC-wZN$af=@p?tX-P9xrd zbFFpEIR+egoerIfYUV_RuLj_>Tce+Rr9HUTuFZz_Xg=|d?stPzubPKsxPatE&2Ox< z`u?G-&;d>#_nMbB@39{?7v+BltTk~^tz%e~){|M(7K{Eh{KsWm)b$nkvg};LnfYs( zb6|v^gq;dH(SlMhiI;itZEsf64+hEM1K^_(VQSh2v$~=JMei!GVWpp(qBp*M=NImy zIx|1zovcf7Y7#>1CDlcvq8mPYu~W~tSiLtNvC8VL!`8UKF!91tJSL{>E6Z`iT5!Rs z#ZMQvo!^QLm$b=$BU1zoi&IF|zhjS|g&n9&LqGoTWGI;Ra3is$zh&jI{MMt8gbiwr zse7=&zgKslSk}qM$ymr&)o2=(=a~0ngK!!U}t|nH;=Dc=|vFz z4LCOk7bo?BoTm*_7^JKc6RYZv+U;Q|YA?gFJBgpv773Ilrg00zSYpiPm}=BTE-EP$5($e8$Ybi|F^1UZ#5ICjr3W*5o{m zNfFF4H1(x;@Mg4Syo6i7tMAXw0o--=bxk8l&4!7Q4)=J39qD^>scvjZxD{@XO1;02 zNCrV-1Y8QNCA6it2uBT0uIz%4c>kzw{kb=Uj&U*&uz|SOaUW2;KZ#O#WVy%cF2+D) z9xwRMFPkFb)`__Y86hEfGp~AMeK?t=2CqRu7~RKHf2_lyT85LWK=WD-*5oe))74Sr zz*k0o_I2dd&|nsjN;o9oNc_yCGV@E~`kZ(&4v`@lRoVEt8GL=&PrQz*80+|))m36o zpV}rtlus3{su2CgO~WXav1)n4HBdm2QL2F`9hMkyF2#E1s**9@tslc@tf{jT%b9qU z-YY%j|R$kA)-9ARKVI)B+oIyvd2~S`pw?fd04to z&r~Uiy81k886f8&{-Fzq?KemLmI5|XP;w<*oKRFo7(>CgFuh!zBccocGKv5H%C)7i z!fh?cPcp{`U9{E$k&Pq_lSxIdc%Hj)D`B2yu104mn5&|W#3^!*UbHw5i)W#e5ZjcHY&sooQ(ua-Gctt~8}X9=M5TtYDeGwQru3KVkMNFe>l(v`m3&z`FCGG5 zbBUG!=XQvpo6O}*{!DP-+b8gc3yo&3Fw>)e9v0#c4|SUB3OJ0PZa_(Vm%{W`=mAnq7kUZADD%QEA`K*;1`3iOO;ELX)a#n9{y0>j@1cc>IQ6 z+5e^vTRxS{TN9&d!3^32$AX#%sd^#1hRJ*Ez4^5Mbab0~@>jUH0)D6BW1%jj%SSA! zJulXn%>|KE{I|eT75m0ySS4Hy^pGhh@Lrrv+M34#V8>Y^T`xV`+Rc2FLm$WOnPwm< z!eIgTsVgCXmc55wEl_T%Nk}{=w_vKS8HeZp&`qhMPu_rb_=xAs-YZ6|9+|qv_JvRI zXGbU`42*FwGzPeYgan&X4(ZLJEL#{z8BEItnX(W*k9~jVN8!zKfdbyyqYa2*?BxIO zNNxCrm$h&y(iG1BOsFl=s}8Jbg~Af+@;Tl-V)Q+V#vy5H3t`(&DCAjU99ka{=Pd47 zt75pw|HzvS%MkCT@%aVb`7zYUgiuR(PMCWiI|Uu|4*paC#dvGaRXT@IiSWzkTUWL` zWaKTJ{zDIq!y~J{*B0}0s7o4n9b=v9QG*n#I_=@CgO+ic{fjv3{ijan?A&8#W*1xt zaFTpS{P5a)Akxun#xt&RG!xKqtj-D8$C>+ER9_Qs8%d>Z!i;le`QBxg z+I(AhSFAbnQo$j=Duom38|Yjtx^Uf)y5 z7aUjE^OCpBo6piuHk(4_bqSUy=NF8tJ`83?gKR zcr`~$hGU0n5g;H$IwN)wqy9c-Za5V;2es%xAa#-p44fd$w2hlEsCNWDp=DWmXCGRC zGajXy6!TzS_2NU2s+|Uqd?t|A%QL6wXh*5-QgkA;j9< zf496t-3byPYsEt37_2Rcy2>Jmid=YQCg#XC*LRq1+>o=O8=tqO0bQ=uKNA?PylwNn zLypdVy3s~cZbhJN39DS_Pg1vrdTR==OTLUAccr$cciOm3Py9~n6Otv)OMN#JtZHJ= z+c~%lGtEn^ngH|KLO07@D^d*H0YJCbi(5zxXZO#<$-5UZ-2nbYM0!2JTSf39I|87@D8eTYd`0W*Rh`!H~-rx@4>f!8k7`vX>5SKnB zxuX!t=&gm-8H_uj0;~l^IPDgytpzE2dd{V};UV0PMO=|8N}0+L8G(otITNcUNTn^Z zT!_F@)p!|@UzdIK04S)^|Yvv(nSPdDQ@s+3fgx?&plgvAbE>!OGm z4ns70-lbQKt6IeZu=Qg>uOpBKi(Lsw3n{m=_VFgfqpedb`;=^uMgg4W5{UE|wi8G) zn&*|gE(+(_vaw-$3NUpQ34vLNU5Y_*$?OM05fHncmp>jWhxLp?G@H%_m{+}!ToGzQ zkbIMd?7BX1*vln}C+MZ=G#KLAbVR^;RoMg{fYxK?Z56`Y%>%svpJZ%y6zjNuD0aSI4lq-L14%Y&h%OWDTm zRkztn1e>wd^vIY<5^&i_r(~0uHiiQiOPfIum!`L}w$!wW_ec-ch2}6FY`5DFBEK^? zH;MCo;HQ6CTL3{ozQ3oDtP`;%SU8s7umgFedm*o`$XYFWn`HFay)K(Iqo>6~`=5V$ zPWQ250h4YkEg&De&e3H*p)9o3X(qSDXSIZ581V;pA2>cOi{U=|&i+P9=4o%;>`w zc*Ed?T!&T*BEGVBbWXMQ6%!F!qn&19&u-cYoPexIwsUs%sYU?65D)_Z1OP)*1SSAr zUR15BX#%3Y{d6eAw9LEN*&r4fd$Fq^=|F2NA-0Duqx&CCE0TN(a#W06R} zpFx-b6TZd_V$`=Ace`%4BgwRmNagYp-39FgiS1Wlm@D54{`UiNKl=5p40~tS=v()7 z{Q;xT)|sYfJ{?)tuc3bQs)t_9x7YrM`uE+L?hj<;erfi%hrXHlbM-xQ{>Aj;J~m_D zX6|~xAKSB2NvToy{@D9W`|0v0nleN>fj%@==4pOtpT34l){oB9$kEyRH`Co&``};9 zUr)7L)7b3W?CHCAUr+z;Qgf%7zxL|4b!>jMrk4rBDo4eL7N{HzbrveUw-=Ch$Ki7u zXz1W}C=vi9;*uPkCwaUA8;^x(od%B2=Qecb63Ga?l5A~>m23(=#2R%uNvdM22=S#G z9@Xeq^<`EfPJ$5kYqE2_t@u#io{D1dK)f~*JQm&Ul!dL9{GAOF_BJH~m$G2@DAi<) zOs*Uc`)+v@;$C8j1lX;O6AVFuy=8)0En7LY_DGGR^l>W3ZY8nxXpTm1Ob|<(Wmed8 zJ9vD1E)nxsPq#`xON3<^jS^fEl@!MpU5eDpn!@Bu#Zh^LqvN#_6N0YyB?hvo8RwH; ziOm%#mn@8m@@Pl5PdA&Zyb_U+;7*85(Wavalx+Sq&^jcW*+o*s;L@OtR>)e(o~0f5 za?Q0R)h%Y5DW%K(j{tfrI<&G~em}Lh#1b4#OX7ZwMLLSA6%2VmuP^6`k*hvKaV2S< zD6uk@)>4Gi8&H!LRcx zwI83|kH4_|^~m1=m$9#{Nuf^-A5_Q%cKwY$q%ji!+lAui*;>*tpY7o4C zB)(sf&|GgZp;~I)cO7@-v|aAl>EVbq#F4p#vE)5o?~{xfHMqjH zk|jp$t?l7830DJYw@Xr6ZYjZ?$%+x?^8F3bW%$k|lSBv#vf^68Gb8F0&mqar4YS4H z&hARQ202JL*mE{DWn_Gy64kwvKB^`)CtJmn^pT&n52?Yowz{oK2wIkc8YKqOG?`jS z9#Ilq!g7WaK6pE;V!w+z(sAz8B|i4H4+JD7ESFak!Dkx;WHzN*DMI7>^^2K07qy7t zZA)_svT~lTl#~7@AkTt%f{5f zM9b51u|Ki2^4I$i%iayt=T{LzvwTUsabg7r_atn84CE$3oo6fXHj3m4nKrT<;`Ipm z64_KU4swFSPm%{LC&eTZO1hU2AM)_snaSP1I$1@=lOI8hDH2f6RZW&N{=>8W#O3ez z&(W{UN#NXI6?>)$%dPH0h1}ML_|O2QM&?n8?z#=7C=Flu4kT)lrLo(?v|KyV-n-9r ziFS3~a2K+Tncd$fBloRas~+CnKWo#YKS$PQ-jMLWea!!8wZu;Q|JYT8v0pdFX?^|_ zi!F=Tj2dH{G2gzYLDlGBbnMBMsr_pZpBTSg!p*{;*7d!=V}D>j}EwvJ;=^e*m|EifbU^-Uz#spcnY@06(2 zfeYfq_7e3qHMaJ<#%kCxxAGSu6w=Q3+l~9GYuX0ypzK5ldvoax>y4)>JTwy<=7}0Kh zG$;gPY0|T&4i_agy%MNhS4{J2t>(=n3+0SF%^)bRsT_4hvbJ~7K6N<&Q5?-#*Sg)2K%U0qOC$fT z3M?$Y*qT{N@Mbq*m7ym?(CmioD+Lt0EDPas zsq)EqKr-z%yh`9ySEGk3FfJpjlcmnMbm?@cS?PJ%IeG(J^g@!>Oe;48!4HN!XKHAX zfxm(?RK7?Lx8#H-Beqgqbwyy#hD4^(RZVky9dFn)HZoGLzJ0#8tO~vZmzFg~ci7Kb z?2uOsy*m0o@h2kHL}Z5fH0ZRhwh9q>raqT$Ua#W^)^cH2h*D-tWm;ONC1<_UdxLp6 z_zX6D&VT-wRM5LZI7NWP?$na4d`-wkTqOlz;j*2(g(C`mOdSfHRT`AWw?WrztaEq= z-*@a{cVZbOE|)zBRTlUZ_iNEf_Rfd{ES-*jfp@7(WhMQNwii<9-(_3*hFl|kd!9uw zh&aCt(;o(1ihmokrBMti>pAye(fMKqF(kZsP0Gt<0O@ zUEOU-!_)rE>mfSfF7b;ngB2B>7V((O&Kb6{!~cFxZgyU7R%Sv>Ftxwr(z5T>$@~Q7 z)99!GeoXcDwjumxHT-AjXKC|Oa&&#O*VhahAe4Kzmj@*}ezMvU;HMOuB-F`XOnGAy z9iyELuM8nd14f?zIRM16B;o`Z-FrtY1xYUw|7@BMSuQ>Oyzv<CVmZxqLq7XO67*Vu%t$p~pIbUuW#I z?@>%=ypUJ8Q%m+KTjnmuoS*u|Ik}bI(n0UC>t-wdNamZk|CVAkwQnSKW>XKU$36Fh z>Gx>qW@lRdLfox)8*k(t;79|%oQAHY8@9`$ZrtPHi-OZ<=jr@ub5)hPu*MJy?CvAp z45SlvY!R7m3h4Q^#5;~JDjv5;$|!O8X!+}S!^~rEPhI0o_}9ZN0q{q7=L>a$zs6R8 zus32EQ5ZHs_XF)htseH6Z3UjiPd`1wl*icdZG>nVS;QPaF#B|3ESxZQv>S5La=|Zf zqTmwD9-(@DEd`exGT=Y8)B|2&!$~7fnoB+_3{B0C`a2z0`>!^G+aa|UUMXGED|meHzakqf5IGRR99{Y;($oH`va8{3RVTv5g!h)U_6|e_YMk>dr017YpwN zmcVnz(iDbGc$?pi%NdgftN;tFS{84LT&0z0RcfmF^SgqJDJrxMifMZHcs*D+9{lNa z($gN$c9yktcDhsD*bH^S+kjDVoG!ns0b_Z)^FDg2aVbQ0f>~?7qer2gH@b<-G$PMa zw7c@Ikvqv%1HuJ$Gp@w$8XT#f4r@RxM}ayfX1n%&Xq-yU?lswg+dt zx`t}x(Vw>^(v9sCmKL3BNsr2M7st%|Zm~CQZiYw_w^)7AfXA_-m3-hAK8f+l&%K>* z=X*Uw-&80CqpwQC5`t` z(|6>{lo)L3<#?(%#SO{`ZgYK_rR@>el2V5&E6wrmq1Cr04lBh6b0q*XUxyMo^bFU_ zYkXb;oc0Y7P2ZI{UESozK)kE{`y3O3YtbReIg6S2i+*d_aLpr0QbqMmvG;Ov4llN+ z4XzOXB=C-}d*|CBx7hr$fZ@v%jKmwfi=9h26pZ;Ae-N1Z1_7=Vx+POK>WCA-K1nMDsqmYt$eJ%*A>YU z9h17{OXDSo6IkRlDy3a|OL~PPJiU(ZaQ9(a@~-W~k5(m!3Em5#fL0e$hy=4-h-R*~ z2%pWS-eVxf^3JLM+VK-h?rN(^fl0aPl7RZo%=Bfu1kz28l5O(~4ezV!Ndi65Z5en$ zxCp{<$6Z<3ME*%6z(wQy+&u7xmCIKU44}+N$;%7-Qi(vc~d=`WD2+o72j}xcDCU9 zbYJMdy!*bg7ZUviX5uZ5`$XbSHS@hsX0Z|LL$S(c@xy+}Zf{7u5or47ywJF?^H&uH z(5E%Dp^^x_uFwy)`~?h_vFpBQ!Gl)IGL`&R_nqocth&_1g7}=-?=jWi-TM7ZZaG#pF6C#hj+^SJcgR5=%-qDHiKn5K zRwqlHJXR>?y+@qdbc)d?F#KICb5xE z$Fbh`ybl^nnT_$vHKSw+%k4FLu(;?e^M{P_(H9;8U+RV=x3X*AN!}~p74&}<*$m5W zRn_8nuKBd`d#mDEX~YBEy{X8#gRh1En^hZ=MKl)Qq#{U{d)t$gcowhfF&Iv>5q{*5f`bS$k=n z-zA0T+wm4+j6c6*PS7hiuMxs&Il!>?)+syyZ;}M($xmQr?d|%w^VH`ESTRjfdw=qp z44%hBWilCC;c6af4L5))d|;%t&A3sT$1l(bt{L1)EtA;ELEn zkaGtfDK%G=rTl45z@b042`$XV(Jgj0`%BUgMak z&YCAdu{s?8q-ak;V*rQLr}=)kmi%gtRpT0B31^}z5^-&E--Ap|Mi9mnB8N}Cp9B{y z4RR5s0++=MF&zi$>#RgsJaZgtpUT5HF%kinTg^1^ip;o^G}i{E3{r4VbU+wIWHkzZ z@!k{osVC{unSJ{!cRs}vaUh-p7^7K4q+DX0hGLM}V6$zR&T5CQjSmg3fJ->+hZItP z){2iUnE@&=oeISB>Hqr^_w>MUG>^ z0TVKcHON_kP=9vJUGq&0x~yk^`PBW5J2H_K7a;;!>@|+Z83<|TbDBb^DNJU=j1+XP z4G%S+U&|P(+yB?C(&jHHP(C~}twL-dF++%+sHRcMPQczF@FLWEkTKb zjroRDCN$$vyzFX{$@z$Hb6)VhxW^sCj$gvmNezHd zP(pkyE2$g4w*IcB{H{hUsmEF*1<*DIXpo!=iBW^@t6cepG5qUi=xjFw5=Ldn9IP7m zG7aTaX+h?OdZpnSek=U?aQ%z>=Vi=THMosoZ=dEU5TIJsL`s#$Wh};fAsTm&X!Y1q13~vlWIbp?1F8Hb_U&XSiCJ^Xz?89L7~k@{ zo6G|Qgv{?5Q{hF{s6l$&>9N;)3dy!htTi-`1eR0^q6jIiC;;Us6{YqLQDjO)G#m-`n%s1gBsL8acT!e2D%wWLUeFIA%!O}*&(**zDyvmu-MK(L>LAn zjMcW3Lm1#gQ_6)LKHL^pqrt7Kk+FRs_kA_gC`RNVnjv8_fzSqHGth-{FBw?i{JfjX zGC43{JQ{+WCZJ>saqrFP(||HfR=1RN*IY)*KDaOm`(X;hL}T?9ZkRExR#V<qQKe!K@HBXYbKfGr7l!!aa|3*l5jabs%mi6l0N!cE$=hjMFa{m2EnmEcmMD`AXi5w!A?%60e)!g2bC}Ow-??ByYWAO=c_J0ah7f`^IEym=+ zk+OZ&h$$J-8^r{M5UHj_0G;=NG=EF(kt#69Mc_lY9T#GJcu-LRr?!_J2^3}c*PY{f z@A(Y%RQ)1`lN<@PKZLtU+t@{FElFI>0qjdpi9m$`%q?M3E==DvXRYDqJOwvfBR%?# z74%9_vKvUP`5s5@+a)h#?smbYApn zOLOP{I&y9wtji=52xc!GiV_?of@-9ETkuvG};awIze3cT~$EqNH^Y zrvK;L4oTbMHF-dvh$tK;Fv_Kp$cP*RNJtdKJHe04&!gl(Hg}d00fTZJ{*hSxZNfzg zhis9k@=*uh3chP@Od|>@ca^vtND*bNLe6H+2X4IVd8$qwZ0T!d7Rdj<*xFWz5n)Zl zM`#Kx!_735;tBt4)Ru4!^mQ_il4h#0W-Z)SV5_!qY`Ni76` zoG>A@Bx-aVP@p63#@i2hvdZ9F+?qGFO$`)+B*QAlhg#(cEeH>XT;1=;(b&B;i&OC) z79_QyVTcV}s!MV}ZI+~!Z}z_Lq%vfXHFhg@s>lLSp;XHg#4WYUqlfU=dl!2E=BU? z!S}m05DVU^9XCP9CR*ebIS_>}bv%!@kIvxGx1zU8mfWKe4=jWR$}d*w4do!ian$O) zzT|c0{&n)^#=PJZJ_x}3CqdY9Ao1gDg__f*ey&s|ZP((8P*=##T2%vz6z+5D{n@He zs%xGx>)8H$nb+C>7kA5rtcBWu0IHWSj zH&{*;ibmERz1Fddj*n?-zgE7{2Hqeqb))7x8j9G@Mm4;bMq4H1re7m>ruIQKzo3XaxDLpFuX(UoWK8eW6ZD$D4= z2x1L$8;(Vi9ZjjCPX#)6FJ*2cebhdkb4-%FoYCCa_pmRa)PYUNYBocZ4Yq2sf9|;N z(ZTQ-K?5U=4~z^^iP=!%69ThQ*quhnewW}+gK<&Jl~lyMm?1fJ5(7of%dx;ihh@Jh z_)pu3C4dy7lNe+y(h=0*dQI_$$e(<+9_YU3d+~U1|KMsQB(QV{NmDsY#%e%il>>(+ zc;C3)bul%Q#wUX*>m{g|5S1H3srGliT2j})XM^!dcKh{zxb7^vfVIvj9mMzw8Jj~W zPZP+2p{BRAl(VPT4&J7&rB1@0Er!s2rZKaaByzBrUV;SJ-w-uwJUuU?_Pc1$SpHxT z=x@FMWPg8Dyv3XT>TzF(*wZ5Q@kJ5uqL37G9Aj(3`AXx8Sb3o%=OKGXd+dzII9x;= zmP-NnRUXO+L}?kIM%p|nF3I&@i?PGWA}>*DBm`6IvOEQ?n?ug!+CcK+wcv;9Ps`b# zCw}~~x^{8@mm__qU*;zV5sI8}_*{z^0DugZ(_1)8M%cx$N>cMv-k>yeVzd=a8|5@u z^pEY)(^yaf*j_TuA*@PdgLt7?_r4i>0nunIt~t>Z!0n9b5mUf^K-xj%Bb8dZof+8< zTou881_Y`>)jM{$OUkvj37Jjid|%=YR>URzQ_xBc5-b^2JC6EZZG{(6I!4EBxGxUZ zbNzb63P_$JwWd&qCESU)RAMYt&Igc-+M?rKDqtp-Rg@Mh40NYloIQ6C z3>h=p<0)90O7KsUPCFz-*c6U)F(;3u(9y49eG)5WFxr+oD?o-+YA79~LFurXOy>Jn zN*f&J^#sI0nkY*MR?38!kP-|?C;Y!LQOBl>hd?ngt3--2OkzDv0awQ3MwA|Mj^07H zKV!9lEwSDhd)fvI$iW6`KOcJlEcCn91S#AQ)6eS9lVA!2q5~LWC_}bm?JYpNoqIcF z_*2gs@h_G59;dC)bvBWWgaHI8Oym+btX{k*!SVS{9MGJMn9QrxXiCC4PFWcYL{Nl( z4zot7jd6y|$!E;aN{um$9p_?Z22%ox!2%Hk$FcMY$XvYI7@e?!$tbM^LPMg&Ofj_n ziPb=EzBrglV*5dRB8e~(rXa!W z9d)-%Yb?_Fyu+-%Q$bL*T~Y@|%zy)vQbz~wd)$|)sT@+HA-sUdKdO4pVhA-9kBKHc zeixYN-c;5@E}d+KRcb(JZ5iA{$f{fppe{=bkkXnPU~hE#f-7rmR2tT1dSDJ|jk*BD zSy}U0Zcr{mIhvP#CjiO3L2?GJr4jH;$?HKDC#|CR@N-VUxJ5t#WfDf#0;83n&W?{WgxFKlZ^EpqbJ=6yZ;dtFYVPg9 zU~&12_WFtm`9R!Lv8v(1j%tAfl9a;1EEN*x(%@w`_O7hqL5kIGj~3L`M{ozH*!{`3 zwn-k_6*VDwG@$1c!h{5&4Bn(R)0S>QpJ0W|)NiJ@j^zMWGoY_PKHSj&?L34~y5b`U zo?Lua{gxcE9;{H#jb;9Dy_w?kM`{l#XuQEfb`}kZ*m14oU~QNY{76~HQAcmlN1Y_W zg8nBvnnSertE5gz_jD)@x>d**Ep2P*~tz8AL9DL8nM%OBgdC(RDuf z{Bn{VA3Ue1KkBtd?AU8-MeO;;p+uMtM>ri&f~Ub$!~j90dPUqkiV2q;>2Hpr%4Vh2 zCJIE!s8FXc#LBUjNl-3E-DE$!7{AjrNtF;X5mJH6#oW*wN+CFrH)1j(Q7s_s-QT+O62ic zlDfq^0d(E?|J{(exJ{AMW#xa@%D(^z(G1NH0GL1l7(0msBp{ve@ESZ9ZzWy80$zmg zJp0fjO+XJoV71@|CxjQ_;5EBGyP0SIpMXeBF(HQo4luDUniNBjKSS_n9j)ZH7-9BBgr01N;C$jn&TQ~>}sTeSpiY0?`oq-l!eA6Rp0eg2Hi8%)?3 z9TsV9^|I?zu`PfY7JHSF+DJcYKM7AAqJOo&GxaRHgZ161wCtZd)KeMu?%0@w`eUu1 zzV%nH`k|TmKYIPGQ;&~f(yiOhQ`y%eN}e!=KZtn&bX=1sM;_0 z+Eda$J;n5PwS)Zo(#Lkw{ofPzS5w`&M0FG-wY2FpJ3N_msjKiy_8)zH&tCOoWEdJG z3$kj%v{3CqTp^WJQ!aoUzd2ad{!!E%t%n??strb0y^(3^>P#$W*KP@xYi%UfR7-DW zkyXsVrpZ`5Tq_jE?yCe~Qh*|*x_qZ0tq7~1>DR|>{|s3GgVL1IhYlHbeYyb8$Lhn` z7knQ|KZ7wBytu7wyDHxJc4-%(nFj;+P58n9)G3Lk{bLB!9mpu_#G0dTljy#EpXN_v ztk&2?2OY)}*xFS-lf*p8F+E7G)2ooS#dC0QImz3VcYmma?->t&OZ~R~@nS)|*m9I6 zU}r1%h|I7SnrFF2fh=2Fo9l-X{f2#9tP&%KbC!1a#Dgyh5sq64hImcToqEd^CBsUG zyi?i%mC9qk{;l0#1onUIKAe9cUxX*|@^I3$Q2hH-#u3(}mmX`fMzcAZM2gxgv6iz8 zVPm(Qj)T8{?1pFm*Dsf5n`(^K%N2xEPJyj!4nSE#X(T{W%aaD@lE+SZv*)W~BpA>% z#Fj@-iaRS|O-5uxQyg(5wojw@ORjfUi(N1?Ogiq`Ak6}YXev`vm@w+SP*cl5$rVIq zk1n6H+3f#O*;nGFXK_6IH^0%<0*)wn>DsNK(%ur7TBy;}<}C}7h2NZNNs zUN|U_bWBf01%?nI(G&DtWy}3Z$;jND%AZ@~J@U^I!AKY{h#zeyPd2FpsdpPog9KL< zV-U-Ut)t7ACA{LcF@_6CNd={yL(`gj44R$wZDsF1$#eaQf+XX|oN!FPM-WNNCY?%_ zg$w~*H))etl=eqk<)Pvk23jlE0*EHa1!(3!kiVg6FxWaiwcfCF=-8GC!wphZCRx^8 zW!6`&V-jY`d5l`6Zl2%O=*AWTOC*5aTIr^FX>hZl^Tr2LMsIdRzdh=SMO!5T0yOMq zAXF`bWn{onx&!edaMlxHfj2Xr?veBWZb_^>*@* z6;JGH8|Rr2601I;Ii|~#+HstXN6`;(Sl=jBfXvD-|Oq6K; zCst0?aUre~q%$Fii^hnPuhvnvGqXmQMawL)oBy?bCj~S8kR{ zW^RvGKVOzIhjy1%Ge^6vfc3leeVTLG(%Pw|PfK?71Zn=deLOB^V)C0xWGkq-)xMT4QfGT5G|_NdRkg;py)U`otwFZ@P%O&e665En zJHOJQ4DMhA(2M z0I0o)928FV%MiKE&_<#Ly!>1*v`j*v>L?p+sjMej=m%=4icGKY{T6gGDRho~U4-$X zaDZ8U9>f;Tj&xm0nlA5?-0CQU_jv;+7aKF3v#wk~9Tb+z*{ z+cz_;XO@R^whf!PwrX*ie6Ok09?FCumX8fR=4V8S9fZ8y3tr4|1Qq94?)!EIaXF>R>Cs45L^4XK^FepqSk87DtVFZW>!j$3oSMqu4BLxsrI4_3eB}LSBf|tx>^>6 zXIY01o-20zdR-e)?}C(nx9){Y3@!OWiBX`U(Iubagv#L3m$S=kllwW!WMqY-fOZht zuTlKCq14DM>F`!4m&;mj?oXNU##J~B>vyN^BB;yf3RX%^T=rQ(;v5<#=k;uZ*Kd`JXF4pw1hfN#)!-K*IHo$ydN>os&t66gevY+tkA3}p-b}=; zS7$N3&wGP;tJSyXXvGOGQ*pcWv@ECGH|KdZxyk2*G9 zQ?(_A%wf}ETTM#?Y8y3(gleI07ool)w18vGw&zz~T*%%DG=FTcWbom%_W*GmzJJKY zqWE*3P7kWMD0sv#cLO`D37lbULyKqZ?`Ca?(4j`xA6S8SdySq<|EYVP)?iwG zpL9l>baYlaoz(vv(Ak#niY?xmQ6TMnEh^%~p`)vUpg0$?=FWiHXJa3;5nGmPtJ zEMT~-7G#CU)bac=aBv2k^N_k$<72BgQKQP#`^&*Gm1szcKN0&6o{6KN)}=U$+$vLG zrJ6e0!xwOD!BLA>r_9mekbWGw@863+*AYi9ne(I1Mcu$DP`<@Pi?SvbHc+!${dn#i z#i8x^x6$ZOZ_z4KW8?UU1t|lJtgkdvoM^5Tuey)JCNa`-x_n!mrelBjFGrompe8AH zNYimc#%qK$-CtL%eaS81#!&`KE%a=GqhH_sYU}x#eJWU|wlpe!0`as9K8;_tg+q8J zmLUBW)70kTzVUALs7hes^HI`O9 zvd29E^uVt7l2ffleAV&2gIn|hy=KhLa=R}c=69#gE7gB1$G58eUCMqT*u!qzpmLaf z$}IuZ9e;V@_MRj7(DEY!b{#rmy88~ zm3Wvetc>O-=c>R9Q!#wkn;PB!;483ahDSFrJlK1xbRr~Qw!OPVaNr4N=lro}D*Yw$ zNHbfQpOwdmGuD}6nOZXLQWeYUx%+~0EJ_sJd7~D@*X4$^XUE?mQ@@no2+9HR^C~i( zB_nJdiN4u7dD%>Ht!1L+&R7l~hV}WEzj0hCHq)%hLmrEE8YBFek`}IAJ2!fp_J!u1 z!rx0{4I0Emi?KHr+sa36i*_16=$LJ0-;A_xG(JjDs*HLNb+66;tw6Sfr`J_&O`Gw@ zS2y^(Ftkg6DVo>2bm1mTdk5^sNIZ^`;0+qa3iLnKYjpf$99qa?*=vB(+?K;&^1hZr zDsC~x6T?RY7>br@)hMsW;np#8)td=$Vu&06XT^EuHE%J(vfXoT*wuIbyKu{xZf@rE zkG2pb{KT6Lu{fqWl6<#(S>9Fh#D1@n zp_%N?ujN-6V+p$|VC`=Em1s_0tt%D&FZawW&g(kLl5r$bN8aNOG^o8B{yxg^^rms7 zZy{+=zh%Bj1b^Xq2r_MFzhRt- z%GoXlLF_F&e(d%4pwB&-b|yG(?Eq z4=Jd_$RO-xf&*Njt%{!Ll8;dvXYP$&L6~kw6vX5Fb{*9|iT{ll+p_wCv7##Pn3z|U zY`_PYzz<>|-nF8Pblq|+$5HEs@(*<4e*hr>WSfK8@2|yU7NzN11pZ`Cbw4{w|sV1&hD=uwBc|H^=iY3Ml63JiksYTDj*O zbpm*=%Z?MrY1^C$#tgcd96NG8024~_&(%Aeah>QPnf+@sTY#6E`AYsf`Gb3I$i>t? z_=SBZ?mia6;mQ^p?x^s0L+lUStPrD30Qu2te&XklME*io-R3Qf#KjgH%%1&3m;1lI zW{9CY_!j$X`>vPYnD66UXAnqS$%ia#d;5Xs-n`w^V0^48aYI8L_ZY!*xx^pExXu=l zpzW!o5S;KNfc4}*j0pyqd&dIl?!|L%9RtGHT->ooife-|(wUF#*iWS0@$lvcE%vd? zkQ>*+QxxJln?!=P_RHon$JHmz6qQ0l1iKSF_W3u(2|jfW;>O!up+UF3F^b%I&24L! zYqN_RhAmYO?m9*B*V&01+pOe?^{Z#=DTu_yKoMYmm%Y=73^(*jw`=dVYdFM>ZuW5m zJGmF<6i??DwvOcBF|=Y~r5Z<7H0N%38pd(sn?*fgQu3Ztrc*Lz!M=e`1|@ zoX-x>hv@L{Em2b>NOJ$HrbWKpm_cRnxqeqMG1K>wD|>^j`R&fnFL?j}T|}N2)Th7> zRyzVET?ezti^Gn&Fh|tM9w!y@(N7U=%yV~o{43>z175J4AMtY6(kgYn6#cgDZR(qv z1I)Y7$6Ov1udKK8TtZ=4{6b^+W$3;!N`X{Q8JV0ULH~ljKQF`e=~o^__T?P4UK(h? zvue9n9;w)i`_^v33M#e1K#%~59(=NqMx9(jGn^lLw|<^Ych2Z*#0E!7yD{F;V05yM z-U5Q7&8m=S!KPr`7q{Q!HOT=?5C}YyQXc0NloPIGVgm{Uo9DJNni+sUt2AGF2;M-H zu7X$)`k)n;QR%w*XB3>QQ%CPC#XG&2zgqOK`>;%WkSPnboFQ#N6*#L9$eo#NBFD#5 zJHE1qXTpGM1SU5A{fg|BIYEql$;M_7e?;eRX68L_NJjTE<B7Li>>JrV!%sk zT1|%na5982(^b(YZ^;-8SfUS_gn+D9L6r!?ltaOF9)CnHxWIdj+do5FQ2uHdbBAGs z&majBRNgfll_W_-lSF5s-D=qbu>Nkn^>H#}c(4#zfdX0Z7J@X^VMQX|_9&#czStp# zoW~Q5_(2A;n5)R;e5H1?+(GopSGP&tZ-jD; zA6MygR(qJ{UV~lL07y}RYbDpnK2!7Po}7ggBi(wXDeEA|Oqa zSvqZ3m04JzZ(65TM>eB&6ut5+Q{{tX)`oDJ+}(*7@B3^orO+aMIOB|EE8c;D7$jd`@U9 z0CsUZxp0Z0P%RWJ3bd!Pp1_7!zzFSid63f0TXDnxbA}hkL|zRnSB_8-l0ht0a!=x5 zT%)oej`Fc@_=d&nRM>!|Bnb+GQ-(y0%3E>Z$N)<^2FiV}pL!F?uZyA-C16>Pf))tC zHN_q0mP<)DoAAXVuD4D5dej-8#z2M;Is{r2z3^m&%!nXhH`|aAFW4-2jNl*7XgBkZ z|59OUrxLt2%;95QO*-Sp`MhSUg@{U(L!Z&Tq|9A9=zpa`$ll4PY=jU?2e6=^nMDuM zz$_)9nROF32>mI=ku@UbV4Gp!3a!jI6zhVvLY^b7qi3LFS|jUHAR*!J0$e&nbClzn|J%EX`POXFM{1L>23miaDXO zQwD~OaeV#nD_jGw${9O7SCh^nRgfw)%bacGTBO_**KyF)R{XkkT3Nk0{#=lq`Fy~C zG-OMMB;0zB9ehgw#ThSc0C8dB)XvZ-L3?@0ksnFCnT6j^_4T#0 zIX~fp_Btnx%xaJ`SKev_eDjEL5#kI|ob>UW3x?`9M69L^Zls|DBMcgBB+ncxl=kJV z$6qdt?b$)6!DOt%(i)PyL0<0|1CA!iE^N`AZj7ApAj&7R@x0Fhp{|=&gsuve7BM`p z=+{V%>Hpbh5pf$ED824Yy$h_M{%Hy$qu4InNRxHFCROz?MD{W5oO>)Qf%YK8Xg zwX*eKW4WMfRKO%Ggk&2_2Up922!vo^14 zI@g2JR$bextNFpFg`=80w$cuq&5*d~}cltP3-#$kjy$QF1)`f6p>a zUlkN2gM97ijuNEvQmiCw!O&{4VA)p;l_e{7!2cDa<~!jWF#;gjXheutZb+QCuA=El z6*(KDVzxNAd&478Ac7?{;T_0;5Mf#5i%y_hhc6mTv5^WCKqW)wG#(QK=2YYKYAAh8 zgj)vGX)q3=`}Ngd|GsqXRzniCt*lLgbWFS+DvpAb_@NRxXF$>~!L{VDuH(0cx}~kj zznR{)%n0AEqHk8s{&K-CHovz^-2l7HEVpJ)7OqqLP!7t>&dtV)qki-VLO2CUDg+@4 zb4Ua{RcH2u(kGi>aK-0k(&b0!MI3O*5wcJ!NC6O0=RwGCGuM7eBSw_JViUd)C}8c~+YdqiX=xLFIeBzU@-va+ ztr^gn7$YEY{1^?AZ(u=bP}qPpTX-nwxs;B&JmiGDIUaOvG8C$$av*>ao^dH5bj{+J z3n58#rO4gC%F?rYVR)9Lrlo7UAAvoPkYW_A8RUV81d!oZ^#RotCH?VyC*P{a8DbEY zhv%Z3M-@zBC)0@TLTTyWi#h%*Ey;k^~1K&xw_f6NW^I1@HkeG%`4DOq++@WoFl(h^396kCp54Kj6fX4QIC~&O7K2b zDGun*bJmcr#v5Ncj0=u}(^m~JxV#{&g`SPMmPu3qO+d20L!@w=P3+^kJ>58?Z@P8k z+5s6gP{+PXz&BZ#s2WMakaw&UCoFaJ-mb@31RW@2!$pe1ImTKDHRYX7s!88NyPy|U z>FlBjcBi0BsqyhYjWh+L+37?hz9zhzuroB4JO(!FdWMnw=$s0w>eB2iIdTY z&6FW?e(1%%yiZ@5=CmjU-Vz`PfuLC#;9`{KDAcZF5IkkML9Ul^4oQk+A#tf++QvMMois+>dX-*cXq}2N z*=H01FmZ`lO9UE>=6B~|dD!Vxj=E}YO)b%Vi#tsspy0Kzq(ZU_rEE3foZ@f1^SBWf z8{6$&VKTt;2nmhsDjp0M!ho6*E7w)ey971EV!fZ}&dG`w5h?*t1>u|zlCT+wXOZA- zxx`A1txo@<5h&BzP?bd>=Esf*u$D}KzNQGFQzCTxUY6hgGrF;AdShv+7Fw^B3Q!?f zAwZ+x)K=D%4h9L|;qf`4Q*!rK1$%?G5RyL%lUQj$hu@c%f+9>nA3tS0 z(8Qp>Nt}+ugFh(IM?_5Is40JzV6>!DNlT=bYrK@E43{X1L6VFz_0G4sVM;uA9JHJk z#K*`|CW?*zdWTr zcx-*@BKnk5<5-(kIw}hukeP#Xoa)h>?-j#QXOHSJI1?3>;x^;P0T{$2rZT z>-p7}(PYM-VxlFbpy-`VgS28DSa@Y|hJT^oq>oygb+2=8uuvHgq7ZEe4IFq(rxbt@ zCWfp`5dPg9?Q8NJ2YAiJ(hITCP}BE|z?nz9J5NgIeRxP&W1?nUr*V)j0VXdn#w%c?{hu?2ET=@N z-=+7_)pe|N`WuHv*X-Sy1Y_pJU}UXUDCf|oJmU7C)quO*6$sASn&e8Lw710wWJuv= z*tIw<4@GDu{kAx8?$$;{{V4(oNZ}h1RR@BY8UW#IBo6pj6(HNv-91Uct?J^yu-h>rDlQaVU!K0JTzPFE>J8@TlmCWFvmWf(u zk}FSwicp;hu^Px*>+fqX$BjD{j}j}hSlPW^3Ni3mh1daC_Koy?Dq1t~_s&-_yb+6@ z1GJTK!q8wAO4s9uY0_2+0Vn+wE=6===vHklnz7dDDbj#xUqJ|R%O(vJgE@8Mq?ih4 z!8pxRk!z}#4p2D_bMRo5@d3D5Z7w#hRn_Lq1l+M^C_U`y^JXld}(&^C(mWiip3(xaPU-?z-=N+ zeJmlS0fTs^1DSq&kE{4VqUmV#=Cp1St%*gWUt3LcReXLanDytI zCKmdISHn9tpHL0*r~CBMP_XrQ`u}5q?(jQ5PXL;^H-{CaiNr=ttcZIhGfL=9f!}H< z2y5@km25wKQ8Dxmt~?N9!wVp*sYul+m_W~q9IaYn0$nS8+RvU%PZ!}J9cCa_BQC)^ z4e=5bz68fgt)x#e*Z}nq5CZ@N0766qFaTh0RIVz;0_2xjS5N+lq*`SLOz#>F7-pO8 zZIdMFQ`{sP761hmZT?*;{{RRo8Y7|rFn|JuXFQq73%ekUpx~)zZ?}OBJcaM@^q#Nw z6c@id47lV{_t^8#kuK zP*w7hERZDtApiiG8K{~n0M>?cNjbGZ5~`5EueTwZ9INv8-sYX{+PiIRX<{`5a)w~Q z3l|`~fU)kfw29@ZKuV~5f%xP9hfnzSv2*idKlJUN`IocN%^myi&tdkj{oJ#AhxWOB zyRTZ_yxGt0@q5enO~<~YUS9Bsmp}X8V_R?VyXWtF|NFViwy-xI@7MjekL}!DcNtNa zhAv)w(mFfrPC8cazWZ-`KkvD}b*xz+G=nM7+F(nbR(B#XB0y215bzHMTf%1SeqB0v z-`ZgZ{!)xj{J-{J82z!ak6HenE#<2U6Us}REZq6Qb3102Oz9~>O~f1+$%&0B7t@3$ zq8lj`IoB@cASrOX(~^1Qv_007(Lfh@>@(#o&QR+Erujmep+vVW)aIR5d9PIto0z;p z)AWU*D$an9Qn3@r6Vr)fo)84+6E#V!*d|o(rz>*wTjc zYEfGt4A?RI_2h3=Q@}bI#brJ@sJxS@LPjzzHXOBrBDQm}mrMHu0)?2PVkG2->a*06 z#kKd5=Y@h?hoa z&pPOXfYIa&Qr zEZf$ChC>q`Vo(LfZCH!88dKtpyzWDXBGAlCl!sZ!tZFRfqQxP^KxMv{wK>pk&t>wx z`Eee`WIkw4^@=ID5ZjqP~H|Rw?b1w0U0~o|zr{N3pIdrlJ0W@tB zl{zL#@-96#e+PyelSZ5httOu10(NZNX4yQQ5usEFpEbGcybU9iC^% zMW?;c7kH+IjJ^;JMOUTu+UaD3ZMnWSEc^1Lf5sb7+AU|sv}zlaqQd? z>jE)`E(U2_-N(Row;NlQ6xu$@kT&vTO{YgMCJ#7v+wBFmDJ7Ve6S`5^!yfgw+MaRB zC7yhRBedCN4KF&jp#O$H`zJa zz8*Vb?LblsMn=#)f;G0?>N9EuP8EBI=I)B28dSM+=DYhpf%4hs_DBEL{wXDAc0&P< zP2{yc@wyvUvOlwpcUyf4qJ=Gx$+^GN_{+-)A$;iPcM|_`Z+{N;)^6^~q|edM(*Dbs+z?e~aJ=LP#PlpBL=ey=sfCj% z*AkPg-u&WbkHo@uN&@H~cY$r*V%~Y)A+ppEDf?9YMnqK*zDnqpH-%WX`Wnj8;4q`I zzCgUQG4f0IWRww){FPW9>fa^fk(C!}7wsZLG}C7~YD8g4E6hFl>#G|_v9)91D2Sbo zEf+KE3*Lrhl7ATfJ30tKnt@kFD~*p7{v~&_8}*QOu3+fe2H1%4U}ICsUrP2~_ak1N zAcE?XnZuJ0&ASF5)$&dj~Kx4Z)flpdHuQETaMBOOR{InFF9J3Q^0{qrr%Bu6@W#j|s zDlTf%{|^dP&>0wh`jv-z#!oOyI{KcK-t%fC^ax6+AjONqS9dK)$=3Z6U(5q>8xa?@ zOaQ?vmUDeVonS~1(YDs#Gn&VNzAntkAm-3Qak7rQwAt!~ctkAdyO9PV`s43yCHTOW zPSPrKJ>+w2O0;qLs$>fu?pZzxEj1Wp28~*M+{DV3?xj`S@%Za;9&J+bedh0-N-l&o zw{QwvEu&SOPoC4p_<@2^fJorgm+@3qUkJwg&OVMKJ>^oyNZ-G^>X?H+5sTxIn~%#< z)2$*D+S(mkR8{{l&xfjID_oydHDgK7$NyU7R>w8R%n|9x8A)ZA=^5me|44FeWh&DM z>5SlwjikzUFz+l!^$!N|-R7QN>3n7_nB=q~^p|N9LkP##kK%647d4AN&iReP21wn| zS&h^p3rAdlVNd-mutHgA-|(x&>meT8;n?$Phi zZ73{lZ+%SHCI4s#l0xrTvp4dk!3ztsX~G`WJh~Ghdd+x6vedWDMFf#%qimE}u2d-* zDji$;EzP%~S1MOSS9Ym#v)&syP|)M7e#RO22R0e)+lg%JzaMEhA~Lywjd>Ypn~6ut#I3q*5cbBdmP7P2=?XU(4?NDh+0T6;m?FYQ(!&wzl{x7@8dNIviJj{ZjkS5&%NC zZYFpG;}`@8TJej|$s3Gx<45BQB5NN=y-T2mJO)J2h*rgCcwLK(UK~8~>n%{X>m-m% zp4L!(?2}Ozi-~7K7tM+j+vsUcE5K>anVTHL*4p`{t$QFIpYhrcKDlcNDO=+}_kQSk z7e+C$U}4{?4i~51@ru}P4;@(*@)9PRR$q13O+!Hpb+?V@D2;_b ziijPTXT(*k?TVh`Wb*}(r`D~N9M9G{6e}J5t`B_@8d6|gAFY>@I_1&eX-cyjae%~K z=TO^a&jfZjZMyoF9!~d;vSH}@ms`S-zUHS}^2I+MyTDRJ7gMG5;gz4$4|3%3>-S`W zqr4%z(fG(4xURf(#+~bH1m7x_3c5~)70bUue&oj@t)pfpl@K#DEsF03K zlj3VN9%LGKwS=oVIac}6*KIFDa=-!!2XiP}Z&I@QC=(0ZyeGNzyRXxpXuR`eS46P9QdJz^hJ*sH92Pv!WQ4#t1>ORTE)XSxkL^;%O{>}qTM8V$ z53qas8$PN#L4FTnfebp*RPjn$(}7=vyPWy%fsEyxt{A+(@nTaA8MpOhC8WL^D{k0~ z*^{)yZ;bVCkME(o9$?JNC^^5TUr&Ov1{Ed@ryGtjm6aIaHh(d>;bZfg>+E8>&0cC* zybFw^5SQfqfoex~O5d(1yNJBVJvmuGa=H&)hi-XCyw0lfiu(%~6RS*(_^QJ}kQQ>k zUxCM-fa?3P6RWkYyc}2eh7*47!d5&^-sJX&^Vf|9x$8dM+P&UNsJczfHWWYJ+hf@) zZ)39tlFlw|iJs4O9L9x`2S(P}lJs5Xm$-}mle_XcwoW0bx?LZV-Xk&>`Rw$V!Oy-{ zjiRUZ@d%O}w%F_)o|Y_wO`54C8*I~-NOgzEeQ2*b+AsoZ0ZqKNG~I4|UqmTE8g zb-bv+MX8NfFUl_5Xk)~VOJPxgXmX9{6sj8l1HfqG!dx6pO`S~#+^(oHX8E&O`CPiZ z{2r}-rj}l(F>`72vi7C!f4lbC9q{t>^`xWM@6y(vhR5UG-Ij>x?R$%`V}&8bVm%y8ZOThd zm)seo?z*IL&1u>nCZg!9p=zQtFmoJuZt_&Z5G%^ftIP5^{iOqg|IY_cr$IsnX8bf< zc`ike#yYQw>M@MbR(3qQ6`4Zzl&)8l#1pZw>LOh}LWKPbz^NbaJr%5Kw7&L*{qso zasZQcw_4$%WyrnH?WBGW&FjLco|6s8SgrWQ+f$EY+fVxQyQ0g?6P+&VnwK3sNRj(tlYzt=&aIbh(8DucBA&PFR?r(e?4DzH= zIn@omHzo!V059Uq$o^AR><*}Qz)3yChK1HF<;uExh&f#a1a~!WnWloGmAXwIO62j^ zym1)lwmC27K~`w3?P~b3uZw#Nw! z)LE2faK~NqUZd!L_d3!GH>HbihC2V=xLRfd&Yb?1nm2MjWi1eY=MH_0WwCK$pF()` zICeq7Bt4kiHBIi{v*}dr;k6*S`&3AYAkARJ+`ZA`A%_(%7?%nv#*D*o){!;q{ zIpvf#OumR>ilgFH8NspoJ9-mgJW53Vwr3`ytSss0&@}x99`YJ^eJ)WU-!Trwf3v!~ zSO(b1#_kXql|*_?hecfH3gfL&lra> z4O1ji>s{Z|g^D=l!P!ztR{%+DZAjoCghS@TfjggI(PiADdIpsuON*qn;FY8Vm$1RM zK&xV}jvRj;nrGJFJ}=k~ByW`SjLa!YYMJvYs)%5sDHr~HDsTTwN2N1(nO5Y`M-+Q} zjvz&(MS<>k9vTXpcWR6LHR!VR=M6Q|V9y~+EGm-WwIxVnxJ88{5)m5n7T&WyS1eI$N0i*wfXsR1Vb%Sd0K(Ieif@$!|(thVt z&(K0po56%cC6H>X0zBzDd{7%Xv5NN@^?$e|v&AmWe+xH?k*nE!r5-*MWWDvS; z?)ZfciEmh2+jEKz7R7L5tM=gSn9hqPUcBgopV8ZYHc|-;raDSVu;TSn;DrFKnZH3$ z4(q+z0K4|hxpkPWhUE8=5(f+v#4|5Q9tyg^GjYe=8hv^JY%VX9a^T!?FG&o@3m3Iq zpURZ5Mp9vdAW&3}X*EN{BKi<`b&1V_0+yB^1ArFCz0PYf_2CXfF3_{w98-(^p1t>D zRVclSfw2f8j%CsI*uma&(H)=2JM23MPYNv>N z3`~ViW=}S9XLl6RY^DrTw3}@S!L(3^F+h&Tiwg-3{w$S6UO_qT=l?23fFWGJ3qW~f zl6e<)p5BV_vjeo&ppjJebnhz)^+L5K;n`=NAXbHR6J}g?y{jO0lUSRPhu<;S*76MaE+yE(Ocx zf+F0uXBTNTa%P`)@Wo-EmMC112ZabyFx7DggSz;1=giqExwFqhcmU3dVlaHAtTlO5 zU|=Gk!~=;P?`Lm|I4qL;{a?Uefx`ovRsp`ORhT;SKoe1G%FtThj-eG| z>7iPU^MY@LZkaokKCa|#zv|HN`|i*uQQNy*kPyg;kl|qvsuaU(Ib4e^Q#S64o!28% zx|Y`Q9SOs(2iWPv!~={Q4;57PZ16LLL=q5f9KpV2Zr2+GKc5DfI{QX+6#PVfZ~?jIrFq(a*9FWFYc8%JWLhYQRsj zF=JFtUUS!5a=D^9tJ6bYr>#}%oog28AwR7ImBls6Xo@#sZbX2xz$8?8??Bkxag4_* z1tUMteg432eytE)sy+uh49r0MtJv^SB>3P?wMGt4<_Qny`XhThKh8D(6J~fA5i26OfE8|`I9C&9_OObYOx8Pp5Kc}V5VT+^#r<1j76=s;@_z`Q zB=IP&yTATkKI3;a>-}xoyErgI=-aQek6_c|1poH^eHEVL|GcU{h{4sWh(s&)9$>H# z*g}v3N=k6h)Hvba4~`Cwe!gQJ^yK%HZi1vK!k#ce1lOKHs1uNy7^3)w@Ce?$``?%H z6QVbOqOBCGQaUiC*vK%Zi5_|67#_(N{b^Q78zOtvc4b@+sZ?4PvY!C2(NsbZUQ2d6 zYn&?^Nksw+C0f%?YMoSN3=w7^)jqy}&DSovV1b6fNEBJrf!h_0LsMB+(D1A{G=WWQ z$ue#y5~+BB?}tdC2#+Ksh*wxrk)`}8n2aq<`?B*X1cZQKY9=N#)2}6tK_RCGffn9^ zXEUwt99zfity|`M8b7-}z3a_dWFdwKDBz(W0V*ir+goAr2GF4fBG8MMqmYoszMrQP z%0euy``z*IjdwdlFt`%6Wy6oS1p;0{-~^NkiH4}0{?Oy(ZG38G(52OHBur6c)fv)t zH8$BnaFg_dR&*Ml@Z1Ka>2>CVr~U6)6>ojZD%jEf{=|Qj4!u18>qjs4{tbVOr6lT% zxd4fJsozpJNCPVNT$+x1;S03z|METOm8F?Rg}hHyGZJJSFvSO(5nM3`UUpmUnEQKF zkALTTkNKAe^6+KP{>b>5UuLO`R!V0GD!8@;t6qxQ`bSDBdNejGdEOu4zvdq)iE-!% z%~cV^M%JL8ag9p(9RrKatsLMe|N5w5Z7i#@v5|6k}`A1=S zeC$2iG9+^H1#a#p#ygBbirP^a==L|(AkyL@j1`2q@EkpFriIt>WwhdjLWC*$u&KTr zf@`s@7poOU@cwdm>HO#Pt9>*Bss7L^Od>BVQgYP=0=qtDju@BVy3*9|nN;QQ{ai}8 z)?`|UnFul%2o1gwh6eTZjy#Z_?rv@)MRIfbvwYsGF5FugM-@uM#2QCEb}FUV(k?a@ zPgu7<@I#>sZ9@-x<~f@GWp}<-NeQMZSYjs;LS90O$>qO8B`x76tMW3uTQpwH5D;1% zb_k*o0;nc!ePl`;PUHglWouNb3p}Sfq%DwMW7cGYU~HnU=rRt3$7DvWx{9&hsEb?qH6EymgNcV!{AAH z!y3K`+FZb1>O8tW1_LIz{$vCK0~b^}-9xNn3-cmDHd?&LR5 zCzqWQwbG~e%@s9o2T+}uV~G)%(u5=qdqbtSUX=3XZf~kSZ;?txKvHB03XO&1SQ?x; z0OB-oX8^cpXlUg$^|>^;`3+s~De2D)%j2y?FGd+2V@tti3PqGr(*%Lw$<48XOAcK& zr#gx-7wNa*`BsFTnBv2iA^@h9)tr9Hm&ZX#mdyQ|3iPAPRmBUj2-*(dT!dDyw)3JM8N0TNPji5! zWCWK|Ks2!OAn0U{5*e2i3CtN|4+}8P;@mk6FyCyQk%TT`u%7?L?PNj-o!TQu|FRAO zHP*vAPdHnIjZGaC*ug@YnL!m4A&)( zxMC^cO>8(H(C^fz?gi!lqXkB z=M!|H2?WA2LY)U7bAud;#Tc6idu=~)pItk%gC?%UTz`gy`A9P(ag^SO0`SuGkf7PO zzxki#Nq+7vrI=>nfvPb`C^<(+kcmMY7t2xb{ATw}(b&&wsHh&uJlJB02@yWhQzqmF zVE;FJ>(~CZmU#S?zF5r7#^~{CJY(>P)TJpgK&h>lFwa=~aFGc8?p+xlO)qPsrOlc8 z%J>F$6+CwY!(lanDstARc+3?FVY`V^TUnn64XkIiS*topF0WA=WWlDzGRR0Xlujjk za3u(ujr|4fMWCKD0}usz)z!C4VQAjM=~EyPiG+F1t{-65Ojy^#MHplP8Po~AWBP57 z04ca>&x8YSx)&A6bFswjFm`57^o*k$^$Ix6HH?Y0RNiaUqA(IxGm+WmR<;<72gR`j z$L(-{j*u=>5A!3em^;7m>)N!60U%2*6q$B^b%F@?o^1v1KVP9XoYi4$FoMu89f) zQ)E<7s#VgIN}V{+-cCyNsB94LLDZp?#TrJsu|GJ|Kj4qC-@f&i*R_wow{3T5oQyrc zPX4flzGrvI*?xYLk3&D&vxjcx`d^V|1^;IAx;i@(`4y>+mK)Y=*&kpo=OPbr6lJ8- zhLXc8&}G&}u?I?aLC)JVFNIM* zVxqXOmfMLH`ii(NJ^$|+XUVP9fA@~J9w+;6V_T^=VW$TZg}93c;d)e5)u&Pp5#to% z+G4O2sZbznpv;+PUJ!f0xB}37yU$S=WRMijaMESDOC+;>B@5l!8GhDR&u%#mjHjXM z5>%4ZSLZ}1AV$q64`YVz-;4s}?*HMnavYg@S__e^@FQ?oY9aN1K-K0_B`j_HR5Jq9 zz1_s1g%x!fQh{g6&KXIk6vkm1S^2+8b=w7nJkf0Z{MZs&@CmEJB5=g05e9pJ+Fu`8 z-##jZKYP<-hzUeQt-*rm2=uf?1eLUnV^}qi_iDf_sZ#Q(vS?HW;BP~=EN(6yec|ia zKO3aI<5$OC?7m}d#+vMCvM_D}Mx3`BjCGD|?@?Ar!A(+nK?fWZLzh}jwl50ZJu9pC zsw4L06AD3PDc&Pcc0*MyNP*H~J?Lak_qX@{97j(V1I{w(wRGljF@5lEEKjEoGxGEC z@UXD6@MJM8c=qi@f?6=8qDWl}X0sq%%Mh_iZVjBd;aOnt2piK8kWB*`DUHG>wbC|H z*MY`@3M$yAkb8v2V2j7J&8VpZg32id5U`l=-XpO%sVP3TF#ac-H_5@#!Ml6*=&GI& zGb+yQh_gC`LXS#PRgqSB7Tf#a>3@Dp?dU}(X)-{>o(%O zujN)`k4-p6kW*Kgp@D;wlZS~ilPU&^R=E94&$UD0eZOBdBp{47B!l2!o8QmmUE$Ax z%sf@wRTk4_3bPS~soBC@Ow$gTrXCmZ+iB8bMS5d+czD@9`HHTl0M{cB->~FjwU5)R zt^wRaw!9R6yIBG_8N~GCVAtYn$`fY2lupGqq*I2bLJ$UX@s$KyKNN0lzH)={u!?&y zn^|&qguw)5b(9W>LXvybYlo(4z}rmV17TsZP^PkqKn7s=!-9WBZB;3AGHd4Bk=E6J zL@5*tb;%_E*3`Az=iEKRoH+wcHgYhi14~oTppN7J1N8Zv%15^Pe-5_nu zC^4@CT1p$Aglf2vc59G4V9Ms26s9QA8a)sVohjE!;6`qrTfQHt0<7xT?rBH`vUU6` zzv+G3^uK~afK^&aD5{1#v<#1kHfql#V&C?&&?`zbXehcFrn@$a;TCmwsVqcp%%&CR zWZErX&*=DxM#8p5n>!bM6UyjXUKePbmD*)Q61)olRE-@JB8AI}KGguIVVAms3cbxi zSw_?%AEi*f*gDLGW5Z7l;A_JxYRaWSUh9`M51YoK4p47yghN8aZ!# z2dFq;ASJl3=#qS_8zvk1E@qlG1zoalOqfbi??u6zxw{Jgg+g0$QCx!xyC@xZA%Vhc z)#Hi@A5faezy8*y@TUhcttBwaR%UXDY$@%L`|X0j@LeQwT>Bsu<^)%MU6rtAs>=v) zvc2%nT({E*dZxsL>9oDJ%x%x%C>q*=P+VD0g*y)KnXg69h z-2`P-Wd5|+59$@-WZ`8?<}vZEeV~hbkcNk{3<*C@a?Gz@8)Qkeg*Y$bhA2(Euk9tS zbeK-lY1b+>)x8pI>HHLz9;;bq^{X&U9dRv+%M=v43@C$1vdvvS1$V5$zD>r)FG5@W zrDXItHISEz)|nK6?mM>r62$16H%z zmYZN0X{+1x4wCLB2K`6)L;{Mi=EXsUL|~d-MVzN=^8bXAb5+Tc8ia*g&Je5nO(79n z)hb@;XQG8YD7K^U$uF+C`@Z4SdXdpL;+iliz;NYB&FWgFe&YidWol8?P9^bR%H2)$ zV`ai+=KkYF;lhkT1ETN0#?Z2^yG}lgOzlgH9Com>M1;bZ4n};dS6!9pOZ(&ANIagn z@9llcJvCu<$bLe4>oQ=>JWAs40{n>2yxe2LC_9+7%r`3wee~SBk%?8IqJ)LMZmOZ0VWOy50;n)7wccSbbJfUd{uy zjqz(iYkW(AqC;Wa*S|CNoEp@DdgBcmQkA7e@6z=NxO)KpN7rZHLU6R={tl+AJf#!K z)Z@U{2H=KvvoC{dlhmpVd30$>=-7OZcTnbLdCMPdxy|qOBU7#98R>NI!n?!5=J2uR z@IKBY9;ziwS27)8ZJVkPLG`kJit&s$_kb6Uf5X4vz=bJ*H9>pa<&rasfIn+O0LjZ( z@|hr3uEYN~^;<~FPxH|VcfS6pN0Yc^6gRxLj=MekIH!ik_}?^3kk3;Tz@p@`w7Im| zZ09;pR5*X;?~T3YW5ze8(T4Q;sZ)~ifnoL*LbgkdeVKa z%-ye^u;nDa*&l&Jh6XaqTpf=_mH_$MY44meMHv?g#qXK%c6N6Ff1b`Z21*Csm_?S# zg00(MiyO(sq8Kj}975h6uaJ1>zM_NTZ-mr@|GslHLGi{IlsD_X8{ddrDNkviRTr=GoY3JaAoB-=VgEC*OWm ztd+ov9zfiCm%^=Aqn>-sf<|ne)hO`$MKEGqy$o(e;^^3lqBi6oyg%<+eSuHHDA(k` zcgcLJLJQ0NCZoyH$nR zOuxN)SgaGMLSB`JUT!C?Uq!Vv?p($jsTZns;W(Mt82wmxIj2+e(D+=oZQsSnBok59 z`+W_2Go8b>!5P(h>$)kbp;jIgUz&)*ZH`xZ2AUlBv6bp#so<+RJee*Nb&l7?XE$pu zAssBUZqsvqdF4z+xJqqs&Cs(rT~UUmi|-AEot`bc0+C#SOP{*ZaGTQ*JlSh&{KmBs zRsrs+SuNdF;+bZAgOqZH{TuoQPZ96Qv)bFYnLHY!^>LX79hS9I+wjfc9^KCW4&PTP zIMpPYEdcIX+B4VQ?F}F*evUY1MR%0B2Ohrzw<}TljHdZma_A;&R=}m32r4&foCV$w zUc}W?vQ_dH6ir#+{;5S+@}OF-WL?bE#8BJt-rx;f{yUh;Pc^OM$rN8e_2j~pKokkj zPQlfom`F*sXHM+`cp* zH}63`ia+m2zU1bYi{ZOcd_xlm72&=}w6Y08`Ej`#a5kOsxfv&&q@MKPK44xn83ESa zucBNm6VxPMS7m9Je#s!D6Y-&0_v|O>gY@D5=*i+Lmo zD`kDDxvc32&B4UO$;{}yfZMVwl0lu&ZKF)@ogRSthh| zTMvT$f2ilzvcM+O*rHld=9i)KmyI4|0Far?f82w23)UZ$@9j1;c3*xa^_2rQW)+x} zFuzm|1u({7=Km{^zQ2E6Iqh8e42-QF3e(Vujq^^uUH~8J+i&xBBmM8|#C=y(3Qz&h^Ln+Lx_@zGOD@z&@t!;r*-G z9l59V0+?@j#eh4wQg`CBM1-YMpR<|6@W_+Sm200;t7FGF z{<`nDzS-xP$W5-MjIidwx*}B1qRFcdvlKXmxe+wW<{M_f_ZQjhh7kMlA{A$akQx7- zkvrsIy+_Uej~Ntw`?qp{`hV)Dz>5P`@ZRrX*2uHliiMt-AOSSD)&2#ZwI& zc3x9YFKD0{{qZ3;Qz}A@kwjIs$m`H5rKEM?BO{yPR$6>)Q*>a5WI29+_1E?5``fww zT#Qq3tIBUS&u@59OQySiBisBl&P#6cWeIGC+_^7qR%eABD*~?J3DF-3IbNfue7Dae zLc4foRT|!vzheQ~lw5YRmmA*qSKY4=4+uef;b7%RY;Nj}lC&qXp0_K~-R^YSEu^OY z?Ww1KH}g;3^}^uq%j!bDHLU&DuLPlYzF9Yxp7VwW%z9cx29puG4A(KI&o}pf*yf%j z(b%Mxjm>AUsw@{$&|fiCj%#04x-qd;q5y!c(Mn>5*_m1C2Z9ttc@XFy`-lzY5H27H zAmNd;u9Y?Io#?$%n%Yz5Rim)nupFJ3r;X{`6<+>w&=7DBC56_t7LFhs6 z$FD3cg?a|dEkDy|?B}CqFZue6y(mm)#9PiHAmTkda4K!5S_%ygG%dnJ@*QvW1FGH3 zqQU=r%X*^;Wg`eyjOCpUWWXgErvd1!0pIHe7`mZ2o2?*2t|8Wv8LT>lOF>r(AX;Od zF%Gce?cd9{kMez?Cop)l)~ilHDaMLm`(@rpG(iMBM0FsLIVO17+$?5;L9z5NGIYcu zLhHTMO0@PS7*e5_g;DC!6ojP<^Xf5^M-YPrdVxx7CnH=Kz!8BdjR6-8q7P+cf>DR2 zj-AN_B{@jt$0G_PSs}5k)q!G5%VXK^$R$uyl@qEs%t$S`?Mz zqQnyRjV-+EB3KL)F&yTF2K${s1`!PaD0YU1`H|trzQ+|G2?reqagjc48Bk$>_X&!D zaB_x2A=T7Zz;(+fs6?Q^2Le)LLc+-F6g5C;M=gL8=&uo4cNfZ(5PXA12ItF6!O9wl zp zA2tCz3!W74^$2qFm(>8rql7LSO9p|LItKw6lp~IYwmaC3VjU;+cBpI~u~$P9BbLAj z6fj_2h+T!oI_j*Odo?Di+eLmjh#qhJ_(Bp1CJhKtDp-uH8B#+k<6}W>JN4+VaeF2* z^tkgbmUdCsySEU7jlJzffJ3b%0c%I0)ZQGw(#a;UiwdzuIyFckuk%*R8Qn50u@a5vBGs zzOQ(kGmPDcCv&vo3gWH5g40*?(O&&&eRF~b?b89VA$)T5kDqJ|;!3d8Q!$iK=?K^- zREQQtG?+u#-Qt_oyxkKD{`65-dTY=Q3y36QtZaJ*DlB(33dD%}6BXKLhCm(ry+2Ud zx?a(Tevla~YT@dm@~2_OG6U4+5cGpDrR81V6i@5cB1OEB*EnYfUc=+rmwG0r1WrE% zQ>mn@gh7}d75gk<*gkva{|C3PJKqeoTlv}A4A~?wkY$SAcJ_W~B5{VvEo9QU`f-Gr zdT)Bq&p`MoiGU!W5=42~KJ(y-7!ES4m5SUwj(Z05sru)y*!t@p+DW|1SeVv!45J|o zf~+*M8aRSYPb~ne)LvuRy%8<>uq52jB$f~;vrwXk*l_7ykgzJF4zD!;jNSFtoreEPqM=e}6-X%_G$9Vgnoz;K)d!0mXA4d# z7FhgjMM!D|5=!szbhfX9nbs*4^OR(Kt)JpiWN57>RkSF8j0{8xkzvA<)M*F=Lib6k z+eOi~oWf&(fD1OU_y9^kwZBlg%u~GK0p^Pgk}3u6KLfL)>V8!o%RQVnLmGghv~^U; zhM^Qt>Q-^L?q(!1S!5`^##t4{QOW)uMO98qc?<#18UV4v{I9Jsg$(ECcmJ|GXzvG) z2Rf7!9%nHL!Q2v^_vw(Gw|isNxBD)(j)~iFNIWVLt>p%iuvTWE=A_P93M+U}T{vQ( zP=MHJ41#)Y!bp+HV-50-gQFEt3&C)#8X+-92_qdQ4r~BcW~{uG8w#%Kw8jFwIqP|9 z#{rsxebYwVfsKJK=Y$*;x*FeaZM(SLwJLN*Zn1~cF^G795O&? zw8(ujm)DOlN?T6XDxr@_sK3F}z7avz5c@ANm{U+;$3%_6rZmJ*Fj#MO#0MVBcU;g} z?HqfxiQmH)PPVyg_x?Y8{9BUB&)vfQ>y<_Jffvj9+=8iNTdii!gfIYeD{$$hg+p(~ zixZFPOGK#U;%VZX3>28g8j7T#@-dr-R*WW2wO5zj723`a%VCTH#vl@G41#N`wMJ4< z0A4u#T=L@9XP(K@XBOrpKDwcBdgZrOzQQ^!g_;mB8r3EX#+d-PchrUo6GLH+i}-&| zaaeH?)9}_?a%dF9N768GI1i|nU`}j9BPaiXO#NnmQOj_s%pq*|q!0!!UUP#bS&ne# z3r@*@%a2iUVR7-gqR374Og?i$M93(pBS$FZn%nRv+pKohTztYuvs5C=1%wWHgS3N; zterGNg5dXWf3nx;0{T(Gr_NghWt}B_2FHpJrAbn<2$ZpMRKw=O#_U$)h3X!Q)Ls$| zB2Fs77h7{aO$po@z+sSpIp0zTJUpHajcyv9^8}I^B)6Ax0GxP`g&_AyCP#ZXl>SG# z;Fp_fQ3*HL0*cO1$oC9`0tcS7&yL2f*uUn{rTrsNlfC3poKV}Ig=N%(m}a&YOMj*j z_J48d1d}lT-O?8C;mO70@$<>JmlIn}g$Amat)eu2#? z&_E+{VPr){UGo+fk``AI;}FPD5>O-+mC^=8tAwYDuyeY#lEN@93UW9DA(JvdMxf*; z5+WRu+$q6479tbl(IgY|Uq_MOFO$s7M{fu17L*!HF%s~dUU8#!mBPW2FTG1(`uREO zW7vA|`@iHP;YjWj+1pM54z8UWxB`OH$QUaluAeQ~KZnomI*2`IBtsbd2G``o=>=n; zfMOEICb<7mKX~P^{Iy&xiCxklwRI{&B8fqPCyePS^2}bqqiQQIl`qA2zw?hTfyZC| ztlN60K1JRmwh3Y+)e=$H+kKGvKlWrU}YZl*V#p0+Yc2L+6J19$&E0aB%}8ZYmzC zEdx0KbWX9eg9N0!fU-S*mMml9MrBeKfpPPG8vBDlL&)&BmSc$3J)0t*Rn zd+>-HY8X8@AZ=8ZQZyRYbHI-pdi3?H1EXAmDK9KYzzo6>X%3baXt9)F@xAI%@g0PL zfq)&2w=vMiz)B{L? z)IhM(_~RUtlS2dugRK}#umg-8!A@W_n<_Ot!k`**B>l=gS1IKz!JDXCa(jxiWI!U= z*{Au@es*)Vn!ViPc1}c*K~roawjl5{=Q?Is?UVy*dbWMkHv2{`+Hq=mEpN>R=Ol`m z#!3iBPoP}&^G=6--E07I3GyIoH{e$sD6m`21YP6_$!H12sDJNn&?{%{m-5fW)= z|E*_n>QmzAF;Jo~N9EiR0faOsFUmbB+qYvlTygqUyatxhf1;F%Ir$h;Gx^xKtiBHZ zQx6X(2OG|2QUGs~Tp*mmdSfN!&{+;4nnbXr92EF@`NqLik(F#q$**EvBjzm_A72s{ zy$}eC@*(A}mqY_O8s{U8jnA?xS?a0A2fK+9^x-k`X=!J(6dDzk881E<D)t!?YDL z!3X9}BAuSPsyr!^{Z~FjO>CowPiV$qwe|@OocB>+>6EMoX_B>A_&v?P_zqOrP;Dv+ z$rs@m6a?9_kp+Wm%|#s7*KZG3jzlK{2UZzTp{7WqYKK4wrUcD_L^#X;@c%IOGH|*u z)t`>&KQ5_DWfuW?!N4E_3Fw`2k%nk1W`>1I+PmK0CzoaGgL)*L8A-MbP?7Du7#^Uh z4~ZT*wU#6(ey5LN_VKRU7ez1;^wgrA%{dLrf%72-3d)oSnt~l1R`JlvPp!gVj~X!r zrEXB3nBl3?c;^dqAWhRd@w`%$0Gr42zYjznN$6zYv-*K@1Y*yWP&oz@jn`4&rje<< zVoO*}6D|W`$kchR&0!4CPEyifEYXzDZ9Rw^Xer)mL6ZBH3e6#==f^u2#G~^6+SV8JA{W1dI{m*}|Hh9g zrL38t&|s1qVEbcXi|E#F)}n|rfyG8r1r-rsF$3+=jEpq0*^&M)w@I-B>e$J}RS=B5 z9dKdCG$5b@%9~Ik#9L~vd78JW&C&LdE}S00St}*fS^+AD!U3BlDFd<4Z$7RcOiwj^ zo757>*+@#X35OwHu}OeBltiqcL@Uat#X0z(Tk#_DVl7G?0`U~KPJ$HTw7^L%*l8?c zaZrKyMJCn}cD6`kjB;_!)6z*Gt)WKX?7^yr1N}(IHOE87uU1IDL8DgSO|%F+IfHDd zI|M$_4VLF6;<$jWhc5C_eb>`%wNxTk9erAUhi1Wy<57PD`Fd#8jvl1&96+er`f_h~3VD zTsf@eJdAm|({0*g0Du`#nqQQW8a@{n5tL&1o=O4P1~tq>5p!4tGv2%;_w?{b$k3s@ zMBJ&FR9o`%=BP(OpDC6v^>Wx|C{w1up4qdM=r|gIH8(OM?V$+hkTIxn^gOfU(eS(o z7^EO&Nd^|gbP)Ci9-_g8)|O5UMI+l!!9Mr*d`RkE@{A;BCmkR zAT%-5GlNfh;+>wtxTl5cve6bnIg?R{=$JLaVk;@+vV< zW8y-Z>WG{;Sm0>4ZD{SF&eP*_2NV(#t%1mM9wIfB1;MG68I^!9==W~TVj=bB)Tn`C zy+kj>10Z1#7>pp{66?5_t_l4vE*e6YG%@mJX}%!H1;UwJp42c?ND-s{sC0~BuJFtUE)j>SMU6-TJXTgQH(A@3reRUg8^cQXPl^L@@Qr; z)ckFpq7~@q4#dbhf(c^8$vJwR-dD!74rdvhgpNeW~sQxp%15e35bM%1$hU0jA3EJqL!0{{d7GeiU+ z0AO!at}1H+l^Mr9003qNj%Etrr0=dH;8X;l zN<>V+*#vDb@3nWo-EQ{ZZP(e=sU-ogy%5f4FtJ4680t-#MDS_Ah=khOS3lM&R&w-`LwuJMrPV zd^E`1QNFVbD@f@awwn}O)nf?uVQe9pwLW`X`=$F!?hj$-?BBRyHLHoz8Iys|0snSOC*FU1e(FMg1}*sI^WhDJD-*$Gt zvQ3eaqsn^a6j@DWGIgaD0?KO+5%0;OuYP0gqewq@AH+~zow^U-`2RfO_t`viaabmc z=X>h-qK(QUqdehm;d7h+{D{NqVa8Y{z@eC>%%o+4JZ_oEj$%P1C{lwN?It1%{1|E! z>NF4per~GEH}MXYH7O!iSEf@|@fc(7*k+*4x!ApkhRvF(VA)8dV?QOFqRP!sH!3Re z^<%NHy0UEjuRNK6JsG}UHdWRi!pb&}tbN}6cJ8iLRA!qpEcs0BL;TT|TbSjH^LbG* z316#q(IR++5faB1y+iDfP{k}gs)WZ5$;l9w(mhtl zlS+_g9+A>io%y{Z;WQ;%eKhs1l(Y)gc4V?8MYOHTdpLThVmA}NRZpU`^G2S%Hpcii zhi`QT8D-)f-ei@Fs^;cp8MtOtVvaki-`lj6b6PYaSk0<)*b+!Iv%pL@Hm%O8!70?3 z{PuHgNh7LXP>L4@n(ZwUi`aUAQwZ6~E9NP`6g+)`!ig8p#**2ob{aMB(hA#di6V7sU zj`h02aw@PAOf+6=<_AQY0iSb;_Ps}RQG5dE@DHC_)J zb9Q}u$halr78!7dE%6Z2n)2QQnVK%@8dGu5a_{-F^*%~P7o;aDrMbWwv(z3V1D{hK zxr#<&W6$8dtqi(&=KO#PMKH3@qwHCn|)1VY)9uAZ5g(z@f_Lq>}Dx7NX zv05--^4=sd0Bk$=)b?l0U_U+@TPAutTS9$PX{q=xGS@f-Dv4l&Phq;Rj?c9dz4yE6 zly9{qIw6jhi!0%Ct84q-9$;Q?w-His28<}F%67L`64cAxZkuP&SKaAyNokc>7U8(N zT$jqK^-~(rT+F{j>f6N)@hqZeCmg0eIlgNln=7o%#Nx>%#x7f*&aQXJ43O>NHV4e? z&a~VpGajRBKZst0t-wk_nna-4`ZjOD1nN1LG`lD5eadFW5?$kJ0iJ6Js)d`ZKz@I? zQ4o~Qh248K8-(LwH-11p37?E_EfgTXI$QvO*y4_ZR_y0~@10m?l7mGEfJ+NQeAK+{ ztiVwI(y{mc?>AU_nd44z< zJ6ZdiGa1Ev-N}TL%YHex_gGbp={CmkPfNJEY25v8O~serdfs+B;s9gfR$h%6g|D3V zU9`$`#k=iJuWK9SC;@noU4UV4_{abLk0F(li;0Jk_1L(&xBsNKl?2#cyyV3=Eb>ypYgZq4$ms|+rTmUr#Y*HH%M6V&^b`Z$_ z3s*+qtuIZ5>)15-0=qJFmTz{T((4tP#c3}2V&_ZxcC4Zu~`Ztws7SsVGSJdLkYB8mZ%`mMuv4oj5N1_pkmi7q--2 zsIx&SmS#Jt3e!xZgBHJ8#%njYaKa>%?d9u)jBmL&$nui| zKkAG}cd0LQsVH2&NvF9Ho5-ZSd%NJ|Ihoo9g`YPYcuyVGGiYP<{cVTM-&jI4B;7+a zBwgg?gw#7X6U$`BqxkoA1FzmLgO!CG>9E8yUO?m%^7m<$sMd4%zVqS zP$E~kOx4oIl7Ux(FIutNC)CiynFHc#ye+q0dx?PN&WBl*l6++f2(kDSGm zA(sl$B^(#Ekg?nTX4~>U6gV9G5;}~g@+9?tsn-=}Ap=L_MW%);nzKOg4N&cB*})F6+ ze47{FN15RIPTfpl@;|%A=t^6qY5wd){`&LJ*5@}@dPcJUvoa^PzbRY65EtcFyE68@ zc1veoEo##ThFO&plURmoXr0j@)6{Zn26Q4Qog-RI! zV}at^Q{z1TKuUr4k!s0T;uC&7|FUGP41Yaz{Q6hAE3PAYziEI`Hgu?qmwH7{HM1@p z8IknB+gGC{IjBVe>x}niVsypjv*emakp?2HJ|!mp|DseMNxr|X%UiKL>?@tipPQAV zxv+Wp%C5idwKMCZ)5W*Z=XY-vs`)K(v+aL;JL9wPvngGBvOcd*Yv0B@ZuvagbNXMx&zH`N z{EW#CYckMNa~9`lbU+l!Ren6%IZ6kg#Nrr`S_#Qh5)T9(saAO0drpkBAygu7l-XHI z?e9NiEJhqw0cdv+6?xod=czPQ0Zb7xm@HS&sa;iiB-L|1e*(lsr+geCa&qNhL)J`5aR?){4$k%9-32UDNxw|6+`Y z>`boSpJpGk?oKS&|EKq?aqHLc)NWkc3t^Z#w{!iHO#eK`y@h}d;479L^of}>d2DtP zW?rC>sCj0t$~VeBalXqazht?gL3N}~DLmPmH>3I;S`l$1@yUW3{|HI8U0A3KALB!V zOnqJ6-)UjS^NWwHIr+{%zWBYLRxr7**leY&w*TKN1FdNbT)u50sz1nuPt`5HcCDZ$ zD5HXC%O_@XJGPY=axLy1Zs?VY&R9N9Q!m6xyPZa51%edkj~AxLdv8vlGt)z!WW{3( zIZORm?^?f$M?OU39O&k|)?`vSG8N*19i@C!@2lSky>=<-I@{)L#Kz{ay}=5Q;DSii}O*N>Im#G`qMx^=TTqvr4x$QijK=O`c}s>WE(5ko%$%$ z`Ngy0%8bzObuM%4^4t4%tQlvdxuCi+e#zx{luSh{@jlE1>U2&LK6HXmwu`i#vT#EC z;{Tjqchl8wy$3DMGdLHc90ybP2W@*?ly~N>o6l}w<9G|IC!IaCB@NV}oeFQ9bpbzb zoR9DBQ9ao@`?-4yy`jxVy{{Gh%2fM@$%9R{6#jf;6JN zhrZycj{4DKlwGbZbHY8%ULAeu0PwnS>Dmbf=hCDFzMW($gAPG5S z#YsOz#sl$|a`&E`Ww30$)q&TmUTlT%UCvlcPDyv-i$gpT8Fwy<04w?1i8 z-Fq}W1yD;&rH5w zdF*p6A+O2DiNj<+?7~CyfSp#T`HNIeT{VgFXD6b@9u0C7FR#~*)7~16zmo)$BRI)=*a&L)R{O`6Uu)*Ta40;>`Rze z+&HjHZqQs@6}^fQpAJvH&oA?nsK_f+6$M2na@Q@BUx`KSUF!*I_OLdbxIAXk8Qp*d zd=hM-^QveYTZw1a(}A=WToxVhoH_7ap{}KoOKz{TU(j=NZ3-2OCAS+#Ukuj90Xmv| zrTKWC9ZT8p5j|*8XZ}Ay1d>AQsvUsu5BlXFPtO@L+KTu_QqZJx)5gxt_ryV7qDi^9 zG+&W9J7AaPrR!g-Y9A1_F1maLGd*9VhsUx)@==G`bll->-tT);#hmzUD{-`&{r?p_ z$7aYh6TXB**!|9oe|m1z>Gqb_KgzcLX2v!{X)mBtIghT_vvSw?e$h#g`-LZo*pd=Y z9(D0&yX7>BHr7EJO)1vL=Zt?^exX4~q4W9O&q=nWNLjlzjgLHksZ2>q)pg&1lruQf z>}o^lzQG^=>ye#@k$LQlW8r$?i=)5V)54EIVQ@K%H>ENGdK#bvuww z8*p8VRS?t;MjuFaGnvzw)bL0`hPg(0iI(}KhC`pSFM()~m3dW{1PfM@seCl?g!D$k zi5sS^T1!Nf`Bid)3)JV7`TG0Cu;GkY6xlZ-!qcJKv-Z?N; zYVO8Nj2K))Y4s1GSsP2z;4OR|7B0pfp`l_WBj7+)k;)#l5%n2wa@a)Pk$Lsj*f-*S z`uuAR4AvYX!9}0|kQ#|^dE!@5CF{h#@ds7&N&><36A36_U>uv;2Upe=8_@31Vdt$o z7Ne3XAhbZBGL-O?gd|dLWy}?wSyUBAZDqiXuo` zWoURNsX!n>3ejVxPzf_&#=Nlm!M4Te`%(7%&c)w5db9Uh|J!nS_m_LcU*Z2U6ZV8- z6wKw=L#-e}0aESLP|87uWj&B$E2elkQot9E5R;jlD#`!l8 z6Z@~bj}aUzIYa4d1}LfBIyRK~xuxFxT?*=lZQ_0o zf>!(Slt73e$}3_H08OAfC0E~NF$nw!mB-T=!5M=J1`<#zpp$!vq{0leOy}^DRpf5! z{m9cD6ZSnmUp9Z$zQG%cuWavuIQkb2@dp?*O%O_26jNbLw;XOb#3G?%Y~j%%=nb{6 ztQ+PTArN$`4jx1z=~OR?m>Eh7!U^A&vGUTo02!D-z=JaAw33j7JLEK2Z(rhv=-)}fKwc} z%|!tDvyK-6#!QVG(3VgT6~J;+a3DNzM6IHTv^xd;$inZI&`M<^m84O^#>SMSf~$fO z0~?$hLZ#@5?hcK`jb3VLG0-tIOr@tg$u(RMARXy;g!5Y{FHF4Zz1)LbaSRoQEo{hLUO!5^sG-zW!Q73ah|2 zVyJZ1kC`ceGAu$)4XGhSxO)E;pMXhI(oEv7)IFHpKKshyc;ce#2*Su|7{R|~-OG6%R_tLGghA_4FI|5PAia9Co1fud8H$h0w5|8tKTW4rW!hOgOJ)2W0 zg~W^v8gVW~|CE-hhj?FsXA=!x^Islj=9&F470PWJ7{`Mspk13C~vrl*p0|UIaHmEckH6_TmJOMhHP8IWR;}PN*PiAJX`|oG1fHf`1p%x)-68 z67LHF=^-&roC3W@#-R~k5a>iIpu64v3;MJd2|_||pxnt(f(OUabQn1bWv?@9 zH$oZPQXWHy7z}=h|9x9w) zBZ*G>$3Bk>kal5$x{xZHt4l&XdQl)qAY$##N3V0Bd$D3~V;GG%Oe#YN#e32bCAi~2 z*qW={hve&J?TcTUH2WF3Q>K`{a%!OtKqdK3fkf-1hayUNLCw_p7Wy0K9j=3*Ycv6A zXf2_XB8{a5lS!Shswwd=RAcv|M940^3>4Bb*h$c9Yw2B;C%(7Qv?D-52MQ8Q0|d_%TM-jd zkCr_=hqH%|)Rh;GF^FI|N(iPz>6obk5^Z1>VWl-W52^cuJkS(3^Rf8AZ!C;NVdVvo z6myAYAg@3WvnS_=?3Odw+-58^R+D#D3;em$osN7=`#7Kp8x>jrBg}5S!BTq*@6dFc zAPeIiV%h5QY6r z)n#)4L>xj2(xb|N7zac=3U@hpZOpO4eOv(%S60w42n9*xf|jVTI0ivOABzpQPIf6KyyoK--#R%Dzjk*;Luuu$$S5Uxg!T2a3tJ15QR}> zAX(LWhwbE2V2|1oKPj!1&D-WmXbl-t3Foc zOA1rOP8kCP?mrOThIyplfj2qe&pQ3Ixfl(SO34C~7@Vy&Dd@nYch(QqoIkSWf6k7E zH-B}j7GoX~hHv%h_A&39t-)#5b~xDTtslGX)X5-#iZa^ z)Fos!7)?wapm&jps}Os0&z*_tE)HmH9pfoZBAiJz4pOz!hv06&i&%=p9`zUZQY?d< z40$=Fa6%%> zk2?@kSj5Ct9b*K6z9Bq_jVteEqvis=uMJZg7~dk#kW0nu5{lF?t@ z(F$Yg?d8ogs>Dori1ZqdP!AS^xua{Z;fTI0I*8OijmRu2NJwxLv8XGk-$%}Xq@t^2 zkDN;7=K^4Q)#5^M$arZ3D4NAJ)+3mNL&XP|R>AktgZ7|FJ&4eNm|~43Ner*`LAC&C2+;52{`&~68UUgqxFZ7q zWdKe{sNEDZX(p=!b>S}jqwqwezIR*}8~9cr`rSQU3u>-F(DmKVx?c9L1E1aYWeu8A zC&2|8Dc{?#jO%)KS)&Z;Q}`u50FVIynwdBoxB^&JTS%2% z+{hRca^BKL^>T0JcH6ewW!-GOAgKZ<3-DjqbKd~Xv>@4Xkpb3^;AglOzhKn=er4IW zdiLEv^y%KZx3;Fc?*DCl_GiBJuEuwL&9bdO+The|XJ^sY`>d(gAMe&%GwokP*!=!# z5bx<_!8dk4>dAgFs|@u>)Zgsa^@D;xhSBvu*uS9sw|>3$@^75|i>@U?#@3 zmf>VTR7wdi*;8VK0-KgfmqxL;@-(C0EEY<{RGz3(1_@wEnpF)*@*zY-$UFg)Vn-U< zVv)6N47pWlQi&iLwq@mzSxPD@tDZ_+JG!$Iv~KV?u7%=V5_6eYLJ~vL()iV$(8!a9 zDa4y_qU`PNJL(1dIVYJvMNZn2E^WW!;v5%;QdpxLQ5$9%9kt~g4Y(o1rQQmqDG`w z&$dk1u_J9=u_%Et{>+gFc}^(=nV7pzj)NF?VkI+clfQfHc3H6~$=_1P`PQ};?W?zE zt$V)X_y~{ch^QXkw?U1bn3k~5e%29hQc&@#6*!SMgC;;+hDe~Ld&8HK+RC~DnK`NU ziqb>WAV=Vf@Y;+%en&@T5)nwp&!Fykbcgk~Wj<;~-amhH!)txAx4ycBhP-V`YQ<`i zY|xgb!*=q#Dlw%?qBtC1MixX?jZ^`X(ujlxA{C8P9E9QSB9Vb?SET0%GxDM`P^7^t z?HzKsk(A2PRkD;)RQ~)fm*^|R8CjvRjuaI|yeTpX|G?VJNvMw`^O_~kiA0m56`LF& zlNs2-ARo46c*U^)0=kGp6OSU|x)5Nn3J$V$DVxX3pVAxNYWxxtAp3?CVn@F|{6=is zr)}WgqiiGsIHr$R6ycNzo%o7&rgd)U=H*C5(^?aCNo9~TH!N}_SfCSDAX=$uhPB#4I5sZ=B}S9oNcMnX{9Xz&-3 z9QApbyZWPnkkHqT{Z}8KI)=U<<{Tk@d}n_b`DrX^-_Ed0M9Fp4*yE9ZnQ$j2dtw+TT6s!HB03Ul5(Tj=m$j0-oU+ z=PNAD3K&&|H@O*JI=o91TYfmvra13opLuK^Bb~O=Sgb!iJR8Pv<{Gac-YaRtNp(_N66 zb?Fi80L$}*91aZ0L-pvJPa|+cr1C+Kso$ zxWOlfHzyvq4#%4LI6!*~k^B7$cy_qEkuT%f$GbSh^*a{GEgTNxdvLo6w;Ci}ukcy6 zk*PJQ6yyBv-sB_q>4^vAML3_@g4<3c0K(36ZA!|yZDewOduP#+Z6>m}padS%fhncn zgM)!#c4QYT zEaqT|Y$|OeF4!GUADnyRxqeunS?CT}qF}_@LnWZSazkm5$;y0Nirx%3{cqN@;das z4u=K$)1dZB7m^!6B$vh+#OncF-gF8Vva2O1A<1FPOv&K??w$WLzkYg0vZh%WUQTBt z%M_0Hi1dz4p6IUdOf*Bf(CFFCaiih7ShrSr%#UPa@n*IHYOZ_zBpFGYA+A39R&!u1 z0p%*25PC?ctwgrZtK)8<-x|pjZ#;pxT&#v<8`pLNtex2|#V& zgrT}McY10nvYMPoiuOoDABjev7M@4#LV-tO&wiKNvkAT3WfxJg);K~#zw%5rAK*L; zxUL|smhXQlANOQ2I3n`Dej63;xA~H+W}FEMo8zYY@W+Rn`Zy8-&-e#5Pde@Vp1+vF^OV|-3#EkS9F0S6H?v8dT%r&d8$bs0fG3<8S&rS z5vn1{Dq(HW>4a42fmcV-3iKXTOY&k_hX96-8@tGmyWx?KpoA1Q!`8{(!bUBFkm@GF zA-PoRK-15d)~vJL@}ff=SxE|9LgrATTz9i)PI2R|q;W%VwVZUw@$+2lY0f$_Zo;I+ zE}KNyI>ouS!*yzzwxL-i5=vO701a;|4WZ5Zpu90tbxv`ksirrS=ubaf_mWUN1l*al zpfn2JcL94uLl$4GUOg5t?r&s`j{??{d4B{w+Kr_c(2s z^`=C-BC=hV1UHi=JVrkH4a&*WD2F^&UH#T8Qsj=4J69M678uX!n;Kkgqts|=Prxo; zBRF=%89~kafKOklb5Z_ z0}BaX#V`FP%ME&v5&)1F9-NFwd$>{B@c{CujbDrKQhX#ru7y91wd_*O3~8Ak%XT}p zim3QSav)EfruhhHMBAEhn9#4vlBeYI=^-oI@xZc>GS+SAzxjJ+k&iV*9kT8->Rn@P zgjn*Wn0Q&*Nv8=h@u$Q|*pqFS89(T2qHBo~hD_H@q_=*C)aE2nKoEB=x1JE^CEWU94Hv&kV@H=a zP7tm3 zmPFAK6Y{oZU?Jiy9Bw%v`?_EWc|wpuS^j?d8VI-#Huzlfh`bj)g@}!wRBNj07`F= z5z`_wkg8_Vbn6_k4P()H7}er5M^5Oz^RSY|osl*b_e$hL&73c|L6N4v^hYo5Tpx#S z>Hd#XJHFa#Rx^(oi`Ah-_S$2wJb`L+Gz}DFL}={Qmdv^~QQKSy_q%=D;DoCOi!ISlWQu zq61N|o;>EA7^k_p_)E0+&8&&J`PVb_H??~sj%u-&;gNh-QNei(l%@ZqoqlJFh5#Ra zlQRh|#hqI58GCG<_NLz+6}M@#3TXpYlp=r8fs_sKfkOoTz96GyM0~_Q#m=^(EAHuQ z9nC1Gi$BT>q@}>i5?|QMFeMP#+0_w+TH{KLnVMSHCPm7CF+aVT@feNI^G05=tswO3 z1v01=Z4)a4RbgsW(JX5@zOb_u_=&7}n@tXC&7vNIPI&Rb{kYc_t28O>_wz-$D}D?W zZ;Ilz$$%fHFey#pKovIAc)v-psY*nUnsR1pXI=9yI=BE+KTMzlG+#Z$8xTQGhT|Se zMa;N-TeUki)n9%b+nMQ);-?=DqR4pj4^JiX#vCT|tC2M(8trh9ojX2bdOE@XZ0+0x zx(yFMmH%tRrIB!;=LJ8{PB3j_KX#MTDRYiZuNHp822kWyY5v#fQ5f`bZR#500-Ep~ zH7(?Dd|;Y#=Y9?G%vuSncua;zw7ZwJ;RT= zk^AE6`b_Qr3ZC5Ow7Yw~@ZR6ANUu7wB7xCdY8om%stRTp2y7Z6Onvc!JqHc!YLR2q z#IEPb_8-e}lot8&QzmmZ{9e|AK^Wj^?tYVo79E_j{dpM1MLLJ_cdvOR1N!w93LPSp zbFk)_I<^~ zz24~l>Sn?>`QH#TB9!QizBIde33^4RQ2W8fr}D6)j`5M3lbey1GZ&sbX)-b=lCC{} z@Den_@`AExdRgTbwF7l>@9jP-JREMo(^l#7`XLK@_#@>sWzS)#Abj?qptn-SpQkh@ zlebN!@C=+TWjLu*t(KdgBtG4}UL$WjwGZo4`@nkOp~bmgf51jI*u4->sK3@Ui{pJ}0iof(XHj38& zy*>SmtUS1gCr>ZpwJCAkj7sueG~CdvA(o{+5e1{XEu$(;S`Cc?c)P;riX};%igDo6 z;a`h^CWqJt0TF-j#(bV~_SQUCBm~y@V8rdNu>$(8^@gFrZ-d-o%jwJo{mu1ZGqZQy zW2!o=?YzZJ-M@AZ1vM_=3~zb!MefJhDcOZjJMSydcR0Neq@(_#(;2<0~fKbgCU1N&`(Y&0K(nPG**Vgvcwe%JpC%7Jrw1i{p zq|BWEh8YluFhy@yU6MF=!RvLj{94ogYU#nh=yAKpIco;LcY1y5L0B1m$zZtZ# zpEsWCxs(PnMx~0{0+GS4?cBVc=6lH0oAZ`^mpuQW{BUhh&~Xf*_WXxD1aEQCcjQMYx+@~992n2Id%J98x1NVd=bQab^~`CqhnMd)IIIk>j#oJK_IviCt}(u`OEm z(9IsGSe7p_9ZiAOF^$A==H#G@w1@_HZsyAZ1AE4`Z>I&e;;;H~#thOKz9h_Z! zW|WZlvKfc1uh6A$?c30DZPhter%2JX@(ctrtOu2hEbdDsfx5Mgy+8K7 zlc);69l;|XP4AN(G3dy*)D8+nPH*YOD`y(YE4tMrM(jSO^HN=E^hPVyYXI zAQGFzMV(1=U(BpXXQtb-@5dRQgQCJntwvitlz`4p!OS}!rj>}{ck*YZ7FMC=F%!rR z{6+u!sXWwQ?eBuy_s(pn9~oZuBGQ|1#OG6Z)n8fIVsdLzlE zh84L&kN^$}vz8p{L4Ab&VUJwRf!rf(5BpGzpt-*oG0YRRCcBkW;@|ku&j_gl2#v|B z9Uf|xm6*T;LF_0FY#CF^Z?65zb}(8cg%mqgX6!9UX($991c~8X>WB-+;UD?!r@QB| zTS^$EB{{9O5DdPOQkil~KZN`}v8FuVx)-!RGshtgYf=qn9=i_IR>!0vuL8@+1qHvw zOu-;@PBIz~!Vq(;r})*144Nu2-u`9oH~dzWRaoQP0f`}KCdr|ST^xB0m%?s8HXDwg znly~bK+ZsjrcrM#Aj;WUt%k-n8re(t|2Kn#sx9*r?8quLcq#*NRw5ycQ=qLL^IJ#$ ze{=X!yMtb^2+lFRk^_cApa4;I91;RRtUHz?OZ(kD$G*8MDqrtlw?Tj)CTSlaL=dbB zA+_bR=|qYK-3tqKH6Qf^^?F;5sI&w0z-=51frC=)ekEcCl@%aL$`ZLxi_5UTmBSf| zbF|SakaXGsSy^1dnn#g2gWJcyt+#SD;qnk-y(GQJ6S2V|A`BozJ9^>Me_c7fqma!?c6f=oH|4R?!MsU; zqUgrOC01*>hd4)rkPaF&s2BTZDxiBf= z+@DpeH<^{?+r#O@pI;KEou8fF=r5NYhR!ki&&7&fWb}zX`nB#fa_6C2F$V~Y5gdql zuGG-%oW?*rYv*taOyxTVM6>i!I%9i#+H=OO6;Wbx0MlG&89hKJXx*sK68_O$!R|#1 zrQSP=vC+Ol4IQpE4x3?-0_VI8f6l%kZTJ$#y%L}y!ZA*!nzoRDQ8X3n-`IEzCt-jDUGsND4amVgcYLLt}rTD zo814mYeGwPFEM8Z*DaxkgYLSn1Mvs2U$-V>)N0&YKcuCQ>jQ1*LDo8a2aXP6O+FxoOK(F+`X13WL+hFRW=v## z=dEmR7kyke8k#|`y0ZLWMlHN2v-&-`4mZwy4fz&}84Zay`M2=KMoz!Wij%q}XR79=$UwSGL)l#kXIxGXAPG!oOWHwaM^k#!)O2957>kz!)GUk4*fgs2NYhD{ehpu zm}rGO`^a?nkJfAY^yhN84XE^7*?VwF9~p>Sqt~T>>AGEgc>Edo1^Q2$?p-;Mh;x{^ zSv{G&42SNwQuW!o^Uq!xT11BrH}16Az9C8W1Lx9t1uFY%rG}XEo}(-E_Y3EUs0AX+5>AOdv}+ z;jw8F6xUU!UWs$Z4R6nqv+qM_QQ!=SNkX7j%y%1<88Na0rLVnyOqJ=2)exqQ^!G zB`pOi#mJbBKq9ax!hk&AAj0uCF~R6aY9Ur3gD2jtmB?8owMby(d*?@`B=vVHp`zZ_ zxh!ROsh!Oi5ak_AGO>jUONzuo-j5h82cn!~on_7J zlNNhbWCnZ`)uMmkkg)xigbRulAbccU3>^PsPn)5t_T?zjIgR35>a~amD)t}&1*NFl z5hmvEkj?9S{hq8K=EGHi%2Wc$166nKnqwMN+ld^MpI!s;BsGegG zmU_x3%!8CTur?42wuwz2%kvh;e}83{r=5}H?8W4nS0(S*xP$Z5ZH>2S#XtUqtFQfhoZ?=p`8&z@22#8n%*2s{CI38*&_s$i|A;I5!WmCVJG+r<)W=uL^O$)GSWZzm3*nm9A=@oejr7>Bu+$H=V+KcEGMAU? zb)mFtr=~`)2ul*OR@4ItS376wO}I7qdwMeqzpS#Aj&k}YLI(rFp`juz0cmH`5Q`nF zVg2F_MsTd{XFl{#*Xr7B+%))e7%(XcB1;A_pnEVv2TLC64^}zPW5$KKUQnem%7mF5 zXfR50(98|kV>2XT)&1t5+x57wJt|_Lt)=62pRfj#7EV1Bffb7|N57%F$k}W(T5BOK#rA325<-pOs@=z7%f%@>hbtg`THMH0&ZT6 z24sjh7_JDCV0mMJV+0+2VfsdYcS@FD`W3H%SV%Qd9SUHi0ZYVS|3T)G)YOkXXYcn< z%ejrn%>YCZ?v1-JSS##MpwUJOfMQW)-a3BixGK&DY6!EiPe>dHR4FL5;6$mKb^km0 z*8rvv5CZ@N05dcLFaTg*WM|Z90R7!wLG@dJwt;Yo?4m+sa<`Fyq#gv5+S58YY#koo zS@Q3IfEfcKs(`yF08<9t=Of7YHnkbI1MR{GzIbxJ9c&kuLCeBvU~&tVz!P|}CH`+* z<(~ij73a5Y&6<1~*!k?YCToJ?yt_`9%CcXtqet1cU%QSNoZ;NaF;N8j#i`@Pv(Q5lIO;Ai-2a0AWNWf|+gs(lCmBocrK z_+bnJJqrj8mq-{$Pny1e{m_MftbY9~AAGWfKXw25^E;o*(9Ooi(^lnr=XpBZ8N96S z%1vgDUh|m9viJwBf4%ysFXrA`^gpq#QNrn`y}qV%h3ccX>CmG(ho8FjTWvjGU-6$+ zJ&%oWFBEVE4qRFmmT3dmgw7&}Rh2*H&g*(#_3Fy|deaeh{xOCZ{WJ9cu`6FoBfs_V z>3hF(^(=GY?+d@!SAFT#7{`C~)j2iZ)jHwHUu$`3+G_>ni4QuG6|o8>M#9xGI6|!g zQ?ePdqLK`e3e($VCr9XP2z}a=ZCRqpaqy9ZOQ@(&r4^)3wcIecGmDOBaeB~c!W*g~ zM4s6a0JEZS6LHr;HCVU0-H`Kw0eYGvJAafk0Kj&lQlX0}?q_R3?*CK{XPuwd){t1RIXec&0OzzFn+xl&qriY{+Ml)o~Gt@#OU;0=vVf zE|SJjC1x?d+Z&PTkxFLI8rL()t%ho1?Rv0!;sAVPV_g}gGQ5&g@3Pbuq}9Aoaqc## zmc_(uO)O=aa6H0%0g?bwnTKr}Bdtbz1f*inH8NV z(=e{U+m(gEG(e;eNUer90%% zylV{wXU-v*8_hqX^SUs@Q8L|AjTP0%Sg?cw-v;%O%gu+WN61_>6&ok zd~XS!A(7_aonspx8Jrr3q7vdVto&pxy;eSEb$4EsFCdewxKX@UnHV5OT%xUzU}HL7 zV#kJY)Sxk4@0p`H`wCL1U=+=ilyMmGXn@ST@(V*D&7s;dZPcvtbTo6TJ}z_q!n8(d zCfH34I3VenwfJ%Gx%*dwQzn6*TbrFAoAZ@gKX{7A%(M;wi55{=QUncw_AX0*SsS(L zr(|bV8M*rs2eVPuO?wkG@s8dxYkQRJ(zzN>T&tMLN)P6sL(dBf2PXzmlVL zsx3$fyz21OV)ocZZfmHiM`6m%rSny26mGfcz6x@>?^Lb&gqqrSPj;x1QRA9$fu9XCSBGlqT^;+2QbVTtjG))5|L# zvb#n&apGI2;h7=W&~xlQd; zTZP>Oy|nCCoo2X4xwi;bD)I!uR*zvb{5!{J^Y$+k=o zQ~1-5lj=#md3Qc46k=iN>2ge#F0Zl4ii3_po|HB;tt3IIswo3gTu2l0EP7l^Og! z@=13X_0}sCYyRm+F~NHjb(FGcbqEYTtQOXb9#iI>m(wT9Ngn;1@ryfs3mGb+<>H=Y zAZSFB;oeu&PxX48I^#F}dVVhT!}%QSKK?+jw1p65ZTTX*y3ZTEdS9pdsN>QjcROHg zv-(1o4dc5lY2S+j?z|)(cV-+&)hk&>6EY2d@)yM5EUD$>hL z_pZpPH=@tvC|q!5bpz~D;$z1sCJ|;x8f;e^kNZthU0^Y+xg{KJwv}y5cT=P3Q(sr( z7rEN`IeOaISh@LmTH5&D7*?iMmc4}jVd)g@I@a;RRhizp7x1hc6s~xaG)b_ck$n)3O!2-+_CoY3_Pw-zdL7n=_2F%)_Exx-O-U9ij zB+7WyR-hD0dCU3Py{iIaOZGdOy0V&#w#>-UPLZY?u{s@74*63e-aoe|mq3BY?=8i8 zBgh9VnYzik8V+9fyjSIZx~;@GzUyAP33;8THqGwiL>{pm)TaoW#&EKqwphXMFfP7q z+vQX!ja{@gBtv8zkoI)P74})#eJ&8X@+FhX`zzfxYSx8Z^-A4uTIYv0U=i6EX1=td zo*~29;pI{yNpLGn_~ywraYGpd0?n8fH{3uO!)9=$P^_jL? zC0z}>i3W<||Cj8#*ZrKJuCMj=uvD*Ma4qlWo0A?&W6}0@@Y>{g1>tcAkoi$qo!6+q7@zPYpt<6CV;?=Gca-gzI&6yjCjX2@rph`B{xu69RC z+}cO_Bz^z{Y?Bc0m6;-|*cWj+E=)e;Sf#k}EJHypo5e0+NO?cllo9nN<>1_0r!8Wt zy9+5m4DPQTQuxJS1Z7p#lOpero4I6T0%b_c#+k05qM!B+5rVfEadeVLTzbqY-)rgs^a-p|8=iWZPLJKh)i-Btqa_b-7Bx_czg*k7f{h^qx{#uH(HHZ*HGlk;luu+E*8`C zz0gj1CWr>g$?Tjkzi^hBYs{5)|$ z@)F%?_lvwY!b|-%bJN1c?a!cQi|@*CbegyTcw9Db*L6qI<4*i=O=dgsE|<41u2^j5 zwAP*(#ZvgP?sm27^*)1HoWbNHpASvJ$8QM)9dUM^qVnLuD5d2xcgmp+!<;wvvumm) z(DMQ{k-T&x_7ov;FfDOx4vC|({rgmQ9>8HQa9~%#5m#M4@Mc6QU&CLfd{9rRM|#vA zm0hM1ka#^tdT_qj)gC^@kixwWcJ+hL_M=j$)GGB#JyMUQvm2csJW5P9r9nEdZ{OR;HRDbsZTxBGd=S&PyW=Vp44M{%#WEdQ+Dd7opG=mBx4WRx>!>B&eFltXU49b%YgYG25pgKt~s7}%hYQ#OD;`YP-CiN&i zj0i)+pfKJHO2a*%0N%sOFFg$S!o#31*bGX+KTvq`VPzK|26Q1|P#5wBCD32_LzC?LgFGdE!tR70Q!qKYI*A8PxutiBJ zzkW;(lIfm{n`I&X{qaV z{5bdI+}o6tl2JhxBe*4Nt_6toOSF>jIT8^$BUNNi(uGowj#g7-M^H~`6r6h%)*}Kp z6d#NmU<6r+BTtFlVEWk}U9O(Op850_-ScVC-*xqAeU%<#urv@Ts1+RtX>1u5IF1#* zJ~;v!S*D)xqxADReZd_mnFdlFI79HY>c-sdxX>tIg{y+?Z*!A#G^fA5?3Zry2$gXf zk5@`-7$ATIW{$y86)miVVpp`f^5?&bcw{br>BE&qBI|`7rY{+=1P&kvf?Ts%gQisJ zz6n;xY?YyC|C`0=jr!@HS*4Hpr}38X_Zm$~f;dyUEO8hl@Gn%%{t;xvD{AAX0=A*ha(-^)+EF3gF#5yB_PzH=W zIC!I7(Hmk#zTF3f_nRet_uL^s)e_H@l8dC!X^cYG8Ys0`8G(vJqHVB*+?&;T~<0yRvkm70(wTtX#%S zDzi`CMz)>N4q9J|ARZjCM>r5^z=cl{yE4}8t-{)w`;nO*7X#Tpu$qRdt7(ARni1d{ z0|SjKEuLpT2;Z-fG^LN!e>7KiS$`2wKN}XnPej_drD`;u@>GD`6hE15? ze>jSS6*UK0tSW3BSghwL;ae_gkxvUpFEb-&@D%drngS4lP9!ZH8zD5XYOjq1feVbX zEtAeJJ7qISX}wT!>XnAbktuj;ZRkW0RwM3zUYk1Xd8=8|#?a4<%B-BNj3uZF3^|ry z2h*C^YbVg8TC0N4D;WGtD6gEQ9-P-ONEGX(fnSLPj0bEGQ8iU%>N@goX_43IQYDBL z79nQ_p%OT>)%px5!t!8B-;>MjwPfA<<_)w&Aw7h0!jAy~dlY2-1_7Fdvq264!#n^Y zxD7E$P#j~lWvl6I6KO9835%^NFk}{HI7dcP;l`T;%Mt+{^JRSJd+8=@+v|F$w(D41XvFy z%4s?BW!ch)UxglYRlz9_sv$H)*#aOXEyghcG|-a#D<8M4MU^{v4L$<~+9Eys9C1Dgoi@5C~)` zhO#GOiWV8w2G1+T0^ia`w>WCb{rrD_Jbk}bAsMv<%DJ8i1+wFn8VDqrQGx^OeAd+U zSwrr1bO5e3@k1hEi10xWa`iKVM*lU{0{!89+^x=o9P{(PVT}bS3nRgx8!R%&z{nXU zt(v{GvgLO;I2`&tAX-R_byP{Lf?*6b)pIXxSeJLZ$nSllxgSu~0&XoH8@l3a$xYA$ zb8y(agI(%PR{go}({>A=kW3MHSEEce#+A8Jgl3F&nGodSMi!QDlSt+jPZix4TD9XmiM5M3iOT&$N?C*X@G}iZ z!9?TWddvbiR!hyzZ*qb%b4qv==MN6XOSI$&7J9VA>z#-2TDD1%?d@Ww*f_W|qgK!q6jYl`*eAd*7-9chUVKnLT`Nz< z1_Z#O5DFx!{$rSG2?8sv34pKQA2(X}J-azMQQC4OhGbj>6{seGrpA?Lxkg&=S0~E| zzX*`VF_2=pvMQB&44$KewTXU`5NWxxi4@YSLPQA3fJ#VB?yu z_)Z3Gj=$qWOEmTZdrmQ>B5-g6jR>S2B~7oWgwPRzj~->XNrR?0w1I14&SfkhXhP(( z-lmk2a2(fm6<1;`_eMMBa!N2Y60z3Enqfur1`rS$TVK)zAzMOdurXAIT!M_mD!nhj zAUqypi;^t9BnU`bx^u9rbXYka*^Qi%IHa?3>{7lP29Yr;Vg}WKC||czm&tu-jGNMK!Hs)f$dmUt;ej@ZB`CZ(TEh_j&;SYt#r4rjU8ft5o#X}(09syd(Rrj3)^ zOfarQQYI!B21Hobs~Bt(t2Gw)H4NtYIr|ukg$!JPrxh(6YT*V6zM2@6gC@2M{QrU8 zzITR~{L51n{o(zB9Obl(Q?cZI&5&6ZBVWVbU=&!tr_?K<(0}HaWKt50a8?Y>Etpun zX0Z$)ktm^e<6~sG8V5@;i=ThOO&AG?!8&Co7Riy)TNw*cn2Q(yVCKbf!2Di~zv*mZ z=`UxI$qKkkeSjiLK_D^_!xJu&UY|TUKwcfIDOc&gqO_4qITS0N@iGknl0B3lF0GwY zVO^0#m*ty5sB&Zg*`9DblYvGDRb@3nwVwpwGS@f6wdgl=&Zjysczeu6P!^#{pQ|h2 zDC(`WBsnu9T#@!&=@O?vf_P(Ldz?A|%fDYMF#U9NFd-|U*N^`2X@hVsZ@D9C~U0L%c<%mIM5 zna#TW8ZaO>1qsZJ0lS-bcXpmum96Pbx6)*z+X%K^h}z*h3SPUd%~hjd5dZ%5-+PDl zPM1EaR^dN=`g9L}`t3`pyZ7zBJT`WAR%cEhN2Mdqo%+nj`KJ#b{MlD){*}Ro)_x#3^KKgLr|NHgF9{W>g|GDgn=p<9JOHnb@cX_y6oOzxn9T3Z~a-q(&uU5^Y?QOOk3^OG?eusZOJ; z-|d)jYgH$wPX!-X%b_EZr;<93hb^!NLUtzJ{k3Nutu5Xe6z5o-QWg@)u;KTqW~gMl zv(Mqp){a9X*Ys$3O6XIRsz-)11)xv`|3TifpWeMdoyKXFR;olb46F=Z_e!xyd-I{@ zx7pfk>P(}gg+1G*J0+sQ`d#%ct!o1b26i5F{OoOu_glAOTIw~h4oP=FTU&8V5!MJ} z2zDZc{_O0a_?>agXYVXGfD%BRA64II?`|>M8G8Ti-I|W|zr%sbPSDQw-JhlqG-{@` zn{2!A-MQv*;N{s2tnJhj-Ej*~!x%xwmXn0%*y>2M*9`*Ty-e*|1ZKORekKZ1o)Q0a zxRY4t`|oO$jl33NK0_k-A*8~c(&wF+PNjBmu~q)-j$PY6AgQN7mHF5243oDwOR@AT ztjXMn*QqmiC80`X-2-WRH?vU9wo2*Gov{;~?N1Uqmf2#v)$k_MxT)t`+Q&edp+_a| zCUrKAJSMaT@y_ad%-3h2{dRLoo>BoH12s@Cg0U~Y*B9Po1ccLw3tXD$&CSC3i)iU90y_1n#c(t1Fw{WDy$0Z#S8zzq5h2nqsJ`P42$&6Q>asbgde$m+~_uZ`aKPcrdoR?oWr!>24Tw0Y;4S zx6}}Rw|&!jlXWiMr+}}6vCV$I)3oQ}Bk1^-Ew{`}b=GCQo4P## zt6_7V=@`s_Z=8&LjRKtiK5MqdV_mTY2$pKsR_rvtx|(STyuV`B39Z+kVK-aOJnfd! zkd~4t`xNT0;hEbHRGxykNHhaynReXkN{Xnz1phHzMDYjDcrm2$tufPJ?)u(MzX^NUX9Z8%6CZhCljsh0Pk780A6{= z*=z(g!_M<*#7&a%--Tw3m#l0J#ZEIdJF>x4?{zw#{$u$$GwDhXZfPD}6<+;M$`h4b zawDIouT`67mY&;hwdvO>f1d1A0vZi+P!^N^z6qPxR!M>wUqcJM8Ma%EvFzAgd-gY` zjDk`spSDRLTedAaxM`_dM)pdU9pW>I)0T!{H`te>zFF)A!12>*(;v)dmp|`IZWuL0H^&#V-6dG~ zU*+Uk2v5B>I9p2tz28-sif}R-NeRY-JzGztrOV%2T9VcCd(MAn zR&eO6W7gj6*;fHF%my^1=tV3h-1y#u#fELo7p;9B)?}8d%pw#@s!_my_cVMLIMd5W z_bdyk2`hK9Ie&%9#*aPH;2 zg?%s$(Z-lM!!dSoqGbkI&W2|f7N*8q^?uxz?NgXZA}tCU^Hliw7Qb&k(~9Trytl6P z4t=xt&%##`$9u}}>TAifKlEGNIquvu>2fm}I$YIH-x@n0ebE1pzeGUX>lqJGn0#^P zB3B54nFq&p5`7+Y&C-!BoL|IM;fkSGz>l0>cJ0${+mRbH*bRttO3b@0oGwxk7R#@oZnc|rB?HkXt9T4XV;+aQ*AP`g370O2Fs{xZU z9T5s`tV8-{=6RtcyN712BzMCRbvLHO{jk>ain4B4R)VKXkS9J_eZ#^GsY9t|5^-U@ z7Pz%x)HfnZ?6QvfofmA!!k@sSL&(r8oI7q-_iUu6IrE2uW2*nU?1hbGVjD8y|L6gw zck9W=&Cz0&n%pX zVB;6aA@gs+J^JNay+770wYa5a&<&;2CVCmLne@w_!!s3$gS9T(96XOZ{v~Co_ zMJ=5r#)=!}yngGqF|sm4`mtpnWqle$Le&Sj~by#(e<&Ty#OhX)mKZ;qYq;2bIDOI|Z?Ip`NB8;QD&p0D(OKuM$3| z@@X6YO{^jQB7U$}q5iA_!KPVw%{>VKeMRqDBR}`T@NH#x+S^QQ!MF2niHJU~aA|;A z_y62{@w|4Bf5xKt+F#tL&4}7RNh8EuR8-V>q@Vh8{USngEq!aPq`~ci%1N*0aR7#M1-25Jn5&P#CdJ zV_WHViMj8so6^NU#+4D*P_-h(xdHuby{&KRk+XFDxqTb~x+bu$?FFdQv$1oc2Csq? znWpPb)$t*D&2WKeG(EJ{(mnuIMhEvVDhKmPH^2B7XKDJ1x;6>!_FnsL@Lp}JH_6e) z$8+fDcH|5cjrpwu>m&xcm6Kvc3w;GpZDaK@fWKahaE#2qtwW#&@P0)iaBmUP;NKk| zR@R9484-i{kA^pQW0O(i>U32ddPTjB!e2Ps0s_n0!rf?L2+VtqQax-glN}&=?S6xu zmHKN339pqGgCBF_x>#SS$^q7=fh&%|Bet*%?ScNk7NoF&S7Nzwd>Msr7exM}aOzC7 z#d~pnv$r69UA!V99>dDCAW&6^8IL}2eU-_MSXs~B3F9up64KW*BO)u1@qTSBHR^;--cw7As^%&c9vhK!~Cw?v|rmh zdw8pOc0L0V&nmz1>j@ROiFOihD1h@Pp))5}1!Fzi4Q=uiN8Y$D1~h+c|3^I+3i$LC z3ijO5Fe#4MS#C%UOw6m6^|RF@&A7r`3Txz3;Lfjm>r#C~?E&E#hK}(8AKYbjvxq+2 zVpbgHOLkF6@FVBvTWG_x!IGYJu>6$Slj4AD%rWFpH&@)t`GMvMyR`BdAybh#?d`&A zoGyNYA(q8PSQx%V2D#UkCNS{xCo@+^=iyA7Mg6EY-Tfo*dt@3nZGfV+dt$-IG}~JB z$=xy)eL>^>{dU=4!>$LPaBEv`w2)ErG>pKK^gys2QGF#LMquI8Wo#$xRp*$F&=iN{+|f%O*<)X+mD^ko0-YP zRUTqRKirD?l+wi?b!xbyjYICP=mp<7FU%FK$mHc;W;Ao|9GHDtAAn@+_csz)Ie#?f zBigWgUmK+>3Eu8c;`mkx2ZQMM^^!TcE5@o--who5!eK#oSys3opg+sLymmq~pQu@x z&GR3AlPp|>Bl(O?AJ#$tz1Y8_}KT%R$=1 zkB`w;B)pKVP`$~BO^3QIEQX!=EU^!iY491E%d&mK)uR3-hs}6iehWWK$EA0yY<@wq z=TQd3!yEe|lVD`}{zm8pG}AY>8JmiiG)u;W;Yw3pvkk*fDL>rj2%XHyMs) zVgdCe$w(UEe*Q{Jb9uZkg%IF5!L^vf6aW#RdF;@bTnyalwn~weeiRya&ce0QhVIjtNpMJqm7WNsJBEdj~wGUPijl5)bq`S zk@!k9hlyvdoR*&{zn_2g?HxOpKGwB$?8&ox=Wy!`J5^M3ZRd80*yR>99cI#`A)>h9 zclLiDQE=%rUtB^rAsUE@w9nTj2M+U6ThG@JF7L#BXMo8+Ud0tMqPKC-jPRRMxetzo z9klHH$eH#Xy4xKGJQQ!*D@}kNVtD*?6-%+$2_Ju~F#(osmag&3v+=fl zMk%NWi|NB=yPj?MHlI%zE@Hd3_`CVwax(z7$qDj<(Z*9%R~ye# z3=J{6KwB!GP<(-(=~RDz|H6WZ>xZpZ1+94{C?X-tz4Mj%I4X5lm>%WmYy#rt9;%w z4YEBc+xQoJ%n-)>GNIBrjM*lxgB5aP8^tXj{l5d9t^W2v9f$jvI}flnl~N3jkF`Tl zERGMGMnrvX3e&Uw>d%@SgN}c#vK8jok_(qo1|0_-%|r*@=G+Ohy#;}cg@?ewd(wt z`BW23T$)3Z3$h6mgi(}56BcCh^xMB8VmfmS0sUM}ikIa{aVs^QbJDiTyd3c zXMT!RiZ-MEmrg#dIaVAP*^y)*G)Q!=jzAHZJT-W4>{1%H(@Q5W8=o7fGIs*0p&%u3 z%P2a|Lmj_L_7a9y-|f5qx7=6H7Fqn**slINcN-(rH_WeCvJ#j9!4i^G;bN7#2e!C_ z0eO|1d}eaaecWvIqxpB#lI^9As|<eJgV6e*2^NQ_ciFcM7H1U=WF6Cw)xi(+v z(PSu$h_9H(!?tlWBevvnIbKVjVh-X*cSDf-c!VfRFY&|>d9l@mX>g(#Qq8Y}CaCYv zBuSP4q8KWAU|Ixi|03V)FFYd4h_BAKLdfrZ@!Vh`${86=nEM^ zR;ec`LfVosl5p1cY83wjKXdx+)ExmRAl^ZoXrchEvn}XbL}}D7BzOre|UGZwG0r6K*UVi(|mz4kmewQfiD$0;2!wpN`MYjHP!2yf_o9 z=PHO9N6#&fCA9g3n5V%I;5{{LF&d&S;4dY_LP@F&0zso<;;KsvFi zIq4FOB9dytzYX_u@riv$l`AeJ86fdRtXQdw83Ke;4ye$8uN&({i~keH-5>QH`u11( z0A4_$zcqBjz27p?K#H|>#9*2X_YSD#>k8o1!3%4E6V7_@0siJmTtZ5+DU`X0d<)5e z9TQuPyje~K-6{EbPMu@7wyzjZPbs(x5}hJ~C&-b(d#m{7vk*bv<5T9a;*od^9neMW-`m>#dWiqUEylp7}Klc+LYzF!s( z`g8uQN%J8BtQA?MqcWT&16?deMLP(7d-unAi9RYyfpG(|4aE?0B(_TK63QryuifGJ zK03~dNq5CvsD}t?$fe0yO3+Xw0^n=Esk=DD{HDkITwD`vP&6bZU=m7@PsW9lZAhoe zg8v~7DPb|W=9s9gQLQSpn^ufDMf*3MUW7@J1&gf10T60xq|skS)r9Q4Rz; zzjW!z%>Ll1E?`J;={LWFybWnsZn{QI!ayfHXTrhQ6f`f&8c{4fx!N_=75LVh zW9OCcF(fk$Dq@6eC&n+C8%-8S!q?zQcu1P!Z_vM&NJj+}+^Owb{~NVsqUI)!nb7hB zf@c#tJS73)m_dxe^IG6Ny!FAEC-rrc?#x$an@7L(_0x#_W`+l<40%5yJ~?x~G+pBw z=rGcIpxN#WSllnrz;@g(lKxlc#SCOJBNFjr$&_iah?|W|3jbHx_7i^m*Il45H zgu>DiNp)C^gjfq}PLvtu5yK7iQ&wx1>=KON)+9tF1ZgFYCb>HiXH+h>NBOQ{wiODZ){Y7fN7`lT5L-2R!_p58S^E(0uM$cJS2q}OuFUn3VYlUa%q z-vQwb&w9H&RDE!%B#47hxHBgipJ@RUCCx^~U*b(0wzyL845p5gv6d+w!oWb+EPGjW zA{0PhtH6`DmzkydAC?L4_L+Tnsbrxv0}@M}JqRe-6Fb_qbeJmEHe`z~2Gaq;-GJ8Cc zplHFvgfXX#C^jPV-#y8LM`mOU^ zYR1v3Kjz2R5iyKAf-+szl0q=_acJdz^HvOUUlKuh7FJL@dI;ENs3*15{1VO-yA@LqAVkmMX(vp^)`#9?F=3$^`yvGnMCfS-5*zqzg;Zz!N zEe|`EH~yVV?aaXmsmX(FaS|9xMAVn;f{j2eQH~@te&t8#<#V{9)?UcG(vg$#dDad^ zAUqYy@&U`d$}4{uBU=T1Manf{+Py<729{0Hnuc`I;1nHKk5oDEGHImcg6(pa*Lo!w zd9Oo?Ao61*HiMY#-LTKoWVCD( z^_~l+LL;97=Rn#Sa>;!;snETn80AJOmSblkprO!z-pGlwzp^WR1m!(rf-RL|2n;su zL5zcokd9mk@nU^p;^7;dRM`Y2uPp!vu(=#_tz@vpiSw~RTQ?-MEEQB+IERYLgd~cT z0|=v5J6OJDZ^DWKsrw7wRP9&c7!J8#y_u5qXgtf)foxqnpCYoUOWh?KW*n86Ev2PBW{h8Dp*h<|H* z_9DQcB=)iJp%Mo~jWfM>d*9Nd%g8>!S-kmf`s>-vfl^$F@`Qnv-HP)o{Ws_Avj1&kG&Rps-}S=Ol*~S zk!_-1WS#A&WSg`kr5;ZmGy?&BmL!q`<0hDs}bpYzw!T zM3ib)0_^C3?y0h%C*#Op&G*-NJP)FgtCbhV$WY5QB{^XZ71xeMaC!MK`sKOV{@=DR zyzrN;x`?1TdQ!r`!n7nN`o$zESaH2>^Y{I7@hou=0+o(DGP9(;S+K1cNs(6)O3`Lq z`Qr(kiCKYa(T|!mJ-Fd(<{HQ~B{`J6WLjarXSFvgW*VJvMFv0b+~AAw60}jth*VXk z_RX(7&V{T-5C55|K)rX0kC-nt07Tg;xWmo{oNs3|4AleqM^r+0ljFbaq0)6y}Ui(Ae*c6pI$e^Jn zbT8m4p7q#3rmi(EA?CewByn2msc1Nh#Jah4vq})-JP`JfSD|E?VQ)ooVrTV`%C)88 zH5hpjMMEnU*^=))V`tNxOf(2-vc2#?S?)pPCq*6tI|(_2n`-?uACb{KdI7^7eiI5U zV?sHRGft?YemUEPH@C`Sb2@g+ODaKv2T1J6{|}t7YH^fM@sK~&lDG@&qu(zdW+or70zoup*3qmHgg5Mv&wQxO%GUiAl1 zzuV%{@;VTnR7t6sab<4GOT*9xrxMS!)+s8zDz^dDil611ZJXJ8%cw-)Yh-uiMd+z%0el0B zh8_~Yvr;qg;(LPvj30Xr zY2(2a0a~Fa6=C48B;<0))!rf*xiyVZgA;89lm-u+B`A`_RbvYd_c8Q;fpK=a5rl0OFCO=hBRlfu$V9@HlUlttx*veK=R^tgjvgVGsS?E8s z0;Ez=-j+Q!f!k#;*+y zhtLUq2Xr29Bn6NbeDW~ z*5*1pAuTuGHN2@%Osxi>xP<3J>)yOz$c#2H=h^Y!$h-FOV4mkMiU%$Umw46>P_3Jb zVpjhB1Wn&fy%3(gYY1MSr7&U4!sCyyQIZ2H`qJ;`FBg-iXU^rhZwe<*^wPKMAdNu$ zr_2@@5xAG)(7{Gr7)nYUa!2XdF*4GUGW3#4EF?Z@Fi;|%{}njicEGviaMbV3!`n_; z;8Owh3>Y=0OY6@66w`5`_m1W=hZEpkkOUnB((84Nu>_RFBQURz=C2t$mLV*D8-nOKTq@T4_1ow zZ_e0TIrd`x;*8JnK|(p|Cy?_h_2hw_VnC_bU>#G=`>;lr{&P!i^XUHhc6erH`eYNi z>56;5Qih7JLJ$YgPdq^bf{0?0l{_QHuLl#@x*LC88RXfYAd_gAA9=*GQWBC}g@gAH z)d8O6Tiy9yZhhIN&-_Futj_7N6B&g@!6l5WL{P4#usxvq5D)_Z1OPKb12zC)Z)9hb z5CG+KZ5Z3Pv~QXNas<>y`qiFH(MSMVe-IS4NegYg38eqdlz#vS&!^0WZSOesrP(9|VMN;k)=Qo=fNf1iS%W0K@JxH`%*?<}qA%DM(3TaZ^(v zf}j|dJDqD&jgF^0LTaMpQ*CLiwo}qDLF|}GqL~hHA%Flf001;HP(*eB{eQ{bFl}H< z}{N-}xg?`IUJ1e>tZ^I`;k$(XYel^y`=Y&rABG=nNfw&@Dx0ho1D%x@aHwKG)EX z+4S_zkG+>Gi-C`u!OO$K&B@5h!^OnS#K*+T%fQOWn*A3e?VkNarC;k~|E{W0;TXXkbbGB8y3KMj)8AX1bLfok{(38}#+NK>q|G|-N$>Ri&` z>D`%gnW$!7u)6HdtrP1o(juJK*zu*mk5-LZwkFbGdlFRGWHCskVhdRnWmQNKC(~7d zR-PbQ-uVHnZU%Jsop( z+|x#CySYTECA!G0((j+O^`tr%K&Rf~`gBk^8U3m$56}p)NHk|W8&Uxk3p*-ItvqOH+LQCeTKedLq4(pp>U)fM zwO^xKX%Rl(q?IcWnbh-OSTCK{l`VVJ)1&6uE~t&0$!1h4q%Y^we6rXl7W?P4GuZhg zck%~Wv^Qe_RLWt^F5{@2JTr2;>n|({B+h$FlJZx zj{tDBU_=YoS(|p@swy*Yiw%=0x*LCCNp3@POIa`L6tsD=aRKz6wiVf->xzUyC-UlK z^s?F4Pj}ua_nSkkO@*Xbq)KzLSQH`(3xxFh)gp{ykCprWFWWyQNOM>F?n^y(zQ!K! zcmHjJdhYUrymj8ZE>;glo0o@+_m3ZZwg0eAwx+YNmRNd9(8aEBO*VUFrHt&cy=LWw z$I?_~@G^7fOd7m{JBJc68$8u%j6sQj3>+)(hM*PNM=1{mhn39K!pWY%+#EWreiO6I za&ky0IVP?|vQ=J{x;{exZO>_EN}oa2ZVm$R;c~Q^`qJ2o6`&s1cD$=BDUGAc=$+`= ze*(6bnLl%VIUAs2+7yv?@vN;WR%Ok~Rr-A`?&hxGaj9)eWqZ%54k@powl9%m?Qw z^j~y&^D$hPIn7P6(hvu&yAbJJv~3#pnR~c0K0QZNyzq{HAS7(}(3dWY7en{w{F zU)d6^oe{u`S}(MgZ-HWZpj*JnCc@w9cZ2Yh(Hn(ClDh}~tMi^ixzeWvZ@=$s3AdMX zT?KRHi;|d0)}3}KYuHKaL0XUdY}C*8TxDcoTd>APpub!46J%M}_~bv`3cT~O_3#{{ zKe7fxZcIk9t z?u4#ybw&zN#L(oT)7!Wke1xdm8$+DlY?a2{2MZ)?=X5RG{}n`tF3~PQ2FUzyX2YF0 z;xB&4LzmB?>geQ46%URUUF#t=oeRERkQJ6i+w^12xL2by^|)jYoSqSZUV!T+91@-` z+Vd}|=QfFX0}bi$_8=&1!^)dokz>EODFj(%y)RNT_A}wRE(ugltwFB5_c%@v|Yqj^g_6v?$wh!=g+-! z;FDPW>pK`o?Ny7-S#99z;!5)TNVfSI*7`JrX^*&vRetyguWp`liTVynZ1dyzn>T&WCN;A*3NSn{b@pttlIU)p-G!z=2#?s1KUox+KQxTMluyHf=Fa)IZLvHH!*Z&^%2 zur$IV?^wEixqDDrGhgkh9BzJ+kkjRL=`Tpn90r;nZXkn+O-G=e^f_~|pednJ3 z|9Ae)KF&5~A8yOk$HT+I@ZYaFI!{AKMymflB^#~`wl)a0-I58B(6Pk7|552Jwj{00 zB+Pgn9N4<|jkqmry861NjXQ4?G+=MkA|F~yTktK`of3E#Zc5+pUXlOCr;)$#)s7OX zmi)@W&cVQ34ZFa1w(#oVyer^?ZpB9{Zc|IEHX~#eYbK2NKSJz>A(kIea?!ZBpgyXNB?b z1sQzv*9ma=7Cze?7c)pMzkgl`HYJId3=uTH;&wgV{WZGfXjO3oXDQ!+b3>T;7oLZdrcr%k7CrIv1M;s{ z0_vG-`zFZ_2fOyMnYh(M1=XpL++NU>5F|d+UVoO7GckjpOZ< z_9FW{aNHNZVEv>B8{a-Xi-lBs+i+Gpc^=~bZ5inIorVE;9ZWw-r=Mz2{y3aPyzu?{iD~ z8(Md(?^XPRwtnUc1p;_o)eP7PL=`YN#G^v^pV#yGb(@;q$MlsQ&$^hVcv?%I6>4nv zlg9njKk>^FXYlbhJFhRVbt@ya9eEG*39zSn0k=tMb$8|Z$RBFXyFy2)*Hegl75MSK z-KV5|$Gz8=d-FH_A7#$T=Wa1|YP2{wK!G=HO5($3Lig^wsQr|2B{$y}zmJOXKJHyc z&$E-0%Q3>kSgW^zddQuk9mdvca^!WDQmDm=35~G(GKP!YD4&>W(1oW>r&0B~Y01~^ z;sUk+oylj_l@aWGnE9XhSD~3myV8pmtl<`Aa3bVZ&>3l@c(=!BINRPI{1Y}0q0Zf^4H#@&Gx)L zduh|Bd$g5;mWBP5Ucn;zc)Y1+me38zo*uMz}NlWPtg_P5Pz)+aDVNo zl=lHgs%>K>Pgd<8LwzR~CY$i{gg}tiSzoXgTNRT7$bAv1CgZ~$ ze*TUqg<_}o!^L3#Rt3I>ck&RV_me;1-@dOURr_CX?#5R`|6d$t9QDO$0rMA zWoQ7q@5AW_qJ{`+81L>o?T0UO^xuc+{I!eE*HX`ly|BseZTW(vAKHx=1H(}pV+0Rg zOw9G4+yV_xNUNQfi&VW(xcS1iZQ!Fo=Ikc-*qygR!0o3pCAuK^%~ZIR&rgNK7;i2`Al=wCZ62Ym`&046k9>*sxY>6I zpv($?2Q*U<*Qdha4J!od2`O2Jk;)37j+a=?++^ZOtp<4|UGK#p`C1?+72?;%90RMu$I& z8VD60ro7X4%?9#A>xyhsBEaPPbU&*rAMYD7-Wk)AEzLV(X&CC=;Odm}s=K|a?86<% z_SL(QEl_MlzaZ_1jJ-lx*tlyYBszI~F&80_JiKb7nFyfl`HGbbxEg#)pQjvZ?! zzF%(=53jVoh&R{V3&w14O$$OwfyNu%H*ZSA6xWj?s-e1b_I47uZqHY*h)@Ikal46! zgu+5|%eaP%mzEqK<1>oG{;{j>9mEaXiR7p9fX(a^(tksu$%l5=ua;Vlerqe#LZ2_m z)@OCvy0QZQzF}4jjpIRXV8p?_-DTboK^*?GUZJmE#BJA({K&+nv@L3p)JQcw7q4I9 zEWruw{&A%wUoW`zUQR!WRu*q0f6o;Z@*VDJ-lg5t?(?r5y!>2DH(BtMQ3MfJKBvW* zTXmVxM`%#uz&4mxLhtuLzy0gwiJOqVR)ar~B{3xuG_h_)?$BdHlS3 zlL)7B35YSjH9pZrHySk6XS)3j7li=$;0vmIeH$}9GjCAi=p3Ctg`)p7OO$RC z{U^rXKb}{@Oke4%-vxiX9{@zcD^e)?dk;VqI-Qd@4&4{ zc?~DdJTU5!6c(-4b0(I-_+hsGn-^z~=vT$ETd(LEuEPrZOyicEma;s90ga)8C-~}0B7+6{O(-_QbS8iH~v%e;orW7K?y^f$$b13D(mNTQ?djSPd(5K(a zE9hwz#TJT2A}T08!`^5yNL&)hH?9`NPeO+ME=m6?Qp|0{fAe zL}Rt+q#zTTkXs&Ued$W=RQaO0O3SDiqkt|dQi_;Uim9#61o(9C_daCB$SEO^h*$-X zLQYm7(Yz!Pw_M(a&f)O)nY4lcESRU=l3Q zxKap#(}hQr6^yXG_hfHKi+@_1Q}QjE%3D)Vr%po@cY?UiZ?|<3rz2(KLB()C`siXoE(Y=??pgaeo=Gr*cNdD~TQx zwKVFTS91aDBHAx2GY;I38}k|u`j=@N)rkV6XVxyBd^d_Qqtyi#k|L;vu|i@GGA^sGxh?LB_5dn3SEprLi9-}DcbVT ztURQifP2h7a44*%(rOt1(Ca}GjA|UnD51`+xET&I#Nn!#0>r(Bk=`W+ITa~mO>JUJ zMP;Y0%bkTlOT*6{Wjw&Yrns}M@W&;jaK&alEvAa+429Y|w}0J57cY>@gqo0lIrRSA zN}t_GS&g$)y$GXX+PTQnF_nsOLj+ts!fQo^I=N4p_~tJekN%q-lE%x+OTTpr@E{$U zaGht?ZK+W~n;9NNo4w^W`{*Ezm8YRUg6+}Z*ju<)!j#|?i^nlkZcE2nJh>sweER)% zW`{LjEWJuP5C{TfGX-bXsy}Km-JC7rkbCoShJ8#^C6jB9;F4N9ETm9sQg?!$NunUb8z_WY_=|h%q5BBsEfT375%1X%W__LcWqRYFUHIjURLAPOEn4-Rv%{3&K=b z;DSpoRZ5Q2siqi#5kVv4%a`4TT7D*?09T?}YNH$Aw)12$Nl}Q=ZZFi!^}L;Vw;%qR z&^jbo40IfLBF{sLE`WL@^qQWjxI(y;*RHSSK@&I%oQX;()P=m#s{nL!hiLf;L z#HU2GkVn0N&*~IH>24Xw963U;0*@u8#Vk{x9t_|B*gN7oz0D|E(4jDpiz*Z}Fi2a= z4I3UB3wO|gUW3@({c|_As=QKUIdGPaw1Z(3k~%{vCBjZ(Fnc*^bR*`k41oiJc~oPm zQsR!JN-HEKr69+_8%jpV`q$aBp6Cpk#s=pZfj0Qa6`3nJLR3=BgG;69);yDZ5t9lB zLsVm|#1blC9y`HDJ1^u__vDj*n5BO7wNJg6P9B5j)XP)9pqZvc_nAI?5Nn7=bET1z z3S;Ni(U>S`4;YUUp&Q$|1yzAGltN0;xSD1~PAn?nh=4kRTZ+QZFXrO@Kte*CQ6#4j zf;5;4i_9by7|7%(BQGM4rtJhW2%85Il!1VfE~RHcrLt=T)l3!gp2%3@Kh#{Xbl?=! z(f%tUDrp2B4l@x`rimB~< zi&vw{gY{#iG7y%ISOimr)t)1QiZZt!OL&Eso0rA&S-P60a3pEiJkC_F783 z9x9Ticmxcw3ObAAk*3HoKC_MBaM`XwhsR1Zg2E)Ckck83EQS|Lk_dSVx3lwdWUVGa z*LhP1M;KC+pweN<4^nY}_qw98b`3N<2x0<-S9=WZTz2o0+ce{Xp#6jiRZ@uV=Cm>M!}LYk!5b&2T%_$b{02(w=}$yuQi-IF(po&kAfu=?_wL2 zQ)dgg+1R&A)${ycPjLcpkDE0IgNgE7sX)YGw4mOL4(%dBS)Sk>UB$L^Y59_Vi9k(x zA`sZx&#hQw9H_X>S>l^1oS_4e=^-02dP=P+$x$RWbs`{B5`HgQzi}@!HRe9Xz#Xyn z9YhlGDA!R8%Oi}UD_%br=ac75FEbJTs!W1BjP?)}sTf1Zvd91^27zcc=Zzm1qu>0( z%lsA}sub>FHk*Smau`^HR}2JI%^)D!5^nwv9phs7A5}dPFG@hJnx6`(aq&5I^CBn_C>SfK*b>?l;|1h*YJ00~6m5jGU^>9H zL8{J#JDI?!LQ+2jx2qd%BDjeJ*N$X0GG`w3&}OlfE3OdR z(t%20K^9^;OOVG9by?drm0uVuN^yBFA_8!+U@bgHPEm+TGlH^&)gPCaW1R?Fu&m%i zQNVG?*oh&sNo>b@+l~8gjWmAsy29veGI$%>qffE~MU)@Xs~$P-eC}2IHy0xdi!*Xb zBufw=LCrAgd!Q`F13X^8w3tn2-kaNwF*LF84 za`mzDnKM8Dudr1&hO}TZ5{813@fnFKc;u(G(quqNaSB-0C90VqG%tzG7#5q#wi%IJ zY5O@JiYDPoSc^lUgie&LrG`j^sm8(B}t>aL=Jd-Mar3y6l4X`rd;HhI0zQZKj;Uc3vktYO8`5t=HB#>Bwef-+2+09G1Zl1|3C4tYrm9)PaZ2O1%N3w=`d zViGium`Xvl7o*)%A~W#F&@nZNcfX1RZ9~9eG6snWVri9vu;4&}jn6d`ag=M`((bNf zi^K^lCjtmX1}#%KhJedjmqy;jrb2NaRzCEeQ>XTo4>dS!DJP41)FHcq(nif^tAW~4 zx#k2bpXW-$Jpo+LY8l-d35uY6Lk@n3n9A{`MN+Do~I(sUB_MWj)R?P-lu zHoT|2S%!1m*x=l-t_xDAXhlg)#aI*>Lhf5XP40cfW~_dFM^`9?5^oKttp!Tvq9_Q0 zlc=BvO`#GFxoVzD9u8ah4pcplQ7S1|<7&(Vg@K`X2*mdBY6GQF&dQ@fx)uiql$unS z#2g4IXtjOPx&A4`-_o%~9$UWh;0^F8%Xv8xb1DQ>=`-ZJC1F`AW?nhkL}E%E8j7ew zfy<}?%vgr0n^IfIeHpRISUUII`u_-o^I#AC#Y0C%izj1(auSfebthyYBCFHj?W4}^QSQ?matece&^f(i&!5f)S{=cu03gLqSl zpS*R|YU$#654pB5yROlyK2N7tZq~?IoVN!dyp|+WLE!73AE<*k$pjP^3cyli#d24Me8?aVA&-Aly!vyv{Jee^TrYsD+7~en2&pqX zhRVuBiFmchl7hkmcU6=6(w+S2uOaIHm0Z6|2;mMQbb?TC#iZCNrwRfRyGSg=?VZuD zvxKFrPdNs0QK8IAwL)8}C&3(hadI->&dj2Qadh@s+v!JF>fUa^G_K`qAy<1ifQmzj1kVE; zK!jP!qCb|w2%m(p1Wn2c0B{fx0{~P2GgAaM0ANp4<|q&V<#~4$yL#6Eia^pxwUm7& zOd|lF{ez=jtt-Pg9+6q2{`?Rq5&$70FrxwhMSx_W1bhP2SoU>*PIw8gKKAPa4^R-S zA3M9E6M zfVSL(85RU~x(WdeOrQV&%mC5I0YEmGz}O?nCXJ_)wFyZRce!@Ed#A#1>6M9Lwv=Oc zNd&o1QayXzI#DsMDrx^||NkQNt-V%KthY1IM2sJZfAr~F!}cDy(AQS&`aeg1+8bpL zKKZoY_s5Su`q0_o{`MH=ANFPGpL)gA&BJf~zW?`!o_;qz{CDU#eQDl~X~f(=`qDXC zJ@R()cK`DI(|ug`PK6e>c0SZ^^{Kiu_}H#qb!HxBR(9@BOkW0lhwM1+$h~JtNM|c& zf9vZ@dug9t{r_a2xC&8Nsi@j%r1Y$MoB*n{oPTZ%dsgQEda z$49)pnJRDJ@%3_X*7oxOTIv+TYEF|}gaELRQD|qY-;=)0DnR678hb;FN1*J__0f>p zK}bW}-ImKV(1tIkh;Q8toIISYMP1X+y{Co9R#H5rAZ$vuuN54xRdn)gg#pdi)XJ`1 zUOvNwvc*Sz0%r}FSY8=dnT2i3fcWdl`(fZ8vU9R9^?KX(hysL0T;J2p5dr%1SaYz$ zEgC(Pe!Fhl@&yKL3{ekXVXk2t?&cJ(A)%4z*C=gW57vvV7T*>gUUocIOtmjR^#1w- ze7($Eu-y;_98?K-i~s0k8`W4>K=BB_qy698JrydB9;Xt246CE9RBXbZ`nh%Hw|gGM z)o0wFj9fhIORR%unC;;sfHAVr#TIH#W&BX*We;&pOab4PNs!3>#g;O!Z>n$u==LWN zHWWw+|F12@PJMOst!;#3p?!aQkKh9dxi+(Kwmx%*txNaZAHL|c`17L{^AN^_wjlVe z5A{nvN*GT}lw;fVeEq)h-_Nga{f|BVe>8WmPQhn(Yprhvrdmc8t}Pfj-I>0Q6khOo zJdV6VRJChTk|T)ws|693Wx#GKq$N4qleykj+rXXA5oU!XnxFQ`m>|~EyPqov7s`FK zhKhmQm%blg*U9Snnpk6jkUSgso6FxhC?B3}_G#xL<6fW0i7$&>JX?5|m3nV2BNF7! zyK%*8Ca5M^YvJu-u~y7`Kj=&Cc5i$E<_TQJGKei|m&feyg-xD>L%==-05iN3$b9nN zJ+>J6)P_)5$rBQ5^3v>q6&nq-4>G&jy>S0De#bIO7q#;`+td=Z)~8*5T_Fi-l#3Y3y+M!v{NKxL!N{BbnSAaqFgpD(IK`0%8AKq z@tpeYP28Kfkl!j_M$NhI2t{;9+89DlMMs$Skwd08o;^12@^HVRrrOjc=|Hvig0f>< zKULaIE*)oTx0JLJ+1o$W^zA8BJL1N1fm1 zA7Sox`%h7gX6NN&9dbuzfE1-cT&Xt`)pbdne}id`tCwn}4f2lBNuD|TUy%nvv~OS^ zH5nGZef0M^W#bp+p_}PWeQu5ZwW&#Gzl)>M80IrBR`T{MxW?_&K?ENMl@7P5+Tz4E z98c_<_H3ZteY~Z5?I`AWE|8~VqM>^`0c~L`Ue#{gX-@rZZMb3%1uSnED}&q?jNEH~ z!6$n@-?8M{cmq#}AN_eE%<65*YE7~PweMiqSvl@*J|37y#-5YJ$;B1Uzvy`^k*2P- zDie+WB_XiZ!J=WfJUXKHn2(YnQ7uV8kX6#FJt5LK07SYiH}L7QB&PF8a^U=#*!x%&ee*-Z2UkIxJg&T6Y5J5wao0iO}qJfSwt=58%uir zi<8J^B@lxi$IqQ7<^l}Pv72 z-ftfKcVUgiV(c~f`1n@$t2=G+nr3X8@|#?4eFC&`T?+~M>wSkgQ-v23uOn!j`Lj2P z^_nz6(oLJ@V5{$LFgttIj_932!%^%0Pdt-;yk{M5UL9tv^i7h0Pp>hwuR%{A70XEd zwLjjM4=lBHjQUh|=%`z{Vptl|<0EeNtDl>TN4vb4D!yq~gDk z7tcYS+HZZ$*ax+c;nJSwd>i?15u5VY2+MmDPp481T%ace@-sU3D}fe&!wsEn4*zu9 zFgnsVUqv|Y$I`74720kf;XDLSj@fnvOs{!lT#$(~w!A-u9CkCUG&$=v<>%AA;@^a} zdhs3O8_J!W<{O|mrVY|}%fVN<9bc2F*f+b%FHJ0G`%ULyaZaztI$v>ee#Wl@ZXyeh z3h~7iQ5o;_4SclMpDAh=DVRTBnuNi~cGmA`y&c~~&v|8L5Mdj-(?oyEoeR=Eew|$Z zBkS(R?E`M2vZp$f-^DN<_`WWF^G#}}O4@|9<+V6v>*UywAC$I$y^A z;N5twKE4^&-J9>-=l1jaT)t5q!;{XmKrid%Xqt_*Zex!+btivTkRkRQ>>2Z3XM=N5 zUf;~SuwlWg9vo_M^>tC>@DJpUtFZ6I>@898lF>9v!-XjwhZU5;0G;xz9{!Df;KBIX z-8^{vK_)wx1apAHK}l>z~mt0#Ydr_f$r zD|Fr~Aa2MzN32jB+=CvE;22tIo@q+|)cx7tGmm|Xm+(Zy-uIi^LZFPbiO*S#N8th} zrdKVo>QdzXB~%~PzYSA^7Bh7;9Z9^pgw3=&{*}3jfLB(_LN7Voq%3)4vb z?}=++fDh#uBx(DbcnT?r!+=KEsBqw!Kbc>fw00YO{pav^sYWU2*%h&BqeVyx^7zYc z?$_3i^k(2Z{G8)Z`287t(N#6Y34=EkALSSr_CS3PCwiW0N{+4qiTLnx!S`pCW31~< z8S&^L5)6HvDYF*>2Iu!~uhapRCTg&3_e9=zV=jCbZ~JGH zeT@$?tsf!{mc?ZUO3o;~faO)R0uIJryAN0xx!JDXKI1I(e1>M_cb69*CMMYqU=|C4 z!0nw~RlIK(2h@qJz_#&3uxCY-3tGPjNwUE97)w|%xzOHQ(I5b~+ALX^nK|edzk5^d zu08(iQg|t?eXTcQ4F4MRygZGBo;)s~Nyj7TV79$B%i3N&28u*joF-gPG<4@|D;tXL zTMmBn#Z=12#KP;&bz`=&71p?RXd9GIW(Jy{fq4q`%LRreIpRYHQl$M1pqw|m+lc(=d9 zclKoKf7Y*~xC-Azh{^bd)F5Bh{3lWcAKlSmyC;gi-;rZq_3>QDO+u9L?SIVQ-%kLy zm?|Hs<)(^Mu@7HfGP)-7V`NQkQ z39;9)nlFbJV2tv~;D3^sTKKig6WTRzNBY>=#rG5neYXnueynrMnAjd%FTmp=v2IMJ z^d0mwZ)D-1{7|6aNa3AR1%2{ifqByKjQq&A1oQsSeSplPociqd?t-KSXd2fmY7aY{ zlwVh=2LRRg^FQP(eX%Ug+%^6d8GFB1<)dwS*pcY{Vc|7)nSH(2#13P-1Gg{7y?OQV zL>HO5t#}Fam#Ag)t{0Tp{E6b}#gX;KY<=$Bg_Ag5wWjTsfSr2oSu%bvrTnp#Bxsti zt@%y1!PvFha~9voOB3`vTM-Z8)2qnrI*Q{nlj_i>xvpBH*M2k$Y!$PUX2VS#&e{`^dk@w?qxnbw*WPZ8^EcE6t3iNpU-{2tkXP$ zEO<9cT|HLh!||Kr$C{=luMUY*pR0WyZi&}*wpYEwM{#wc%LYep%)UXEcc|x z&Bc2-ndcv$MsEaDI|cLv6Kh1G^8I776ObujlP<~XLJxf7^Nmj;IEGeT&B^3=E1{NA(m}rw1{qCcXn7Z^#asYzX$0yFrES5pHLhg1a3|oKE+o;lK z|LohAhI5AisD+l|q0)eqWdR+?0Z{(;9Nd5kr9<*_yoWEISLM!@xJb}eju5OwiEJ^M zK2(|zgRNKLL;py~96w^UKQm*k?0EfZf=E&#!-N9_A1bVN)RurWfiYvGt=|8i&kUl! z?3yWGKQpF5&;n`#7J)2Ql0*VL<8-gqh`b=@Y$np6D%JX)tmFW{bQUGdaXi?6)O6hf8b1+B@w_GfPhZoCYj0 zICm>5iTe2F0dy|*8toJxQZUFla`l*{Uz61WKNv)a!y81VR0Ey@24>);yuAeL7Fl!` zN8GvY&?R-pR!m~K(r*Q3nA&N%AqOm6DMgqdz5t(!-1-1=* zP)?!%atfCDF~NkOqXf>HQxLT><4i%ya63mbx=$5?n~>&|ydBkW2OgwWQ5bXv9nu&S zEr=Gw8BjA7)wWFDO>K9kc4lT#HzeNxWJt`4B;fvph?<1xmXrbBg$hl{kI{=qf@=^g z62Xj5jI9S~O9#$tJA6QA;_T7eX9VJ4SOn!>E2Q&C&nq>xLr6$d8Cw;3uf&ZpB2GT3 z+DinayMb9y1~-e4A|TnV+%`@vj{J|_t|;iLjm(3Hrc)Zm6;=~mK%A2FhD*uxi0Y@# z=5MdKKSuYg6gQzMV1*5gScFD0AH+G0wpB{hc_2T=_2Ye7Szb2u+lk!QX>H%a%u|&-44Ne za)CtM+CoRU%icVnQk*Jj#Alo`U=pz~%|_DV5LmsAocS8)k5d4t>wq<_bsDM#35t{q zD)`r4uN7;tkIT(C($hnl9)#4)wLk%A?t;t=o}3yei1)T4Qh4mD-Q=EJVFw`~L8&o8 zIki#Erv}tUAVAds%plb-jp`g;Q@Hl#<#m&pBl>^$Vh$JM7y;FX z2u{Bl50aLMsh)u;ml3S4PSkW|3^P|GQ`8#fl zsceJ7w=>`NG7Nn=z{VZ}sS!{C1Vb@6_P5bBWth4l{|u6UU;bX5fIutAY92+%u}iQ3KU@`-v{!1LIcuP?PYeW*pa50-i`sj`|^?ckja|s3gBhaXr z)GMmOiZYG&BId38_4O=!``j-N$6K+u6cPlW7OFmm8jKm7LNQy&s0%UD%=6HfI_#c+ zn?k@Kf~8WehT&Kw0)R@^+-ph=^T(6XG9PubQ)!{VRq^uS!!}wky%Bqo0jg90X@Q0a zm74)6&K{5N)<7J~2^1uFVKk6N%tWdEpq?Bc#^S zJ{;D#O#wUa`u6KJ{;c0U8VQYqcsL2q2vkxD=zQ!32ZoRgm@KMt*MGJg0}4Z{k%VfD zLbe=EEe<2g@l2uKYVJhA)-CXu#0E}?C(hV4b3tUp)`BzuN)KQ>B%hl*^sSTJ+z15H zdli^)wWB0K$|axz!5Uj>{f%f!vEU-Cb%ks^^{i`ZAJ8 zQaPj%$My(FvXBHGL#1>D25#~H@Q;2a$O}cM6^vV!BQX%m&&kaNQJJnvk?|pdH2VEZ zJcT0S8Ja8((?|pM9fC+AS|Li)EaqMu_2Ou!>Yw#>Y3JDeMZTtxC?M{8>jtK&u+V#< zA`(t#7!oG#{rDdhyPhZ8SNqaYWS&wHk$?{bq)C;8k-;fyDAUdtAv2nKV{5M?x55;@ zK_dftNG;R?gF?9iKr&ii!d$2wL(@=qNJDaCL2?*DnWR{{GdNob6Tg>S4zgz?woRD>6L;2Jw z9AANR+qDF0y&g-ArAmlY;e!c8M4q`Ykunmi$Bmslhi@JAS95GW6fst8T1F}YcQ6Ww z5%?!7an1~1p?dGTPwZ5p|6`9>_`$l`;GF-oKfC93<5A{&zQR8g=iy{WxlUUfV1`o7 zCGw=m}y{ z2=dT+h=XTh%*iN$I&OcYFYH7!@7YIx$E^FfG(ZkOv)0N6nG!p}(H|zG=}<{DkWt2( z7bBS)1scEEgzj)u5(sDpKtx%wvDGh-n2$=frQh>dkhLCRkgkZ8THZm~CXAqx8R;Nb zN%JG}t5!Os^e0GdwABv@dNEhl8$~IQpbmkN&P+O`StPZSpo5Kwd6DFx8X>kK4k*i& zH1a2dTa!Y5sq{y$J>v73C=pBLKn5wnlQYEFSP(7+mFx^1>UVB_wUSGu-U+Pu0y<*V zxJ<*8!Smn=6aX=f^f5k_+3CC#qo!IDN+<(xLUowJsFE5Ik@vM#n}Q?G{b{=pLRHhk0Z(4ddi(>nc;&7?tEssOB1%aj_6(TkRZO#dJ zDsH%rf8@zt(d$*}j5}ZtyC5_t0K}DK*FB7EBYRZcDLT`nLGw+A?JBk|_nPP2JX-IwpmDN# zG1=K!7`&d~eMJ1V)nY5v5&)VsM+oC;ivP2&rznUq zH*}!3^5i2Y#DqN2QgSR}J|&-jP#THo=Bsk;_DNZu6cGWC>fYK>gB1maXQc`S$uaySDam1QW5!2MUra}P-ZlQ>5w zw8<1TQN%s;)Ndy8Kf0UTM4Egw*kh>RJG|nb;7<|;xjprpi0XyEnOorBsYYFhHYT^w z$XFG_L7{X0{0x*4WLuUp)xiJp*CQ}yBi3_PGL6A3azx5?2OA@U7G4~Kky;14PnJD9 zuVMkQ3K~QWj*3`FvR|Yr8d0k93|zoX?YJmqu1fp=(5cNGM3fUL@S)y_R#4}PF@zY` z3y~4u%$=lf+oJJ43qufi4r(<+j7o2znQm^iF~&Xg)XOy=bNvg(Km7kX z>K_z!3D0EllqBaw!`@F`AE8+&{Oz0W* zCzhvVrd5W#N`IVNtA~fF*OQC5hlqRBTEJvr!!gb#y!|D?Hi;a|H9gK6?f*84?3sD# zbNrbYT;qiS8c|{-#4Myi)KCmpOQ$4kB&z1)&)@l(J*u|aWO#O<3BLbb(kJ=A+(6w2C>rmmuif#ZWnl znduiL^6_(W$Uk!m4oj*M%qQQ=i2m zv8z(J9D@n@YwS*GWc#==h_*%)oBw8)ufK`g?1B1|6g8MYG|XoW9*7&NDRc;hFdXOG z9uAnlas)&Xh&H*^MqkZ~sNq@ekVugMiW$fQ_(;<&_$Ow-{&$aE;`dsgoRyWS%7j0b9R>Qron*4QZwK??CwgfECTz z3;=)w5WraQkrNx_5H8}CaF=jPI0201eO$mM;R7KM+&ByI1e$O?FkPSD^Z0(Xk5Ga@ zo?1gfNd@?b3tb3Qz=*qIA*DzN&>&dcg{1{;p5DV+LIxuc?*PmI01S-)(NqC%H#1uv z83PS|NW>H*A-1>MUEOSwxkyNl@u)pWJ&5i;O()yAQP6+_kN-c$|F^zx+Pz=y4|08f z{r%RlU(%m^{hhqlKiB^K*44je>yiI%j@V|L|F=Hvcfa;`3GiWl_jAXiRyXkYwX}EJ z-Vc7*t^1}s_n+?lU+P+F>GX8^bAY;@j+aho@ZP-b$nd{AGt-^6Clq#zy8rj@w|@7( z4twe<%?Az(SPkCbaY7F^*9b9o$_B0-{>&2}@k8&uJ38>c9cJMwOP8FE=zN}kqLYgu zsRj^a5eh6#lg?>cF2zccwGaN&(ogaGqb+?x9XF5*1wQD5DMnB#rc6N%rlHalPMDK0Y80CCC32gQUcspv$W<1&Qd}(1)D5&^oIX zMWz=1gi9)ink3k-oYsqlGQgEUp*-xRAa++&9xn9Wj%U9-n)ppQ9Z_tk=|eV95f4ha zMs(1j>ZHd~P1Ia01r?GsPFfgT5fiGrPL_!GAIm%jc1(%QWfNbMYkQPR>9YbhF$*Tp zsi&ywG~xaD9dz*J;p%5+XX@!?Y02rd%?w-}8&Cuanx;Cb4Jr$G|Hl}ugljTAd97TG zOxLeX>ohg`a%`QA-2FTe9+$ps#Zcyo#F=k=a1tTGIx!6*8@6L4eCM%dWcBAXHIpY) za~TQ=0lWm9h$zz$Y{|;FhSQLD>e2AyYbSs4Bc(4xqq?lq^iSuVY)~AzhjH&o&srvPeZnAYarhQN{L| z0@k_KxA|NA!Dx1$@XIw`2)2;^C#3gSG77TNc}dyFUPmdWvHuAY5fwoh(swTEO8 zq6I6B4OR^6s-i+YgLExa_Z9a~&!w%Ym!Y9Qb3eA(u6~(fa>aGsifNKXK|!W~=UweD zx;MTvGc>a@^K~hZ75urV&49pDgdo z_f8&$ModOz@?>Xa$oYtfUWT$Z1$l96fd^FEG5oE&ySGWMR!lX+cHw-=P{Jt#T8kf4Sgx71}X+#uKIo*bY zdyl%8ZpK4m6cT4r+NbVWI}FyDHo&DVrZcUG&>~J`-J)FQM!ut0vHd!%OrE}5M~F9c zd&`9V$_GM!xJ^$973^n>`itz|h_xl%tx&7hoc2HP4EJItWh_+NalRi1uDMr#znzhd zJR2G`bu=|P^L)9~L*&`4_yN0gVZ%h(%6&|ht?qZPTBmScg(Uy$7NuyW8ekXRMeV3o z+C@=lL#nn3-&!7xR!V+Z1FSax?3(#?rARXbqTQb5*>z9s(iR7p4HT$fZk~I6bhggc z^#v2!+I3MLDxwUnTN7=y$ScN*CGQ(oD$CP#yE3wBa-TPZbamzlm}Xd&S420h-FT|k zYT9mXlRtL98x#0K{H>2B^qmVov?(ZyCU=eP#xA2<&l^p@rLN0!ftpwmn^r{RD0aD=1t>6dV7U6yY=Vgw90HGeKrs?rl=yZ(WN_F zXchA~fHHZ~aJfc$@S@j?mQP1*`FWLFa(bLX@;#mBI zP6hL}&dKd=B-aP)$+VBDyG^{I!nS_xb@8mBi&0w6bE&BDvid#-#`s;8Id zm2+hUw_nI!T&|bRS@XD_`Q^f-wxh1Nb2mBd=tAip7Y$8oKkkLY<%3?bV(gN?2rMhB zP;slJ4(xp_l3M7!&it&d+yQCQs~B2rqS;2maiRqEJqzWqFXFq(=N9qEzw^nKJDNX^ zyKC&HOq?^W0RS|xono(wt4m2;+7 zTcP=TatA-JYYq>A2dhFU1V9E`3MFrXJW#wTzlgQ{h>?Vj*rN3olnw=e?hcI9kukhZxVSnb%P?OE;Z z%HK|!F}?iM4*d2veDCqaha?-i$(Oy; z{Jr|do;y_EU3h;KX90r*sdw&$X3m{w*W9t5hT5(M>R0N4 z2x*Y4way|3w=zi=o%Up^Do^<4s~DBsP39JE|9w*{9}g7 zw_NofzYatM6n|IK%*xzv{BrHq4@4W}6jb?jLTzZ$%O~dW#JOk4$ShtUbDCpW*(0vz z7r1D~Sm&)_S>A&}y%0;bs2uTan>fQCFs@*|xTsU#>3ycLfvD0Zr9QfZO$O<|NGEfD z;9a+4iyH73{N*HY6@tJ9$1|SGv5;YnGj(!8;;Y;D2HiVkxR_iqe16{9azWc9EC)P|zRh z_j@u%H}3HdwyHlfZ|rPi7F2xujZd^w?7#clj&YCgI9znyZDX=$_X`#BF!6q6d1G zE!I1>An#bV2hVqdOK+Oy_tkAr_@2NI%~-~J_6C0nP5S!Fy+(}MN9yc?zZueBtxYXH zkFy=Zt3fm+u}Yp2{~WAf#*x42lg~r#59h^Z#TN!aR@@T&n*;qFrI(q|NLX)bY&uKK6VbSw7kf^ZMQyAG$p zs6==nA)ml6&0Ws?X;wUmmM$mGMkyfIG)_Ha4v4jSBXetg^+NO@qrc$w@t7c~|? zGFm|V;6|Ah4Cf85#kRIv1g8}jc9Ic=iQ;?NJg-d$$(2{XR9W;!SUX2YPc}cSP{vUw z;SOmmbEXe@OT94bn?@{UQ^e!@GTf^?oopdTp#C|uD?QY}ZmE#~vZa{fuQEC+THOEo zO%>Y`xk#SvRv`AMrGcs=@%`5Y+5LA5ATd{TgABiHzMPI}p`vgPR`d-QjkK7%S@B*< z8s{~xV(yedBJ5?Bwy{y;LB?Tg{@!_ggdUMw=Wg>b$r@K>;%0@bXNAOZw)1nWhNlyL|wM40-5Iy@7Uam}Ck!p~xXW=5&p6aNCidEHQumgY*b`!Y$CqPmoNIof zQhDo2#k-hX(D9;KQ?Giux3b2fO=?-d3I0Dig3Wu6Gc=`G-G?d72jn&1x*`kb9K;c9 zwvC1i|4>R4og7dPNA9c}yHDfV4Nz5ud;Eb8*CCJD zc|Tb*Pd6{tD<;z>l(xj6#I9a~df-!X?rgGE?4RSR4MmU15p(?Wbbjjl@0TPdW_siH zcPKZWtColCg4ZqrSV@@Uh0@8)^=NGgJ9w14vm(ut#EfkmtmcJRlP5W$HbHq)hchGY zJK+4cE&6-#n|J+%z#)5KcAvlZLZNI2D%PlrJNhQxoe_!Z zw~e%c!&Ob*+qjJE#<7pHJj4S+RSz5Bql;WVPZ7rGzZ}U%-sA00)N2}hk|zJr`SHJ8 zCxWOEx#LMsp@S8CE{NWe|3wt0#;W*<~w_q6{&+ zoZP7|*JA*V7XUFF5uEXNd*^Ng3-Z=Vsi`gUZT!i)>4xW_eAi8d9wh8Z9V=!Dh>D^I zmQ=E_$;;?|yJbow;czhy2jOj}m>uHtMSYR;STDRJ1p!# z7f2#HUWAnwu2YJdc3gH>_oId~`fRXHf~S^#4E0MES_&6N`h*X5={L&UJuOI@^JoL& z+mcj^O+SV!UygBd2_i04NKsjRXG3&rYCQMg&u>$i^W9g3|DjaYC0LsPH{f?$>Tp{P zM^vY=d>U;}k4{GUDbFP0%D9b!i#6M&~A{CE+D_ZSD+Kw|#g{-Rzg;>AK#~8@A0@VZKO3 zyo|tyq3|3YE9(a#wXm7di3PTWN44E6~r0 zy-S~DALlc@+J;r3j}$>0M1#Be(SbMoYV!T@FBq{L6<0em==(vO5b@pKENC4XY0;V8u zu{n%A*0RDK&zu}SBHhJ@<#3k-`C#>I@xeFWi$j>^C!e-OuK(d~{WnMEfVjY*tY#Ma z4eg`TgwGMk@L6Ildr@?wyvd(8N9K5&V4{CGBr|Nd>D(oYJ9Yr4XuVJ_m~*$iIsQC$ zUFpy8^yh$=w;8!6iwPB^-ND49Zv@@J!oG!Xo(-Ed+zy97b9|Oc>9}h*#n)V0C=IvL zm7jVS7Onnh$&dBf0>yc~kFYvJxcl79?2l3vr4R4hEN}7t7jHMin?<3nKzC2f- zj~rS+x{-9l3~hp0Wta`=KXuzZkqaTC#F2+<^yxyvyEkv~?**_aF0$q_%u8l|vEcr* zjA4GrP~Pfr)^%e-M;gJZwh}kuG6c-9yn)T>;w2!?QdU2CAt8a(A)r1mCir-;crMHF*QVVv!T(IzQt6Rf3 zJ>^V}wy`m$6WX>Gy<~r{;=j;_|{ z>wV9KNVl$k8q@;@SVni^TBe<+zg;{8pJ){6omm05Qs*qcQ}8spNxsP9{nCi8t$VNC zuf^4GyT0~|-_W}uR+D1v9FlW_2?JROij+y~PcA)km1FNhHmy=>Fh)%G7Xa6J*Qv}5 zmObRNujOCM>6#c7<@G`$gf7$Orm5>W&tij5FE%knKbS_iw?VEJ7w#MiLOGZnNT+>{ ziY!R=uR@%5{M;A*?cfi#z?t)84^bChVsp`sgDHC_Gig3D?8=b%#+Nu!91uZ1d~*E? z!IUGC*BF#7!n|fOTTxbvztHQv=o0Y7EZ`#pcf^dXCGt04%}xSGem3}=mm>E|EvT9V zy-IRKb~6?gMR$u2vvfQfc~{e3vEMGWd+6Pb{Qh+>mi$K!Y&>R0ZftHdue@F4XdkIi zM^%Pp;!e06lyHpiHCnd5EON;*a-?+vFb^3>c{qc^;9d+SMc}9bJ0!f>_v!RW>y$s! z4xx!E$|I1DMKS`lXn2?*xzlofF6#7@+L4gpF>&C6Ocbmon1IdB30}`>Qf}b1j&a=V zFd`zkv-+w9KZFwk8xM<09{mPomhWV)PKYLKp+Gs!fsQFTm#zp{#3jg4a7&kQ=QWfKfV<-Evf7v` z(j|&CT|-(+!75uhFAfD=O4$+V^PKyRGl~PPv|_CCo~k5GG4x81RTm~*y6RLV$9LrBy0w5 zXID-$EYU28F(B80s^|^?inM2Raj<6(7iXrn<$AwTHZC(YBJd;1rCAY{0FgYh^pe?1HsEULWqYte$23T*4 zucxZ+qfYPRtj0=785W3wEwMq^TzIWzKJXMdbuLe|mIj>)C~EP+nvWwH5_GSUQUx8k zBJilNp52r{nS?^9OpsDfyC2y89BO5^tIBzvY_ zR;n(!pSh6{Ie>yJr~?m%IMXA0G^<3M774(j=E-{Evb<0+r4SzgdGwLr3!*h)ihF8k z?YtRkUb(03p;AiAyg($ROd*8`i;Wn#1!UEPDCb6-oT+o-#5SkeDlj+#t~44#tjNw= z7~qWdqc2&XPPb=Kqtl1iSbXMppe!Y@tHD>M4Nggkb{C1REVN*JkY{~dxhT~(1YFv5 zCx;&DB+iM!gg|2LZ&WdBkvaMUbeUznTDr#NdXd6GTX4~FSA@?K6=pJwanu&_*GKVY z?tiFWVj}uc5Q^!wR%+^!%7xT&NoY)nxInzvLst?dlcbcvT8ieA9>|ITg^H4+$wGErmSV%dTJb7bPc@3r6>C8BSIYKSqQXV`h_FAk1jlY+c&1Qx>caw5k zYy^*IMPO5Uu%OU_FqBL%EoK~lb%-|KryAkyLggX#YiiD*n=nO35bC62t;Fkj12>xQ zZ9UrBB&B}7cezrGjRst4S(FSYk462Y5@>L(K)^JQwX-&tRsj{0iC2}>9??& zaX8XBgtrVSjaU$jM5}tI-YHvEYI>+7nO5Z(g$2Z{JvI*0UIVhFH=1B)B?1wxk{MKT z5=(^|slw%ICNGk<6nGP4x|M{zgXGb@6i&e)M2=>Zi<`>rdjr}>w8>ntLOQ1Xfkju! zNvc3ur1+Yuf;kExQXz;UZ1)l-^aEe+8<2`+Hh7$D!hlK^A*lzGFiTL-lrlFEE!SgI zbT)!TDG+l-rmQ71#?=NV0U1jp2o|B_-PMm@Hl@*@36Ztbos-KerAI*_U4<4*tUObW zHZ^CD4kxn)6I(gKU*Ts;uCY}F7jhN=5-5^0u1`g|F;c!RS2D?ew4?ryUY?{;=1wx_ zrBFJFqpp=jNJ~s;o)*={*QGUj;$_nHIAvRLAWATUtraTO758ON<+??2RL`GcUHc$riPfmc~pQJ8Qrlm--A-@q_%MwSJzkP zzU9C4ir@%5tYf=V~}3~YgQ8LtsFX*butPr*c*2|L!(2nk*4Jeof`6uQBsDq*v^!qB9NCr z)1YZ`QJCH{V|Iv+Sv30)A}`*Fw2#sirJEO=)hxLpN5}VHhGh><&^;b1D9$e0fSEU6?x@_;@&Zsw8zjg zJ3)3SZSK$sUiSl4(h08kOL_cfRNba5C$DWz*70GLD5&M^XBE)DxAc?M4RQD4Y z){KWw5Yk!4zhgN53g)DtPC$z>@#0D(JqciQ3gy8O zE0hx~%EUljaTNj#K*b|PTuQ$4rGxwRz6#Tioai_dqW*q@+U@Fm66o=OoARNUYG&J< zeA>e?X6Qg7ttj(Ez%-$Xs8AUpU!SaSjyC~9Uf$A-^;8H#m11F?T%4#P(OBQLx=FUj z$749>wpJ-3>O9tj5QME1i;BfE4_Z{f^9G$=j$@YD##ecqF(h(n=_G;}a&qeEkp(nk zRX*e7m9g2&wmC_ef!GS=fXB*0EtiG-k1obWGQ0<4PMqU~F*1BHa~w=HXb9m2i4sk5 ztx{4tZ{L`fVP&TlovU+~BGmNUXH&&GIj?f4mE%gOJvvoWV6l`n1R;t3nqFd#565t3 z+nh8_@`1EqA}saRN@qRkN(2=_gEV|&X_##<9pSeDjy0Ef-0qRcYZ+EpFpU2|lv0L# z3RY&v$749NjR#WE;*mqrP8I@a04Wy62aFWIP=gsD>H2?~?U<9vL?kBVVxl^+c<5o! zD=7<+)HCEDBbsAD+2DadgBXcowH~&!tS`krcqz0E`c`P`+hOo(_xHHqVuQMo9#Sba z(m>1Z7tmXYXjhZV#=K_Z4vkJ=dmZyy&&Vmo_=;vMg_kQ8ZFbx=+sz*O2-IsLf+-}i zPR)ssPF`e3(bqA@XJa_CjR#T+(!@ii#$a+R(pt;)mMuNC6`vblH`%5+sVtar%HTz@ zR$D1`6bG&Wi>#{9ld^SY+1ofEHOhQG2UH>|f`Z0UTi=5oSW>Fp8hi(~Z$N5(+=_VK zg_$&Y5NM7k`Z>2lZYlV^94rMycjLg65K!u)Rzw6PAT_9CZ>4RI`DPmvq@*O)J5-KL zu;U!($Cs9k_JLNWF#(Ns{a(%XeTZ`+xb2;SD9x&+s`jE*e%1TSjF30fFXGUxv>gap z@f*ft0){kFIWeX)X%Hip#?HzXw{ zNhOwtLWS2nM#xauiZu9kBDjl%5S(3QVVqo;<1}#eOcS*78l@bn1IK1+HFzOKQ_+u1 zb0VPx8z{Y_N-Fe4nf@QRMMNO%#GUp0o`!Q)J#)n5SQmkaElN9{Lb3ofxhm=^R{x0x zv-*JpKGbi0Yl)vjOV++V`zM9nyx!EG?y-9Y4GaGE;QI~Xeah}NzP~$lhtNOCy;5sh zOblKUf7Gpi+gpbH3!p}8&8xwY1IE-vsQu-v3ll-!Q{1?8%e^{njU&;*oC%eTCwi&07kBKJ zp7<`vefw(u-u1Q+K*d8!2?meJp%nsiW|f{5xcZL#KYtP{XZ`J}^IJL=wX*hD-gK^m zR698_OH_$KjbQIn!JA|8k}0`WO}qcizn<02ojLMlcz_P0Bp?%X=bXYcq%F!W2gvnO zZncxQ@5X-*8Qv=>aVMIjU=AiRsM7*znf8zIU)KK+5CZ@M07FCsAOKKaReq|)0@ZiF zPPgB;QV>Z&0sz?zppBV@B)q%k=KTqhBP(U^r~dmOQX~LFL|{e*0NVg05h<~Q$jW98 z0VI4UeBfg|JJ=@@0s)`EE7)$y@Jt zcRU_y%|c8efN=?=3l~5o$amZI8(Z8EBTO*g7ySFNN3!;Rvh81UW~aZ4kL;npcCKsE zKOOt~p|73n*3;>Zdj0F2jd$1GVPj9QtK0qXmt!CO*mRfg{p*LW`wr>3pZ4y1zvWY* zb$_&|y!PxKyF2v5RIn$Nc1~JrceP7(?@LG5_x-c*9-Vr!KSAPKQNvWsiZemwipG%K zix8NW2nPS;qTTlg_uel&>K}Whz`vCLf#qBEGok-~>}h(Hqapq+D}j=n#19Sr#rAOS zeT$sOuJ`Yy_A&RXEvx)DcbWf7S3Ta&54D}uOPK^Y4FKcWJTh!*phS_!{pZT5U&6&9 z=tGZ2>5CCP3#zjYC=>HNNiXBx?Ovx_ToQS8KbV5Z)6`g+-nSu+y4X})=(>%&i}&%x z=lJR^_?i88$kl#}9M11p5fC1PYLpmCks?AP0k4br?r-mjoT0$(T+1Cn@pMl0$(a^3 z#~tt_ZQY*Ue=k{2x9^(vlD>P383DIsFTwLFKJJ0$sfSbyiwm~8U;o)jmc@4trBk{a`k(5CUn|$BGs6>B#rTVL8{aIZnFHt`P9y96SbCyzSxei?afje|GJZ>2U(Cq zD($0MNG1ol&f-F`&^rSN@R?G{^9p1LqVkcg$!skAoUGh#PZmSX)BTn`cj&Z;o@;xx zk&VvtHLe@-EF%5p=tyG(Ug#ugfoqFH4=Zy`e5&H25tB8$iTj9?ovopvi6M`_d^$U# z5`=}tKDe%D$w`SmLdh=Hg?W}gc!`Upn~$HLmz|k=^fNRvvoxfUr1CtAC_+_*;P`$I zqWmtf9O<&@_$38m)AZ-o?;thr^wr&tj1)z&jZG1lo|NCD4XWdb(UXgbp^KN5Yj0&$ z=tjQ^A{C?JYPoEbpCJNAcy=(I8+a|XbZLD(ZmhP+E=aQXF|8=u7F$1D0>FpxL;Yh- z%eeN#)?&B1tZv2h{PL)2%v+mv&4jF5*QH6_{QSBts_^%_NV0k<;8k;Ay$vN6li{9Z z4UVZ-W|?_8`G1*?^BHp9#?fpSov6K$VJ2Daqik!3^NA57tvtWk`R(5~WRWXS>QMa~R&JO+z5aTQ$EnN;2FvWQdWd-)XvyY)RlBBaNe#_f}t z7v*+>t&?r?R@6#Q$SOo9X#wT)2`!aU;;yE8RYfI3Lqf+Vdd*Ic#8uI1Yk9tMkR~+y zXU26x`1Ej>NbSh; zl|3ug99A?XmC(&8-K6)l(_ZtF0tS;ijZl2=moi4^d9I4j1h?u^x>@IP5D1c5q})c> zq9NYnsplp@(T)7+TsL@&lZ{K4kCUNvp^ugQ>2d33hpFD)?{jz)tDVSY{}D<>l4_YK z+P#Y|??z&*F1Zg#!Ea{KIEhN?!cF;pXSCJqL9@`%pe{9{vTLoQ>!W)ATcxdBT>tm} z$n1AxX=mp-TrFMM9FGj@ohcK9uFq^t?Xwb{R-zLAHbsCM)Cs;gIy{-iO^gn|Em#)* zeL+CH=vScJ2J;$w^-J>n&A0!)xz#dNJvOKZcStZh>-B9_^9(9}`|+^+V_oHzJ6lWA zwclp6LgU?qH4V}P{O&1zM$w0E6UqNP+|t$+8Qf_@x?kt(%Mx2s?i8qa z0@CkHWG?!d%s4MMcThubFr{zUmPmNOUw$i`inkH8gL)FH^ycRp6~yP{ldOvY>I-w< zKHIu;>eTaNJXv}FMsZVNB&7Li=O$X?)(cLB>PbYt)>q!@&Dzc}xIDkMg6I{JY_y`n zP1>Hw(>o!*|Klwqzwr%dVRNam15x+>K1PbQcj*2MsTu${K*zs?8pbjqSlYAjPriqP zdjb5KC(ZF@F;d^&1^lAl`-Z}A%5{&r^V7YMjff$WO*U}#TOzAinv*YH2Y10CX}eYR zpK-lTT_td@)x-M2mXjE|^Ku71H$N{kA3s$6$h8^Kyz8)+cOP%TY=+nI7%AxSuK7qP zPhBmE*Oa|!J(N`hShLP*?0Ln#@0MyZq>@s38=dL)Y3;pp485&;^P=C6mb@$ebID}C zVD?urAJnh#kqEAb8I>hf(bgV=YVuX9^~=u)n1?Qni733X_g4+6hSZt4o4iJfNpx2` zahfRzV}*RXhRfwN`xVYR1oSAEoPFzjd%{99s_hgcaO?HVpPo5G4dB#{j&Go7B)UE& zHFv)Zw~3@mEBIc}c&5G;m-`QsVVJ*LqB3gi5Ehzu+icG!loc;j6U+#fOZxSzK2**s z-|b2XowdsHOf3BTC7NAu-w+ivmaU=gWvm(B`yO8si++(eP7~CP5~eTH!zDulG&bWk z(|j<77=H#?1q(M0QTZwP*=q^CYaZ;4RsmcD_WId)E%TEt(&`>pxeH-0 zYD-ou-L87dj@y+g$d+j_KZI}DL&@+|$y0;H^1IUQ1}3ZqMRedgF$75?FB|PkGOnoq zvfU8kZn)Sy3ny0Uv3fGms@%^-z@7RaNA%}e6s(4?-!rzi$E_NoVkcIIGshHz+({dY zq~nK{_#@ti>NxKaK18$2o_9xAOw@=&>dS~1%eW=0E9HiVxf9KVLbt=UJXB)tNP@?;0=f>aaZ50Y@%FDe<9}MR! z6Vyw=^0}!kR-+Q5(1zfM{cUCc>;0wT@GXtLjzJp@k}ctMqvA!@vH$nMZ=6IsgX>6Q zfBtCf<>T~X+b$vZOjSD~Fk(8S4Yd$;MYB?0O;lBUke1cGVy13%_}ReR@-z%a@+F08 zuxP zqaWJr)7$@cN!to>sV|NjVYYAMp{ai^Pl8e^X2jV8uSsd zykcX|a%Z)(l5gJ@e5V8_*U_d*UXI>120XqH7Hby7tJAg`faLU^o?qXx$zb#9D50KJ z9`AVt+d}*AVD*Wo``k{}Tt`Ix7j=>+QE65~D1R6ii& zT)vA&wJ$-9f}eRJ7F_@PZ3*!uQB+GLOs^EbxNv4OJ!YG@iO+bI`C4-16TR!Y48RY# zHtEg%Hm2_LR@-WlpS$Vif2UTB`(o;@nEMN9Y3_f`4$F6UGCK`T|Tt>X9B(#6^~UpKYNh zNY)g>z>yWUN2x)%;P20@V$Si)&Z=0C<1}CUoa?}lIIhEZPk==uSq6%u# z@H|hGR*ln6B7?ek_}*yTM|i5zcc#}w`@ulT_rgKIlIMEF%3I$SOWl$=w(_4=<>Hq6b%uN<~n) z)c#o}Z)my4(9_Il{2E!9BbrpSe>RW_2zK~SxU+1ILrX&5Tb8$?pJx;d(Jwc;m%_vg zY0KNlhJEq1wPPm@RpaBC_G-ZZOK0(A@lo7S)}OZQpPEfosh!{g#8yV=QN}E z6>n$@x^1tvCU*Jnu^-{VCWD#^->koMO9GOG7p3(yzv7-z*KP7n|M*XD{jpDN;b(vU z=jrWKWnxQZZ@A*r(6}3N%F8~!5;f%DGoL)2eEb~V?7SsUeZSGq*|4$$xyz9|`)xd<46B z=i-!ZV$IA=Ibua63ur=+p*N_Q?~94^>5W4SzT@rdS*dekf~ z8#VVO6A(4E4>K&9J`4@ep`$W}F%J6%ASkivpH+8=ypkeCRZO-z=NzZQ! zW&Z&2)PMFLb4lr5oEFPR{KewzV90k=Tixd!Gt4Y3l_)6czCe2T?9yG8t%TT?s12$= z+n$NeK=tB+8s;j-K*j9Cs>xrWt};qT1Hm`rD4xU(oF=Ov{SY=&aC8%%feyOc3vo8EIp;l{oH_LJJwKE6{Ng_PzJ)C$K zF!bSknsAW5zLET;p19_p>wgSz`Y-vW~;p85ZnG@2wPfT;ofl{H}3Wxf5idcxJS{(9d0BRcHnG2FgjQ{8$}OGcD$ z&*09Jy!Lg*o?!V_R#f`^g>$|_#G zZ~?=!YQ^9xck{n$Ac7?E-!b}n5ME8i_hCl0*pLk-i~jY$s-Wf1h8ory}-!DkCy!w+yd20yqDfyQEM<;zydMys&U5-lh&-A-3jm;)yu@o-M z+Xv7ereCR-N&loNATWp!N4eHY)c0|S8{@rH6r`^ynYGek=*2Ex85z7|jF(b--MxCe%mq-5L2Mo ziOGag=PkGvc3tlQPLt&0uXUv7PX|QydNR1TWB=>kyY?G0XCk-&Sj}cdVnW4tCCy<> zKp3dy!QTWi=Lhn3yQf!YR$f#^5fvO28HO3%{Dh-t;l=?EJTmWMDTk17 )c{V2 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

$@|JBfyf!N5O7G!n#CcIkcJCEe>4&X6JAVon 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%0D>&`nuYe|j47 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 z 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 z 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

e3U$dYktF>8Igr2cv8 zp2mO12xq4C3{SJX-F9uxkGc{_ZOJD$teAL;$prJ#Cztz9y&821QG^DLco`nGCwEqp zyDv@g{&?QpI_de&dNt;m{m9?d&U((}im-Y@GkImcNz;&DJ2>#M&i$#;9SS(o46?2h0J=eMCt!ItaKgGq)&E(SCuUci76efskhYxAu5 z8}nG7>Z}*w^S+S95O>#A^C#)T?cl2C-xnk9E}(ip`9_B+EJ7)82`&o5d;(Bc0w!yUw|PnX zyIWfvhB-Y|`<{zom$`7IqHN8P)BA%K^*5T>@>84b^^|EpnzCn|Ipy>_kI=8E&pde% z<<~NdM3}p5o-Cfn-F1=UJEQVHx>V!FFZp&{+$O3rj}UT2h7uE1nJi7yvSOWaOm!G^WAmS}iCj)qQB5#&21NLOIihz6 zRQ$Xz;}5sw`H)<`^3^4i@4>NtsZQ)%uOFu3C9%%?a|P{_-ld^zr1!IA4d$2uN$vC{ zXm=;gwTXTMPk`1_JDjgeKlo%_B=XU(jF`@Q{RF9d_;dLYuDSY(r5qY&!Os1LtvdCB zJ=XDIz7w%+^=Xjql29K4)kj-VL8EqG+l{y9P_BOiW&C-8nmTTm`s<56>Y~{-xbm{G zK-7Mg_1hL5$Rv+7uAkk4Ze6CKgaeW*{ykUPH@;I#{iFkRyWfSq zf9CY4{;Y}_1L_OlpBZCCIos17ssB6Am)T|^z3dZQ0TVYSCn?w?BK$krnb`-=6^e*fJI_|q4`ci{3_ zHAb%iXN|=Tz_LOKm$`esm*ifA{=}W-%@KQDz`on+sDZqTO?(Dw@ z=u+m=GWt?Wzlv%ARdwmi?0Q1ZVz@UeRr z(t2|bGub{x8VM+<2=%z;O9)M*X(U-;6xiT$SjL}sfDZ0xT2a0ag|)9W)t*WWucdBVx=8V%gGXoDA~ zjs(OCbO|t<*?$b1!t`u_-KI15IydK34|fAhZ;=OSr62k5^zN9<2-yn4!X zm*PqrK&En^aXz;V@r!_;=g7}7&D6h-U+#C^&ECp=e=CsQ3PKPZLtMCVf5jGT-lK*z zE;_xB<%aJe+m|6e*SLeGx67aV@=RCb{P0g!{Yjnzo9eefE%^ccgX7i#l>dv*)~`7Y#Rga86!;wI zc^rF>S;s8Jr{vPdWNz)V@Rfp&eE`FR(XZay7!pmorH9=yh3)E9hc+s6@acWk=0kb; zT?!Dkw+*SE|j;x|sh~9w+Tpx7m2MiVXe2Vh?5;eUyuShNVjR zRp+e~xn~dLu~DRGhjpTG-S_DEe#zVu#?wE)j?vboV>vQ75M(ikJpn*k1xXnaValHy zY6@lu7mr3CW6RVOJf76zerSOAuW>zN|3Svl=RFA`0FhN}ldhq4>z6u^Pym@rQWlsK z?Dmp1z*LJOLJd3u+Qyh4XAd%^BNIKuivGvQD^hV(6-StDB#kOnxJ(dBC@Z88P>=z6 zR0UR3u5L1qhc+MPhsGY3%v!Bt5wqjKP$dH9AS*_QMrP!?Xl99|MH5l(nMXq#3iF_* zLZa?dSx{AsVzw$X4iu(h5h0Oa2mlge3gaZ|u2OP>L}z|zMUOTb?swGS5t%p<1CKLm z^#WNXuAM4F)3EX*La`rkGh6dRrwWgv4SnI*0Ev{TA!YzN3o?oTiZ0eCNer1R6-?(W z4{BDJriY`cTSh3zg5udKe6bo~i3pfsl}i*bT>)72Bf*mL0zuh2AKGS^hN1&bxVy$E znL(&=1X84{2$_WeArPaNbIl~(n-e%V{LnnZuhZP9N2DYDj^C)Hj9Iua-oZkg+TYPIRfIk!L9o=$T`l13(o4oI@^p>e$e2jD z1BxP4fgvDn#sNF`88DKLlt_=k_A%y%Lk*&&e4D!+u@@PtT%|t? zCTPo`J%!LP9sMRI(`tcsxC@0J9wjni1Yv>Y#?)rIN+J*$4%&_c&aq=fVa)lVZi8q_ zdg}LB-OZScaL5#eG(fdXPUs@BF)Xnz%Yf@u<>JiN{Ls3=I$A0Udph$sIC2|>>waEo zo*+bW@eJwL*@yrN$r2SLh{8(L5Zx9`y`ojsg>&RY&P@E!X22*P<3J3l8Z6kgjQJId z32~PRqz#$qmsOt`Og4(Lh_hM(^IV6v^yuN!)?nZ+x%5dX6OBWNPAQo+D=X@{P2ffZ z5fWPX^K3)2di3%sM9H4tlO)9NpmuBuN|fDZ90H~*j-h3eEO|7qRRg&mGZ{6g%m+tQ zg#XGq@p*(|s+w2PhyfwhKv8TsaXkqXOua0Vh<%K8EI{))#;WOuA64LSqFb}0s~E(9 z^>?l_2FQrYub^>5swTc^5sE6EiDUC9Xf0rVHT`5nN;)(h@nfFo5&8thvsE)C%3v0v;YaNAkhlSuBkS;L$~4Dv?>&04gtg z7;6>I)DOO@us>Q;m>&-=9Q&E>Ty#~dYwp&>vmQ*BBOcENS2#T{R zp#gu!0SX0$vvohTs7G$12&Zv`6;l{*D2$1$Ar&LUaE%BMn)1CxbzR9MBx{s;KeVYa z)1_=^Bk3__@mAA_07*lYnXEy=!|PP0pv*;kxu2 z3CkRv0-$0`D$gHUL9*$)ejSUap8r3oCjCTy;=MYGw2C~LE3O3YC37tf^;1s9jI(4c zIgE_bF$M@wRnD10k^o3!;v0)(9feKv&A&383^<5kDEaJ>l%7S1xmlweWBHH@=D`V|}sz}nV5lnFyzV>;4C z3PdN0+CaG1OV`Rkc{aLC5CH-d#1;UkDl@=D2>VUv4Fgv0&XSB%psG`;s=^L5&w~3Z zh?uGITb2D@+RFLYh7Dv^b@~qU3OjRUM>%5s9 zDb%TsB2&Ie016uino7)PGrAUi@tQF`N(aQ{yl849gfTmerE1H=> z?;=3kL=Q043Rd_Uk%-WBAVq>drnOd_&}~Fj601p!G_)}vh!!&H__tDziYAp-EzILu zO}-;lu&A_willZOI;t=Wzm*#;edh;^%bSFJ`Azi!I)>kfC32Fi%$69&Y|-8<;-eff3L-&5QY$8? zutzFWxEDwRL!)IUb0f?NFKlrPK@2@$XS~4sE``tR?>CKiY)DP4sa&m{axI0m<}qk0rryc%&inEQp6DIWN$Z=qkk3vhnotBK7+Q%DQS}*+IfT-4;^x+!Bn? zyTvV-A&;WtlCZS}5e1Y91U^YRVZJXSg<~O6_A5i0SP;@rLnSgUluJq_fY2{3HXbmc zUHqZ~2V@L>!}}W1v=HqgQNIXTUx$u#puf+~MIx+xZJl+BXVEXuU*xJajphI%1P^4w zMbR8Fl?t$IE?ugU^dw0$-Z!o#6C~x5Hmy7CC-5pp?;11fW+%&bwZRw3qo`HdKHuN8 zWc9+KdF(DK%;jPhibb+us=y$U;7ymB1>0*PRgHj>VFuzplaX-aAcmT-;vMY0)@$Fv zNml%Y42TISmvjIC5D)_Z1OP)rR3-obUu87a{i|GGI%(_3oFwk%dGnaNNZc8KGKF9P zk4)++z0i=|z;EEQC7IXN-+$=z0Kmx5Sj_+c0vMa9(|&W4ch-v}NfX~ZwBVAQyk1Y6 z>BRlbPI~I++({8?S;Gt>oldDYHVzWjsC{opfnuQc%NGw611wsezR-kPz3dNZY%Q0E zaj>wKS{^2y!&X+M%)y zcQK*1AYHD|f;w%Zh4MGk_uCWEeBBfFNUC!U|hU0xJO!@)=8tcQ_p;voas|0D>r45aI_?<$(()zx#mCSE;+M!Cy z>dH6ExF^6hnb!aNpmT0j_JPT+hD) zo_6Gu4(j%<_K!9HYj$_5KY_My`1yuzY~iKTd-yLc_q6^Rwb!o7C%EY7Mzts;WFd(y zHIQfsy=5p#r)Yv~4I4U{p9>$lKVj)P5-uRl-u9c-=z=i&?R7;Rt7f%VT1x#X6BT>v z+6qP071brA{Pc1=W#xN?i|W{kSz1o@_Ki*nPw;lNF``_pk9ON*?)zmA(xfWj>Nhp9 ztdTqJHUsotPvzQS+zr>d`tXGK&K*6EcT3%RZt!BR;jA5L%$6c%LZJu)#EKU)I03xc z=tI-?sJaXLXAE{*ZdpE{f7ZPT#wKIsvoW%?pRZKIK6#RUJE}Am7WP1Q*RFBMRnuW* zV`;HhtFKyJRy6DVwLhr9ix1!xpIfQb_Z|~_BhY&aKl=+7F?*0$FLX)1@sU4#j=HR} zw)ImN!14R)72#RZsQ|Xxt(yy9pTa+_86fiiB`LB5>DCvm6!QCK`!~07|5xi38kzgV zH2Gu7EcCy@qq@SR6yr|v>@-0E?1~3%D`dLtqPVzdsF7SL1PQiTECWNWiAJM50$3Cb zENWF_$&0Z*IJ>wE1HRj=?eu;@q*=s@5txEr8!X@bCe6#H?hVmNfZZ`jja}@Z(Vzxc zFoQ?x;r-Q|dYT`{1IgxV`?3sr~QYhJaR-M?AMmvS35{h!i9 zOZ{MV3GEEx@oMp!M28*Js&g9j77SHZJeD3PuH@=Ii~Xm`Nx!zk;T5c6q4}^4q(#?X zE8Q=#up4~Yu3of;<5mb20g&YkSd8F>KEBILQHnTVO3d?^!`dRquo#hW3qlL0(jZJ^W zzcIby<4T@PRW*c>?1*hF4FDDkBOGEd#58YiSAHldYU*RC(_pN;YmCot=6cm_AK&%) z$PHC^`!K40R?qeL-jE-F7_hothcFlV@rf9%(B2mO%1A;Rgw5q300I!sBvTc6#2GEp zd+79oOaHG!Q`Aq;|3*<)QbVTaRnybc$W)nc7npiD>@T2s5=VV%X-j-uJzV^Phd+%S z|AUnz2fW->NNmFOl;Tmy3E?qGsZ0=H+v0uWMp$97DIM}7sfA{$ivn;SIF-w~O-RNS zqw0@x1gwaSjFXzE_QT8==N8r(TN;*Q1;E#am>(nv3A*YkJ`x~M)bh`N z0fVK50VO;l7$Flwgdd(3Qi9UH9Iq?vqpMCI2a{`LptH3^HWl6a+5h$G6pFzz3EPaW z*9!$e9-ci1~L%_5v?7{l9FfFCErs##W#`MfVfCffUz=U}*~WtF{r^sAL1ppWXgjmYsABA>!2xWgI#wNXW{Xoug?(Ez~h$DP2p=u8Bx7)l#J z`D}D6VMSIr%M5EI`4D7zKlYjhYI9VsGS_@K$|FdF32y<|`way-={#&47lUr>MYL+ z-Vo*EQ&j;=fkCQryRxnZ(LodHuh(Sps15LXF0Zf;*+?U&V-{#}!sgTx!RhhT^-ga7 zBmKFvn%abMf3m%2BF0DpgV=JAj9NAf!Z8k&#>}vz#|fAScj7ls zDtnOWFL)c*SzBpcm0jZ!vDjsY@fw#^f?4ysUvC6Cu-ZuQU3}h~w9DUlB{(fXQ0Rzs z14WkG z@;r|V4@Q7TJmE%bO^ps&K#K!Nl$Mw=hzKb}ou5lwhGw!W=0E*JyOc> zje-knv{UJ1Q2N#DB5lf9^~d^e>8R(ZXXvQua44zpX{mfV=cJBp>zQernqu~ep>9ob zyHjw*xUUWWrlPH{XV*#)(OWx%YC#-ZDdFjmfC=rS2$x16j$wUv6vAf&E`IY$wH*&N z(>v-qK@#-H&k?t)YOjrXk(k=A0=6ZUMLQT|1EvC z^rK(NUg{h%YU-K4T2l^Y+%ftmy|1~?d40T{f$E)C0?<{DpL%S2m(u^}`*OehO!z>L z?`vlF=0})sJ~mJeY9J40yoSD~|D;0;wHj&+}d7;MO*2a?1-f4do7ct&FUVW{a&-+qQpz|KCMReWEbu zp)X(B1U(yn{#r@&T-8cM*8A_W=K~_(Kh;9cXOlL47>4o@tDUJ@9(m3SbygEr>I-tM z(%lTX9HPG}9L>o<({_n)e>N{+uEng}CD4Ju_RNm~qPeJUV;^?-DG$knq2bHN zLxX*ruCTlP`tE#eQR$kHT>S%%#4ek#@u=CXFc?*ikHg9epu%l02ftli$YJ$hWp4g@ z>7S_8j`j1|S@r+E9UbpWK6bG|gy_EbrGCJGgkQcL`aIhX-S*|HpdW!ZUE@Q(>sKdq zq|2P$Ro?ZsES*YsfXCFfzA&{kAP@v?HJ9R&7LXdgkl`=nAgD+=>Q72aUOFR^%!>f` z^()GuL};6SgYId(Grl(IeQrMDzQ0`isy6GzT%R8opP74T{I~p~+e7|AT^87q9|Bq! z2Dx=i$L6m6k7NiRK=0_3owP7{pt6I|wC84m&tds|^T`rmDQ z&Rf0#QQK91rB`0h7p$vT-YqU)tq=@vLgHYu^<+l>;K>Et2JYqe0K{!zZ{f)qN^!X$ z+XJnTBJ%uJknIXA_g^W~u<5<32XN_!y@9#1CJ+eZ`|m`!0y4xAzC6KHW^OR&2ZdV! zTTe9gU3E4s|DqbY`dOZis-FLj3jFTgC-J_~onBomg_Zwx&RZoo_*~zAX|0G^`Ub+j zv?cFyNP^GuZ;uZh{vhFq6DS7%O+>nlCSqA=M+OFD&7R>qy$}y+5&#wQ6?Ke}=DmnR zqhczInb{G-5QeIPop29`*2Y^aAae^% z{e1>psCt{?yPhmY-kx^`Fdlc$6>7A>5VdxJvboNVL@^9oiFfumScWzEKN+rHja_LL z=DqNP2U|}q@J7Ufb4g4T&%lf-R#Ntx!OlVJgwK4GvU+Nnbkv+z*1aV&Lcn zdjC@JS&|1FbjHkK!KW_s4)Nna`D%A?*>UXZD>pag6J8X)RPB!6DLYB^&)2%&2o93J zzVyRb#Y3Z{#yvkScGzVF=l52}eCJwq+U(gCYjb;!Yovp&XK8I?>Fhi6H~i=sZ|CMp ze!TP#g&wEeviOv-{m-i_zj144L9tGIjQ2crTZ}msm-_y?>aKK5@!Gr89p3E|UYKe$ z3N!N5reY_LWOmD^yY1eV86ucO)H}xy`WJq~*9l#)3gt81ao6Uxw`Xr)e=_Rg419jn zY>FHo=MjWa&DZ>lJr*7HZ{DQ_9qaE44x;w|0AQP-hnIPn{IUdi&+tF0ka~9qD{8une z{}Bbye;wzCn&nF87a!$A;r8sTE3_QXXSu=o-38*m*@N%xACvpc+wk}C`Ns_YJ=i|d z>HqC1QS@T%>E&k%-N1p|(lg#~9{YYU!M}TE8jd|XKlhP>7~x93Ntayxz=@gj%MflX z1%LRfPc5@(rw%oJZ(}Tf>$sk3evU?ejrw_c3gKmo0{hpuK6E!+)xRG(_PEI7Jvt0) z#r<)9C@<*$Tlr~}UDXqZQlMjYzpK9Y4;z`I5Q)&3@+IXS#(k$=nV^2IDoVBAz?8cM zJKUeQyH73ZKJlP^zW?`a&-BxFO@wY_Th{%x3-0lfWi)E-aj(-FA!Lk^TTBI~TQW0)eE=`Jq~?c23(A zXzF(Ub32Yfq_XEmw`7;qq%@uK`TLq`KoM;Y9(U`_2iv}RHtANKw_Mj;cyQ*c3b81A$pn6yq^|Jra6Izlv|up0#pn|^ zz>mxaj4$BjJQ?S{P2GMhs=nCD-`OT-Iq(nt-irFH)#!NWzcFIC_hJ1Tb~8B)LMO!f znE7;ECvyvGx6ZydhW7`(q4PI)wqKDw0fzoJ=1hw+@l0eM)`74;0|ato6)JtgExkOQ^2nEa#$G$mKIy00nTQEupWqArI5%Naok{ zRI1JzXJq>shYjydKD#xYl7APXs#(Ug_yXVR`VKYaf!^Tvcu;d8MtSc(lI~NZJNe}P zpr7&3sufP#1}tK1f8w97knjxg_@{>W=G{#xHciWl_Fr;_P>PGc-I3y3FQ*#*Ul~`s z+wRI;zEVr|IxvvTsM)~8%~=ur5xBC*7&pgJph@NOE0w9%(*5FALXAQyTag7A6Sf`6 z<$<2qYCx=JFP6z)`BB>zYK5P9mEWxr&HtFWyt|gj{Y&|CzxgH!lXb+vj0|(R68i*) zpdqHIYgxoKNgIwiC{q}lilK>+!g+1>TbV(RfAdet?E}yXq=7zR;AP={wL8_l zw{O@VWANYkIV;Ix7_VyI&Hh_h{yxnK0|=@+{LMuKd*STh`R>ekrLXifXwB8DezkR< z*wu}AL7!c`Gi$u$S`$q0JB!mx0OaSL5@Q>TdF^U=!TjCiqGvM^+_b2lo{Lg98S+2% zk{e7$cEaL)+?e(aHbCoHTQy21F-iUg?@IldCP6=U&P-8t%>$`6%^ipzaC2|sSE+;d zm0vL_=gw%3@Eoi%f8cO-2|wC4#5qF~d;<%-=R&rBH(x{fm~-EzEwp;ld%$v?=9@&~ zzjFZko_2XwpUP1FM;U?ODGqg5Om817>dltM)Ojap+vIi42}PX$ydj^XWnqtyffof4xaHVmA-#%V(^bI%py_NyKD^$X7H-V zRleEPxp&vU(x4Ar1ImnbiMVzBd;E8N8XJH62Me-O{B+ZX9|QdFBs-YsqHkPeLrVt0 zpLi$jBfmB9c6L2>lNQuTbZoF`>(*Db(NccgNvkp%tyOpX>%EnAr`ir#Tl_;L1pCOb z=ioau`#)Y1bNM_Rol!Iy>SNoUR~Sdw1Fa|6`(2q`qx%$8|I;1W-xr_2>QmG-bCk62 zD;jmzpCQn8NVOGEe>L zjsCM5KUteoRZ*fvWF&4^-5<5++7ml2&YdS#yRE1)edv41{syS>ba3qY>qS({03Poi z&d;Vh>sb%~dcp^Eji%(!mspCIdmjYidt%W)2&2>0HC#lzT~`H{?r;5ypa(v&d%^xw zep|PK!@77+VQo;p2l3`T%_6&&??;k7{>@}wc&<-r&;lBY@pUY`78SSMPAac>Q=0=x~=dGb#OfE z`NkWgaOG%BIzFXv>_4oyzg~uH!27uS4i8@qYupk}hvuzg_UOZ}etl<|Zv%pP^m0|b zMTAP@4*8pu-ZjWR`m3wZEu3Hfmd6JDf2sR1eNwbhH7!$mmVyBg**Zn}r{_4Ot?p;W$&ppmu`;;}(sFw`&IOt6XCb}0A zt+Wd3N;puSXBBt`mQq?#eh%Sdj{P}%n7lb<{kZW7um2kwyPL5|ah0cFZ1?0Nx@)+B zHZ0!`vnzy;_Da;m0{YS-?#dUe>R6%;bgaxv$Uk7afFCXbIMn=Qv;RSlh`A(zd;)(F zXpqPoaK34Aye`+W7i|zYuOmE*dJ?%Vx*^dG&j(!?9O;M^(FG;;>VNHuausc||C=8E z*NNP_1vpdU`+wooV*aJ-s#$dF z6JM<0qVZO=^4wEgME=co%XUG>IKRG$A*=rW`B+DR9^Q^lkLgbvL+G5#X>`^tY&REI z9A9{xy!6;(cz0*9)uwApL-=Uti++Z-0qv{$PxrEsE|v1=A4&6Wy5H{i^b8kP=2;t$ zUwXd<@3AoAN1Lwdjx}E7{=jW>=AX>Pl=*|3ivC_l#iw;X8<`fe+55(N+^kj;xKHKz zE?)%BCV1XH+>U-HcoL-Zyb)5}j*}R%a6)1TH!R>UYpS-Pju9Y)`@RwUUZtM@ZF4Vh zu23iP2LslWI$2k+UpVyoke6R3||x|0HaA5@_uy7l%y|J z*iX<;RS<8i^G&H*EB}=$wQgm02FK{x++=92KNcb~n6`mtcW@AdvMi>-Qp-#+Y(fqA z_Ht?UI3Jyxpr?AMOXJhP^n>?1NLDC=#j_>Ci%_}*c5GlRsY(nXZ4Nv~H|gj@JL>L_ zuu#xr(8BbCw>#KuE5(AXW0@>2hOkV6h8R*DEN~H|*&y;ix-UVWr|N3eeLNi-bpT=t zy}aoTH9=3!nv_3{UadBBO3EiuARD?Mt-iMdXA!@hJDZzn9C%YNXkI0b7;pa zYavV_Z6Km)34lTyo>60=Z*6Q78w~l2AlbUOK9qUI*AUT~0?j z=zTA{BM<=ApbXO6OwHl7G^g+gCPY=f+jVOH%j~P^=Qyb+DI!BOJq6ILm2=JwV!$PU zyQ1o_OT7e?u@#a-3qwihKFc&F5NtE7okJBuBZ*75W(kR3Z-}v=fR;L_ry(dc)sZeh zunU~6US9PY!l6nI7Z8HU~1KlirYr}e%cHM0HzFb_cDSC zkSR;!-3@S*GNn>g)6&#cQ#(k5hxb|L`9?m&W0{9aEV5-7#z&jHQFS3KD8Ouln!AiE z1m7RP^h^}6WF?Z7vutY+AOg^oDLC<5JQ+I6kT^$&qRZoTRh4W35r9rdlwvZcRRj$5 z7QM7K-!1O5)F_EO&IgrVps7-GZK@uOvlDJ7r7wxZli!_P41jq7K$}1T%Y_&aaF)t?L#d#2J1iz%t{^=97c?rNofnrDx4&=Z5>cBvYJK?8KA<{JPHE% z1fjnstpozJk|P7pUH}WzQMn0?!?VQmEl z0aZS4oc51!022k5PD&{Xni7ywT?A z$LbV1;?jy_8L16cwuwMnycN=bq$4olGobW3QZs8{#uu*z|w z9d&^!*u-QC3xa8}GE>4Rj5h<9*tiosH(GbmtJ{&2sWjb3N#Z=mp;D0K>Zo81bB4}| zOw)XcgWB$0ISB)Y)UpiYQi75uT<;-xb=L4vKb4b=AHwQjylsD8OE`!h0*qH6+obbBhP-z zDHBlwF1*;M?7Uj_SeTuk@XFw}Gd6LyloG&rRdLLbcBT;u8OqY1x&Z4kQ6|9Sni~co zgai`du%u)mK1giZ%5|6+Ka`Ch3`ZWT6MbVKn9U^#-+ifOt6V9C=snAEL3LzQ3{%qB z^fdBXonmMcCKN11yy~~g42Dp%);Io2Xb&+OnyFrm<&u)fhnWQ^CgAF+WbXhGqBEFD zW>)aJ6SQgou!V#Wb;xhKjxr09u3nwEV_62A6B%mSTN|!es!%$H42!fKAbUn#iE%~H z=eLk52*6N=OrVrj92xZD$YB~hYv^>&)AaoPbiC}HqPqZKG^=RBIC)DDiq^L9K~Z}P z<9Rl2Lv=bvyFfwQnk8Q2auzaK+n_FDDq$7@D4=YMj!2n+!A5Ed?4<^=9Zf8tED8?+ zn4ri9sX#;3A%-5>tEy_L>i)Zdasl|6z?Fz8P1csBbeMsoY_nk^Y697Igp|6JbeBI> zZEkdDO1(h|G^UoxxW-lKD~*+$jLZOWlN=I3LPpB}S1LSARxmUXC$^e|FNjGoLz^L? z?V{WxLkNYtuB=S3sCQsQ++c0Yh?_Xr7%)ZD1|d@#N-Z`=yT#4B&aT{;yq3xt`y_%M zDGJxH$Oz~l0EY`MolLvSqzc_nMp7uIpHXp|^ofeY>`A{?>CPnz!P562 zg>5PjwrO}9*p_Rt%1Unu))b0%9kwZm0KqF9fG9!OQoRTvkdO@Q{lVgi#DT4X7@{${ zR7wj0v9^L=@ri9hJPdWPDP^FP5kxa>A!R4%F?CfYrt98a9S&LIhJm)HaKdD`1Sq*N zwFHoc6*0?^%_-JlmF(7`3#x-W*ovCg!kmRi=y2uI!7jm561q;6T#2E=HP*&t&Nx3w}L7*oa6~WLatmh(&YO*`pnlZ*)q|F;lSM(Gj+`t>@5fWT*^g z1KetX1!W;qLQ7qFT3PCn?hf{zlF*s3WML*7Xi_TB5^PH}R2w8YY;}yuwlxVO=cY8E z?;Lc2o{cjg4PfXjphzKoM;k!&MbV$yN?v;VCh{6eQRE!d4vC|1ajj)8=d0QJJE z?06aom_Ec+$L5vKrkKF2tSRY*j%cf@_HBS?kSsJ!z`DffmU=pOTvV(k5QJ}`_&p^U z^*GHML3zQ($4kg;NfmPK6sRdoU=(nO$*spIUYZ4e~K~>Y&dh6ZoG4s!JaC{)9A&JSSfXK@!!luZ-0Q|nklGOhemdueSce>0Dqv@~ za38|WcqLrqSx;q3I~=;psw&qJ6;uZ5o`J2Sgw2g5l9ENU%K__1L-h@saqaiI?LoM%usactjScc)tYVW<5#dK3))g&4j_b@oU`&e z5gFrcye64-RTOJTpnz6Pa4Rv=c2F2gh@D3QfL+LJZDvxZo^Lgzv!}DyR-Pw8VPQ%m zs9^_$J0IIi2)JYy#cEuBj)tN;i<$+xx`BXzsh+La9zZB4Ikv95hcd2GxMA{gQvjt_ zr;V_bvK2<8yAt9f8!9BT5hC#ekm5S7*#3{xw;-Lz5we0djF+#WF&$@d@oOvLIY;4X zCv})YEu?cevk3jPO*1sJ$3q|`IgtC9RX8csc5lmfB~K)#r!kd^Zf{kn+|Y43WwB); zAW{+@HWoScS&K^S`>mGt@Vzw@Gs+2*ubL=~u*qf*UfaP!q@j zZEMZgE=8T=mM}S_&D$OgB8~N}IwhIIN_Vxb0_r4=Dx^ZC%tH)h7&0P@NmwIL;_AAT zqs9?IBqCSt6AoANGwmjx1>R&?`N){mMgYLO&EW=>fD)x(_Qo~F`YdQ!=A4D~{vI%H zD!~E>lGMB#WYhouAjoj>d`6gO@uq_#=&JwrCv()bJ4Mf~6!3_XqY;oMBCHhKSr=M{Ei z(RuG&?$!O8u2$%FvbmIG@$(XS`1<;Om?i7Gh3CONKJG@e-vt~77@cIr92~%T`x6r= zC?$ojsJv}3`;%8&;}F1~V4|G7o?eDuiJVs3R4Ob9*}B?pw)}%C}`yr>lmK^UIl{~GF~UkX>mso?LL1bT!_* zanW@h&y!EvXsR2HCFSfT6-9 z=;Cn3`oS3UR7?##VxI7TaGjZNR3F1ReE6>eRtH*8R30ww?9Z4Rq2NvY+5)rk?%Fa1 zj_d2Yst&P-NRq@}t1kL?s|*7>7lZksx<%Zh4e;n}hF#y@-rrkWU2d(i9&6u?t~Yn^ z9)XjzeA#dvETyf*-QU0-JTL?#BzXGr>A`Ox|pPo?puasUsqjh*87ST8#ne8*FjbY&pL@%{>9yO?LGE-{kpGt-9Z-b z`1k-K5N-3khkNhIs&IiH{*c61YfGhw<-T=Z= zFyN5!1lH{v&Rp1sU2e21gtr^_3T2UVd!4*K(LMO;g(=^5Gym~I4z648-awkjTeI18 z2;3kC;f5=$AT6O@lgUZuf?6Q(4KN^zrkq71@`K$v315Jpn%PVVnE(h* z!HL7o8FfQYvpPv`u>4kt98s5BJXOaF#wW=ON`aU+Xg<$adgy6_A_hjIF=iYeAJ7rF zsT36(kSTcpNKe7H$6De?bB>PamPy)>JS2Efg2Y@w3z)x<2vAE8)M;39AcL7oOR~(H z5NG;ZuxQB!Kxqk)Y0YivE+7})GMWxaQNMUH~t1qp;81eOdZqaixnv7 zZxoHm8$i+t23TnfB}=Q6WDbx9ZzJ_76HX_X3uuABH)>iff}ozm)28}FN8~t2m5Uh& za>rQXG5};QTPywQBq%x}r`7ztO9)TgZTI&~+F6vUEMDah1A^An@Dx4x@WW}*IFS~d zg?)TjtjwdwNPw!4nSdw~4As)JDGM9IrP@<-1RF3pQj%Dpb%?ox77u?QW{ni0gvoN6 zM@^%W1dyR0spN1=gKsZ^As>SwVVlSMcUS){-Oi8_}#ogoXQgQ9C@74)Sc8(t396v7bYhzr z80|4+pOqkQ%%gTUk#4{MFMMuKo3|$j=CSa$+~=o{%;bbjQh8o8BQ$J(lW3(h?&T%< zT2p%g2#_UJ>9{$xhVU9IfH-h4UO;zyjdGSfVv&qgs_LJw7xwxM?WFoW<0$Q89us|U zR%o8HdTS-O=Xso8EWg84d;qaHShPaVtH68RhW3Va=dOl)`1cNcWTPGZCFz7w;}+lT z3G89t_u#;p9+_waAU_e6AT2@n`r&t^6eDwWZQoyB93x0BA(ix9{7`=E-oj(PC+H4* zpSO?hBKPAKyxBT+V^-Dq7CUBsE5gFB&a$!C*w)+o``c>DD$CjkdyG%{CkEnt?D#?t zqJxZY`ZpbnTf7d2kwX@C+%@m{`BeGk1?*>rk!R~@15H-)(SbL)y9BxaF;rZXc|orI zH}C#uBaeYv=E-AjvmaThLIlPNLM;LqKxL6a!-}$+(DT}9`37FOFUfUWiaj*7$+e%G z)ojj?Llrrxkqb0|DyE9YV3Cio=;~T!+ty}|=FvSnsim4BPm`&&Lx3O)#1a~G9Yc=_ zjV1~h5D;j~YpzbN*5G#dpX4{mX$Dd=H8jJd5=D&}kr*^BgZK*$QdmaKd1ZOkSpIt{ zIH;x892>QK5H%k61G>MuKIAGXGXq(yUF}s&;ZekWsBPQJdyF!uhZJ+-yKayao=a-7 zBh3`Ks!tk?7;Q=u)tsr#!Xh|d1Fd&QhI&h@%BE`jmU66~jpy8QiRZe1*;cFZ{KIOFIwf$a!h|~ z4BIkB z-b88Fk{6EOBJBX;r@og9b%qdH@Z{^SFDRF@oB)(rPe()+6a`O3k|T~;hTHc6+Rp%z zx>szqR~q z@+h;nuQ8(4gua~@%XLst(9(-z^IO=})!JIt*OA&@tEygDRZSm0YiInMoJzEtXEN3t zq!g zRzpUxWsC76vEWS~@ql5i=>SKLe#&2@Ru`pC=@Y@DFcCr)y^`3ku1q8JVEGq>J!yiz ztgyOhM4u6lA>0nBxmAp0y%?0OOHQOsFY;chzla%MDT?RNvDEJ4T=Qp;J$AZ)mV(7ACcveNm~ zu;)OEB(Ekc%K{0FnvWDXP|0*!oE}<66J4aRShIvx)9l1NU$;oHt6;3#*{M1$EjBl` z)wGRm*&np^`x9xWKOoxv6!P{)@&>T4E>9{i&A^7;+2TW&DcW0lWZbk&@F+4Z4c#Fc zX*UZIW+KAUw5O@53GJh5Yxe9_7PSAN*0m1^&-{NdCywztu6TlSTBENN34rr4(^IABG?8(Nl=;0^Y*LG5 zt5*^*)px6(bPV2iFJ7c}u=jYiLCvUS{&y!@x?_RIB9sa9!>~S@IdXf+vOTLz4@?cl z0|tpDwWqN+6)1g8&svA=RRt-8H6T68%YEb0S{ZsN4pq>yi{GQ%`IRA8xdiEpw` z=OOvGOsy<%0z$Aqg+ABCG&;mSPq`HPTv{t?`*z!XJuHE?=OG6Wj!C<{*0{Ly+pcq- z&be!NYavJhdHQcRCS6mLq7wx#)vy8;({2h}`c1zHZejacj?tW@RwW_UK+!BYD159! zkv2UrSwiRB7$^QfKYlU}IGskN6kv%0%QrfD-g5}HuO=#un5vwkJBAC^W}DJyd|TNh*iVys18b};rC8YUR>aM3 zYv@+`-%#DduNfrG7^7yPjV$234tT{Al`ioJ#Tdy@Q*x4v^Ux&K&jOaBLqIWc#-d}9 zI)aUtx!ep0t=Pqy0-DJ*MVQCdnT&K!jsz)Cau|wd8QB3dkOpHe1Qy&{6s(17t61YG z)cfilPsY3C>H6Gi_QbCoB!U{!lamv(z)@reNvc^CxTI&zTXHL}O-GpGd6VZ@4><)n zmkEYhSDWMNCj2AaEp3#dJZfubY5!K>WJq)4W}sG{W8sFmlaYO#~AfOJBbv8@Y80vo36ot2`#8pFtJT00j&O@$7%`2j!n-g2-evX57&%Hy zQ?>G=ycw7)K#2&>1J}3!PKvojpB*#u#~!9s32bu(RiH*TIVZM3`ZPx;E>0B8#?W{w zHiNQ4s?UYu7-+80^d0F+krzeqxTMm_E(F8Mru>dKj~3X}vQ-f6T@5{r8u@H96@is= zSvg)sDRo<1=HQ7Qk(67K$sG= z^9eb)<{YziR!^^LIP@PT_b{e7nUA#16@9(xWFJ)A86;;!6qeHJ(@Bv?+_0NzsAV$; zH)jszt&=SKXd>fhH?q)zK{z^4;aEy8@@A%LLitQJfEH~s+m9(>L1V#XRj^eZXQPE^ zln^_=NX=$ScqB+fFN~FqcBKo2hz$qxJGC1PjG_=$GE+K^AuNek<2o;meP=jvjj13M zhOYuHOG3)T8AU$4*=C=9YzFvOIPGK5Q$4n~g*oimO;`JR*m|}$NNI)Lbgu|uOy$%{ z!3x{M+=*2TjVjd*q@js8d8sH-D@IWYP~O88ytoM#Necm51SE@!Ue3za-)KTIaguZi`JSlEC<$L6t#d!GF(nvMP3Tb2iIjp8L(l(HvrQrtvsHr>~rm2s>QQR>)R2gNcAm$rR``?+vNh7LKoGN6_|Gv8>%Na z`Xg8Ogy^J&bF;%Y)!`|4?t~SHlgv?5-LIkUX7L#DHL`tF%$wfvYI0ip%xNq6c{DBd zV=bWY%N)>EDT<=n8+4Fys8u(jm(CoY7~LC?0$MvNt2@g!7RSb|u+X(aq;V}~q5OX- zPRbytpcde?NUu591*N>-Pb)IUrf!O3;HHT8P}-J58oB<@jQ$)s=sM2+=sbsWpz*)g z)F>F$K8*#jSRYVKa2LW2%}GuF!M#IhsxY16%d~W*p`Jp@by6P=Z`xFsl{7*45sDT= z3t-(sStUPj_YUjRHB(JhD#qog4rsao7x_a&ril?qas#yXx#vPgcEbKN0}pu2L`mTf z%~GEowlpF2=-*5?MvxpS8Z}DXiIkueV*6Z*+1W$N6JFFr>ndq5I;T)$z_uegP2UW= zw984Tf+yHH_>xf*Qi*p;{rC(Hi0EKS@~%#n^L%v@y6j!Ml7)9==6qDR%ERO#S_L{~u8h$ZNjUFEQuU8sWT%@t{ur}I-( zp^54-QL2oJHlSQ(bXt|37Ate4pLzrDp4iS1eLrumO3oVqutPP6)25cOhiT^|iUYcD zq!)foPhdm#zLt85SDz;`6HJV5t7;0NAOFgOWgTDw+oFaQm}NC3cdDAC2?nB--w7-y z=jMyf=4)=Ow}q~AR!7{Z{7C;B9T4?vM)3xsBAjL|w1h)dkmFNwp6;nK6eJ@0s=!0E zBjD+Q9-SEtMavQQ2OiBU>0tU1K0yhSRO37(4)dutOf)axU)PQZ;NFjkf)s{@a0(5o zWwBUE=u{J!-1Ve|6u!RJuDG__j)3&*M<_J2j5C&x@!a;o!g9X|(uS&`B&MkoCGJQMfrxVo;Ib@i3~7+aIdX!nk5@V%{HP(#ADt`53Er6}b4}XV|uGx~#vJaxwMOW|>laq%A2UpTU zeq|xJWfmbne%UP+_kz0`_k!rQZlTv!(^__bRMNI_xdl=P^R6ECtoja*3m0fBA|@h+ z@W2rxe#^P~`2N;@PoHk^Rl>o9=gy*!A5XjND`)UKV3nwQ=-~U*t?6XTich<%ZENiI zY}htzMGf~e`EyE0 zY0mJ)5^QQN_|aWrlPfX912KhxTJ8M%w^x;Dp50>gFx;Q-e6eTG!!4IYlBwMeHSpm^N`*O_zdJ)^Mum`H1<&%#WOEdl{^-C zt$hpJB696nMVRtU!TJ1N)uI^HwkHYW2tDxnE-r1w_KQ=(v*W32-+pU#_(=S$4OCIO zW9*C7SR~Uo7)3u{&+ZisJo|_Q)qmJ8*UNx5W8`z=8_Dr^gCZfv;LZTeNpy(>w) z2>0P3#)rgREOJh83HU`V%&k@JJSNh1tqPEQ}=ioN9|=;`VDU z9yrH1t}n6vSz1WrH6Cco)N%m3b6?J;?N?kfj}3lI&?C-hp7ZNZ%xX>2ds{%g+#}8G zisSCN!x3}6^t`7wpOx2#l9p+{0m2XGp)8~69mdPqjeO~K+jHN1z;-QbUdf${o4pb2 zSn;`z%!fw#+~r@`7d6zizDyzQ4n{qKa;)a%4!>h-sqDP(J>qW8`6Ky>SXqRDbq{J} zU>fImUZy_DB?-*Sh&ODNxpPu2hkdXDP3bwFxr9wCzeA&(f0O3fJc4umG<(4D`Ro0O z@$!gX^**qS+VMS^Up6bg?>7`D-7MR$=g_s=tMQ&DV+*5lkP+=aHd`bdb#|Giw>=4o z3-@0>u+u>^1h>^a+0Sb{O@2hD;h7v&m<)(VOqfW zJ6}jQ;PEkw?pWi83wie`Bfp=8XEje=t;ioqOnwnm?kg7LxaRDx9aSu7-9seXRU5!b z`s!KbU-pY5u-$|HsrdtcE1h<4S5y5kL%Vv4e;)&4d@t$NJ4WyjDlSQX{TjQ2w$UM}Ko8{XJJ_D8Uw)){$x}`qsrG=f7H1x8M@!gOJr0sy1@}3#8iYPnn;h z8l_T6yt?g4lMxS>cYkf(T<1PDRh+4GMNPklE;zmEp5G&0xS54hBo_KBRYuMAaFrB% zT}YgJ!%deR;_d~7uJF-W^#h+7Qf}b$!=KcEpt>+Z7c2XK)$6~00nM2+qoUG+cx9dW zA879qCea)4Nuxn+>h(TDKhRiYPq*jK;k$!X+qu#G`FH>6l{k_iU;Q^j5rLFIde`$rQN4u!bp{P*kt4S*`g%wTACoP7wkik% z%zf!FqRc3}vWt(Ib*NLa&0PCF9JkoVZ({JGjuzg<#v?ZKiY!_{*suP)gmFzjR&#dO z{|tPLU$j_FBL}CN$k=+&sL`1CN4LM;RO_80w^=QU)59%I5jVTve; zKcL-0g*v_>JcMQ#^>gQQzb~yX``O6az1+jydirpPlh=s#{_#UUSekxN3TeM2f4`d0 z^Xrzoy!vZ`{b@3bpz&+-uh8?Wf^?y>E(~c+vj8(|lFl>uri+!KXX8MJgHFwBZ>BB}U z?x#MJR#uP5%eg~y!z4pEL`W^XVxa${1>f!_z|li*lSznHukyIPw!g4caBsMhXw z#gy7vk9h=C^$1}k^~WCf76|z&8X4KB8^?;9mAJ~O<)sa$U#$K?4%WV`Ybz}G|K32! zU5nwBf*c)*GRmz5Kl*FZW~Xyj=1T7zP!;t0`W@jciKU-F~y7yLS62U){8gH$x+XgV_X0B>9`n zx{}5ZRPVn4`2fJk&=k=C00If_vOEEwwX z@+T(9cE03C*t9o2gfP_Got;2~)>^(!SV3D|Jq~T8U9N@)wjM64NyUIjtJKnsS!vbP z8^lZ-s)eVAu{MDt5K!5sMV2;PI#V@?eKS9_RBjCqy zEXX+UJ5v7pfBOHp$#oLM)N>fvmYd)&8Oh|YeRe@Jm){;EZW=NU-wpZ{?!r!G`qM7g zk9-)CJGJcBeW1vb{5uFNeBpG8<@Ak-AJXtT5DWh9SJ=6iNkp=;VIYBrcjQ`kK>R^# zx)b{k?Z&selsTaCgW%wTK*#gv6_>>x)|*AhchK(FE4=c6yrvov4 zdfzyGsK(%DUH;y3Y{;ZI{(67^XZY{U%SW5h)Q7xlT!k;l>0gwOGxV-cGLd}S!s1_UwiuxwydsQ|E2YRwfbhy z%6W13Y(wyD@sl|oiWdjvwUezB%->0WW*;jSjvN-CALBoH7mj)_|3hV`OJg2e>@r_# z)a{Lb;g6l(+F#%9eX86n+BoR95|5I^h>~sg{}@QSRDe0YWP^dml~;3Cmwb0yl4QNUx}WqcPUyR2qrBVqUSHlm zYl>p|Z16T=WzfXErySb2+ai7MXRW+-&PBP{fF}}P&(DAPieG^@R-mFgm)I&?-TQ0T zH*0U%-t@er9)pRf274PCjQ62s?abu8^@b(kKh0Or=;CSm@%wppKY9^KzZy{z=i|CdzPpxpDj zuScy6=F8N7*%SCz0aZ~tlR`p8NhmBwkR>H56qt!igXVWVyHoa$n%T+y*_^sJ;li0n z1xmrk3bB`Rj-#gb!5MdX>+^#f-uY+98tNhgEGv*nuJPWPYK$_YyMvuZFobLtKEr$e zOm=TdbzuZ^;E_vZE}x^H+uQ&(#99+wb~t{nxU<1EJdAw&^{0 zR`(hIJpW7|@v%o;;Y|XkrlvDQ4DIYKhdfi$Zoay`b6`7{B|JSgB&}Qyrb3VCl9q_Z~4hr|dT9y3k!`8pKwXSkL`bmOL z{1T5T??yd@ck;rKJJB0S;k%Ir%8g9#UqjEoGI`OmUVru!){G)ZA|}Zl5E(F50W7Wy zlPCc|q(fVQ&0p+^4NnwL#KH|H83HZS$LK2d*9RXuhCJ^#yJufAI|(ypG~wVhs{7DhqVhHgZk zerA61i+`H%Q(&;EY2aB3bPj4mx1gK#(7I=e9#a!H4t#A~*RzT-sFuGu>GW{#uLb^( zH_F|E#WkUtC#DJ?{ODk#Uf;K-A`y8tH2x?j=d+WmB6ag{+Rfyi>Fu*@O85EI+|aRrfAG&_MKOc~EE?C65OD^XtO*PV zEwIV@vJ;j(-_#=2e%LdiXCt3NxqaxXgwr{ zUt0GNik14c*qt4GRL>U8@BEJHD$(!9b2@%tr?+krANe?F#+JcHy|{jn7r?t5~~nVS#P`PYYT0VTObkbwyxw?iYJF+-p_XXTh&>Q#H$fMeH-UnX3yhFOv`I=QAGe zATB%pKg?Gh&hOgv6`kyyr$7q>%~@LY#R*j17f>ZuC zgFeGHLHdNwL-`qp75$^PzGvh$aZ2BCaJE?<_)d!r2XTOYnP(PiFq8BD89pbjbLV6k z{g0Wouzg;lE`FN_|Lkd>8xVAz_IPY1p+DsYdz8=rfBYUoFYL?6D?6<(29qo%*0baS zeWG+#&>+XVYK67`m+#t<>cc}gL-Vo@#`iO)KeJ^ygQ8m7!uxgS_nxw83I1tOMkUFr z?3}+Zsm4Ki+o!z6*x!BX1HZF*?RxTi?)8cG&-cl$Mk9$p9jn$)E4Q?HpSBN8-^qRN zJDP*u(Ow_Q+4E01z9JQUg-;iqK)-mYDE`mr$2_jnnr+#$-#&{QNhnED?Q_*XP`iS9 z*6*;@yN8bN;`*K)#Tb>89A;r0_Sj;T9U{4G?&Kyen*@?ycb=BX`NJ2~m^h+rc{rT> zk%wC`d@akxgNDoV!EcG140XToF!0|&@U9naM(d^JnitH*It~|EFkko7hvpvtRA;)` z%0SG3&5$R|FXiFyiZEgHFx!H^{f8(s#NgpPH+7==-E~AkUXuqeR3pM%IXV>z{?Oc! z&ewaNU!a_R$ig(izfs_@+<;#NT;F?q;shGm^a5_rx*>Y%{_qPfXvUqZH0Vu1;H&wY zG5#sPVBtpp*Sc2vwEQrc%3)7H;hogCz4qDnBYeo$N6*!Lw&lz8+AA(JzJw>-fBiSQ z1AJ25HCK*WtkvYX>+qZv`g`Rm5dTo=Bu#9JyyMntQvS-Ll$qm-!MODpo?JM4IP=Va zPo|@!xCdj^$>>6rO?x*i)$cktxlRVI`(bhWl%K}UD|$W$y+1OF%VJH)UoleH`7giC z1!|ykz5IGvnEyU~FOWf-Uc9r8pTOHFjD9D-j7-2Y;MsmTlgqO3nts=MLJz5Rz22i)dNu^nL9>iLJq8?lO_0!T7g6_qi#6Z-8#v{Y3oSk2{b_~&2C*$aECJ6pSd zjlUFshGF`CHEq}v#yaS2z#yxk^d~Pyv@&Vy%{Hs1{xqn^s6e-~3P@uzV5C^XAZ1*J zFl>k5KDOuBwD!G=VW6{Gocz;+&PfwSTYRD?Z{yrxW{e@}g}G}n78nyFSaK2A&cR`A z&%4>ac)LcvbVM1zS&{@!>QV+C1_7e_IK{^|79jj>Z;gH6XO6}?wtQ*ybZE<{`8WUe zW7us2AU-rDO&SfPxQm+*a(d^CNzkwig|{evNQb2N%EERIu4H>GtxjM0ERabM1q;vm zUI-nNW{pN;wo81xFe||%g;plo$f>}L5XM$O7% zfdQ~6wkoE+Zr%GC)lAkJ2|c0?Ziro!_{Vj#qwJty()H1&5o^+gGZSiv;Z;T8Mv+Xz zKw|-k-?k%g9n^AlLk7n;=$ABLG-)cd0c4TnV~|I0tOjUJWf+f+pl#UC!Fg@RcW*W5 zc8oKQ>LvsBlBkR)p_(@_0Kl%nG60k$Nq{iKiz`KKx zga5cZ_ahdDo*M^l&*S%e-RytA66gIkPxpdUlylIW092#A6)p8uN_9#~mc$qk9bXRlvErpjCnmI35-lGg^aH zF$-_46(kep>d9T`6q`^ezDzPQ$}+N(M`8@dtbUc!2$?z)M&gQ0PXEk!7ox(%Woej} zfW>aEw)ozj#gIh_%Mne^j zWRIylN+_LL<#vr&)g?V6y@aTP3u2dOF;~k?WNjry8VW&GbxJxDvCM^slXwAcvTXge)09!z$ zzsZ`&T1nbEsfeYoP(lWf7!h4DUTTFvxM)-?wYA)FqPk~6(b(pvBuyEORMf3xF*k-7 zkr1RpH3S4uNyso@5$e2g#I!x1wYr?SH&;d}YC~r21wh6Do!lY`7|KDzIuj~~{Y_dZ zyKfn`a&T7E7gr3HS>9sMD{1LyY=^Zrq0BqdF=L`eW|0V$jYx9Ape+-HmxD24Yj<*Q z<;@crTS*;8$|=j5x*;Wjxq?&M_Y|s_7$AhfP;{KW{v2$O&+Oa=Yn%569$O7LvtDIR ztNEPNf-?21eATKS(w$EZf^`oc;b%pNx(HEd0AST?5|V&AC|Mq=FGFD>^B8W1F7Iw` zo-VMy5YiXhSi<>W$67y+=2f$g)cI6FQ>*HPP$V;M z1*b?1hK*3BN0LVo!(SvxAXn!JFI|#rrHoKjav45TDDhVgWdT@v9?n-bW*Spf2^?Gs#@rJ! zymyjfh7q*_^#F2V2s!o`Cj*pKPz~}}=imhHK-TOOD-ha;$C9IuI7!XCBIXRSxuhET zmxI8)OcW7?F_3_+8JceyKE@>#H3&?ot0j<(@ju8WieLTSP_Qtr5<_U3~iL zH{L%#Q-Smt?Wr{4b)bb*TSzK{&3G6&FSb!O2p=$-b3a6P;;3$Z4flpfl`@GV)HOg{ zCk_v{ObG#C-44;^1kG6k;^;MhSh=qlw8t2!ot5PWcq#zGDy%rcU}fmCg66CVar7ua zK=ZX|MEA{+6&WlZX$;gi;;Anzu1%DRMk48yq(CUI``B`DR@8MibM&dctG&Nv#r(z8 zl_pI+#XWE^P~$O1Wiyft>O(#Hr7uNtAzh=wfGZ$PP6c4>$>&5IrdU6hdNfI2hXSl> z!Cr@9Wf3!6QvvHI%c*dOb2&xxrti^kYa}vR81d67&`|33OLhA07pN9n`XqBcaF<4N z)i*hVV$_+o>rfvTC{fdJbV4Bu#2SQ0ZT85}9%ay%-p)8zDzO8<3sv<>9AJjQA^SO< zb9ALgX7nBuL_HLuuu3S}zQW;TnNd(K4<#V9Ndl3X!Ux|$$X1TQ3|M_krx$0OIWlHR8fW{HUrCGKS~X7*{q_tT(dk8 z+NReuxm49k5l5dc2y2tIsZ2fw;UoGoq-h)+0s62OlsZ4mj`n*-!4~I#--b4u{(!vk zd+hibN=QJulSV>$k^~E@Z6rL^o_zAy&=8S9&vZl6e*mHE5M}~MI|@lhh!}2t(eD*i z+}vARZgj*`{*%d1xO33L!o>=$qVXatafRbJ4jqWD2mX5BnwQZP^+hNY@>0t&)hXu3eWX3{;_WsgUXOTNH<2aK^48i{$QAd*UKU} z(ne*iBuQG$>Bywp9GsNT{}1!V`cX<5a;7mF{HtL@aSCvxNA8+I!y4POy2T=s9u*nu zo^mlYS<)s9sJYHsR;3^?RT$xoa^);_?isAm9IQx?e8f=Qjj8%rj*yi#l`{8XQYVhm zlnUhX#Aq)f_KuVSf~;qX(!oHc=*mcg3X#+{Z=78%)BlA&hE7zN~b59GpQoA#KQY=yaRbrZU|>&59maXC;?;a?L;57$A59xU z&_yhS%UCQE4pw@db+?68+FX8!4i2biN<8CHwGA!7>a~qlStX``4+YCsSQs`@low85 z!!)bCdJwNDcN|zWRUFSCA;x*G@6{enB7KrBjz$Zt7K>$0O(+mg)XUyeDo|DD%!CwO zF8XH3wn24STy$_pRgD| zxt3SwkiYa0XA~51(hsH76EZdU&;nO4lY+(^%pT7{v0_GziqZZ zTQe#g4DqHq2Qvf{l_@_|H8$q56E$2aG3x+mcpC^RZ%}#!wPhTD2ur*osToVtGv` zSO_S3+@3Ud7FcJzcVE@BWG(PfXC0@8>q`?xMktBUu0Y_n5#}@qmh!ej%A}qmjeQPI zRa!LX*XaEe$AIaYIsCR;U%E9~ArvGI9A&8*rcl{a?&(D4Dj^H6LsZ9^qH|}au2brB z@aO2YEcl!L9G(L5#%QIY=2Y@fOp3(oR+tgA7&vPv#RI0n4UcojN>PqZtlzWvrvI(^ zr6o4wYY%Nl+jIwV}dHc;B;~EnkH^bRY3EF(LfqV<=}Fq@=2x3 z=Vj3rox=IR`%}~9DBLIMz-S#)5QMiii*gzsO*s~e5mKvQV-gcl!PT74{3vbYXujRE z*0tc<>t1Cwwu4^lutDqjS7`n4`PaD(U5lm(mXj*75LGZEi>YLeBBE4T`x9h6Lb0js zZ=6Rhrv1CdtL^he%es{2jV4n-*eF^}ZK*6%n0aBb5Lk6iry<(vH=#WTwynwz%g?Pd zqr_aLZNh(WUi<#mZFOOT`d;h4yR*87%@PeK$Qg(*Ze??7k)OCHRv>~ZCRnu6_$NCN zQ_dmJ#V+@q4;os*mwA+6NGnGwv?)o55aGVMJn$*&ko6QDPSMX(Eg?IrH0~N58u9sr zOo(j53gaqjHV`596{E^lHMF4UIW#;^wuJ9Y(75Y#i2tbpJ38Qi?OY1YdYvw`nPnk& z+EjXJcij9xN8eXWJ=%mlt#+g1(qz&8x~@FkO&HQuXIQ797*3&Nj20@zz7%W(gHu_T z!LpW=KGM?)VuH$ybR!6w;dW6Cg)*Fh2x30vQ6kGrmxHodozVjU?X-Y&Vl;(9Izjw- zjbk=p&O{&rMXQlX4^S9QgX>wBV7H9UYu7K$9qkBIFpQ$S1>z1cds;y<;U)<+;g6$e zahmenTsGL+T-@E)yWN7L(o6&Cz-Ul2EE_k46$^S*IUFTgILcz=RV?WlaIeyG=e@P? zm!U6B9_<9-Nt8g(l9XAME%K;{H~_c36{yg(xXyMtkS$-fyxm^ulT}Z@)L*U(>GEg{ zN9%w=8|N7UAQzcXnLxy$y%(sp4$$S<&%tSJ_hcSY{@>ZL-Pu1olJwwP@}x=gMw@Av z$?~!&1O{GzN~#kye5U~@FxNy~4i1`Hox#9dgf?c<`_V_zid1%}a1H>(;xxn1Dq^@5 zpQn9n%fTSCXS5C*F5J^KC}Knk5Op~?ZE8A$CW;x5 zNfSn!dckI7Xq{r7v56roz@Z9?EOSFFplNX1>~cS-qA;TF)I>A`Ce0e{A}iC7*fcvn za6*I$qR16V#c`J@YooXgb~&6>ZTQIB{y(welV*(;s3B_DhWo2f;|RrvH!Q+JLLtH! zrRX>ksX1{wD)d{KhODFet6WrxMa%)1VZc(6R%SyKSS+T1!UY6v+*8yXx}73);-z_L zQw3nY-brjaHXJ98M;1@qxZ@;Q(iaj7Z=jTL}tfRNAvlUX-0WG?n@=paWTV` zE(zc>&-w9DrScnZ21-)svbe`X(a2+5$j0Qy@o{o97cmm}jhpwy^lXE3b9sq=Z&&>k0K{h>ug@x=l84C88{{I%kpsJ_6b)taZ%Iyc`yO z^#!u`bA845XB<`tgQ(Wi?!_)?Xxb8YcT(&E&DdP6g8FOQO=#gX_^4r?^m1*O(cy{8 z(!%UXwDrw;VNeu0|4<;L`LtqA!>O0Q+)4$2j*2Ygy&U=;NteOwuBQS}7I%>lrB6_< z+g2F+s!Jyxh1~H}uxxv|ic^%d>>%{^eG6F&Xl~%XTq581-Gl?x{3S*1E!DblE zc+WDiw}lWvTTiSPaUOjOq_r=g#7F2e)Ih-hfkBYgXxcpP%fWEaZm&47I9F6yqH>aK z;kj)+v0j1IM5nBp6y$v3Ghv8zbOTd^IBq)%Fp=|ewg1tkddf|TTF4qOsXP}61v{M^*=U{i6dwr)_N1*tFQ>xJzX9Yv8X4dG?*&*W)^GjM-Xm)iA zu?dlS08Y{ERos_@{*fHhzZ+5JYsiDYHnjQU6jyvdK8Sw{_tX%O6?J-{gu+My(ZI># zWE434k6QAdZu%cab%SR-984g=6n5}W*S`mHMU!4=&ag(KGyOHp2_5o$&Xgfo8C{!F zNi0FKIk=aDzoYlLnE2RNco-&C4%*tFThF-%LyJ_|!&id(KAP>Jv`*NwVDMT#6L~n&bJc1jecW%$7qA9)cuNY zBXj-8XKU}gQTi2B>-qIeB7~F(Q#8#6b{-kc$T>plEH^GA8%HlJe{_1141gBCaELhh znmi#ai!jo%qub?YURj4iNu4kX=1`XAPt{hIikA65LJgY(laEK08A)e^dz!2>K@p)i zkv?Duj^sVDvX4U7C<>&1X6uJD~Vfg>qThTqd7v(`Vq{ z?r%8p95}xlPd&K8-fMf@b*3c@X^0-PI@}}_qcm!!tH+w)Ps%AH9LA$S#6TH>AM z9T!1KJG!6|+|&_jO#64C-=pqfMS48#J^aBF(GtPRfCh-Lg;QOSl0p%TPu z`lpYkOF=kD9cNXFl@{ksA%zjMFMddt4?755tXS6Rd%4+smMU<NrqAQTb^7|rv#!0?KcCrV0BN8ahA^GW9X4_-sfuGR8Ht%TYf)DH7AAZ7obEHY z*NiOp3bWP*3pJ0Rq0EAcH`$M2zFKKCE4w*^lIyTOp$}Wj zHZ85Tm8vOWw2++!uzhUJ(U1&_LK**B8U;&=*`T`WNFs!@cC08$-6UI7j&qa)KqY~3 zD!QliQk4B+nQd@BwAtzB8j$5NmK8Oas*{Fw$`NX9ru2ZBkt3iTfeNomb!W6mBD7Y` zt30v_mXQg=v$p9qb4ykPM@s?PM`NB8TUC~9Kh9m(QEeyHoM-s7{Ht~n<^_9(Z7sLn zbA*PD9l{Y6s@vQTWXpWGFw-FN$u9G#H2vnh- z1IPR09w!%(Em2((LNg1J?2p4BR0rMB;!u_7PtzwgcuE8EJ`5%PrLc=ibs?mefju0m zvAb!UX_wO?(qd;hyT0MOeR=nho3F03W4j^NX{gRrOx4l8riV-)ox#=hI}Ke<*VNyy zVA(F;M(cN+IKvf!aTiR32O}Usm5n~>gGo@(lSDt|X6Xpe^O~-u%q6kVA2?q`8)W?ax0fA0atu5s z8$1r4{uP;_xeUbZ=)Rd)>*?+W0{egGJ2lMP^AB@`t1_<482Xq<_V@pP3ql+ddwI=$ znRUUpgrfN6m^mE1<}T2eM8M|DTv#I4zY7+GadK_ydipp2GsSKiaPTg1OT4eU(0x?j zzO-)~cjMAL1W!KiSrPxqr`5Fi$?TYkT8EmcQOk#Ka;#0xW;KXJ6cSiMEOS`!^{YE~ zTn|$@vis`n`blSm>0NEIS|>jFvw8sB+OG3F?~>U%{&^eWar!Usc&=C&SeITm=ggM< z;gjw6{&0)?_?3!3-fMn!sWUh5?Pod+Xb;DF$xQ>N=bg2NMnN-#_ZvL-93JV1+(MpD z$aVo_&H1fzHwU3(Q>MpR(CKdL$;ypk8k{m&5(B5e!{M6H0{hy>uR&sRlFaqxM9~<2-*PKF$5$iO-_oIbExM7l~EWF=n6X@?(h% zQzZ&+xZ@<#Uw>>PUr-bgHfW}`QD`^H++p3*!AYDR&OTL73`sBkI^*8>i331!Iu|BS zl!kaoGW`w(4(%Qo?UW(AhBNR<^wzJU1Ddw3?pfzxbTnWpj<#m+0fy?q+3r56p`OPU z%!c2>UE-Yh@uYTPoJ}_KfMVciwg{4AHcsjy+S{!#2)2$!bF-L1jDLM$@3IF2xy0|_ zukSo~&kX`&C$UrZj{AFsn{oVDR}xQHB$LgOI4W6L_bFSKmP<(t<<^yq?SdW=J+w?Y zES*u(-8I&;QIj&EsY7n?qrFm498tCl$9h6lo8@svU%1Lmx!mU+BSzPmV6E+qS_s;u z!A4!aIRjj-c;SunUgbk%cS~ek&eAbkG)jVq7d3d>RV9h?a9a&mN3%NZMM(_&&^Hq^ z#0$Z|yu-P{yT7``-@laEE4*l*lGng4XSG06e23CBx3LQ}96<3|qE!>FL=fTJ2A!ID zOYO!}-~hB$Nejq&p;)(b0aF3iyZu(&#=X^ge-8Q$L2|bDGJ_S1tQpedT&S8H|8aiCf+#nlUbSi46b}+x#dvGXwJMp(-!0mV_S6q|D(76+? zIR=r9(hwu*Ec+06g6_gSjbL@>Kbj@x27CVvmENptV$|D8ldYN4ns$AF2(Cwm)V9Vg z!?tWPcr1RP6(*W>Ni2wNa)CS5E2_+@(QGwo0rD=4+uF8+!vBbc$Sw7j{OiA%W`gu2aMkDr}|F0Z&{_hOXOz5TD*yuG350?#LkvM#qSOd9|r4Z(butHxG1Z3G(^4Wzrr=Ni}pw5=^*>qXd;1(79J7@2k+OhvTI_w zKT@N(Xp6QgvZYR2M>1|=3?5GGLiy6i{{86-2pXZ5vSccCWZ4XQ|4uZ)Z`)+ebUv1F zBW5=rwD;jBT;j;Eb8d5J5EGQ?-uzZ$HT*igT(IzLz353WNJNtTzQtgOwa-KQ|+ND}lhj6dj? z{JOPlzWfdhH=YQ zk|jx_J=Y!dV2S{(w+gsUD0aP8Ruc}^w?PeYR9e?y*NJ_FHfnWG)QwY1nY&oST5x#Q zi&f5Q=1Mg;Tl;C+s@j$84+y`#Yl)zBw8(U8J@!+aI&EcSOsa93bh=TgF|U$$Bf}DR zT-W2>vR3=*P?VNiG*+s2nx0kFOPa+wW=TQ}kXI*mz}QBNeWbEpG#w%uF>d=n*LYHK z9AqaGZ@~8k7CE{mr|#9PN{!XUt@%-$>{XLTCcLS)zX&^N?`P<2H1W4$`uY>U^^uMB z^Nh88nSZzTRnEG0-~K9DdoHwda2i2ro2(%1IPv~;d)~$!&%w)w@ z<&K}N+1Z#j2mQfHs$);I5##@bayazPPkosP^W(rRvvpqy_`^nCQp}0XrDzCx@Q=I7FSu( zT}`unjl?yB9+6%i#>ZlT9$^cda~Fd5sJ6^hR92K^^7hVjC|$-VT}?O0vo=%r|tM`V|e$*|%5UiBI#x0$lFxPus98{#lTZ>?L%2x1A+*vtB4 zZSoy>YPuMQ|KD-y)GG~7&6Djj-ZN)>%M19}EYCYkOr$f=T8s@2NdhU=cARqF9a@g# zV(KwRo7i%x6+F?oRiBPP8Vz>$zQWPesgA*wJsmetfA&>M@L6afw!!ByW<{Ck{ zhon*2eNLXRxCffLq>=QPE>)sQ8?pr%+=BF+)Sg1ZWwpq(jHIBKr2aN(g*=i=(Ufh~ z#xQH|;enGVM#Bs`mXM0dLy$C59;2l~RmsAxp~-YDFNRh)sYTq2F4LRtgDjCMSfvE= z%-C1wYFwK2^fG?a*lw*Q5y`6TDyz@_wPnFCv0{E5iTdA(bDP9-=T~F1qdP(|BQaTS z+iTsVZs!WUT{Qa(J%f-S{EW~J7EO06ztV>^&c8Hc8YW6rC zp|o#f9eo6i;8vjC^TJ)?4}GYg3WDVmkEi-`z)E!T<;11xo|Up~?26KG$p%fgXQ=gR z(2D2E(}q7*VcWqPbAp)%DR+PVCnIVqH-3vFCF=FbdzD(7u!XL22RxI(Wg<*K6f?E= z6#6h+z|>Cbo~NnjR+hBw?Oyjo3+3q??O({5`7l%IcG=y9@MozUYoQI+wZVgpxomRA zv%5bB?!V&x<21)~mnxn;*x9y_&p&RDgxMi! z+s*fvP$9XN-Sq|L3wqe_xt(;s@Lz#d(%l#~7d?u)?;V?V6Zgzuik0dOm1S+aj-fhq zrPdY-TwK{=9vV8%+O_qcFpS!(Y~)DZ`h#G-o7irq{&A47zc} zW$L??k*zz`+;)IHMFUlZy6~^t)6p@QAG5kQm%@U<6{G9tD8^3&pRWDg9Sifudq2H# z$k(o{`%Y#CY2D5|d&3~z`z zh!Abp(TNXJ2b(#M*HM2Du2WNY&HV*W*Is7uo~_xz_lg`$nR;zy#P55i$h3{ z@%l`eK>@^7v$7G*9>~?OXT(BUh6GKUfoldy+26dAB*FclX@?GN2A;rwnI(D!$u@6* z;AzD{z+BW5E6bX9SS`2KbkIqKp;TMaL!)hF)W*mFUWW_IOFfM2JR1S#eDZ^xzc(~@ zvi1K9Bi$`Oeq;e8V7AT1uVERjwbLG1@=rElS_l=j&92HpSy`3l&CW70a8{Y-Ee$XD zHg7bLrAYACFy{Mh2n=c_An>kzgFCW`)?lo@10PH<*B=6A_FQ0l0bd79(-uIH!;y`c z^J=6xTg9Vwcx{$6*%thJom@k?9QNon%#_*!ajh$QOF#h(G40E6&z^lWo2+6I0HZJ) zF_clqbJ}iWps2I$;0s~B+lfl+*}gl3^^Rt!fD5sv0RK#QUP|`UXHVNmk&cMju9!?c zx3acvb+VwZzBo zLeMV+S4^g!TUn&NOjSh81#Q+aLPx}hKXtk2Vt^}+qZs$JeV9+(v9hj%2W+Wgxg@ZVFe>wO44f!9mQ7wUlfJ-d zHBTNWm_=SKUanbLt>ywq`&3Ok_*`uoQbJ_TtE4Y=uH7fwUgMkh+y>R(*FmyFfICYh z-cNv#=yq87EZEJ{WV!mkATB#ChGIJxZ;bz8k^@UQp=kqPXR1b z-mO_`KCKNkD-QIp!h{`A&S z?72eoOVivMhj4uk*47cQt)joQ#yGoccMQRxgGs;41=dY>|G#Bf@fcRlh~ywUa^#f} zOa98lobyh6Wr>q3^p)Ltii2M_e=`NuZ>0ey!D7eOc1(N2<#F}E0LvBIa!Ak0AT`!) zw}y7^Y1>mqtECatB!tap?IJ2Nd-vyjXiN2c{}MKp*~dkxV5ZhCIJq^$(s40wiW=&P zm8EN*PiA>3D)mUl8KQIq8)*W7Si-gB+BZCyWr9ZD6iiGnsRmnIEixcCeR&FiM3t2# zX`ax=u|~G-3c?{UF2nhqQVJ&$CK`Rm!H_n$pNZq%@?oK1U|Pi8xjEWtuOo!G=}3ZUCe$XB=V!VN2Fo_CAAwy&!{8v1i0!VxqlA7+E3nh`~jT zy+0UH&n5S6FUW+jpnUm(sDPS4-vC_XrmhUG-lG}NeEn=7Jsu7o6&CdF$R>Dw6R^N6 z?d?oKe%I0$1d_}$-X2k4&*ipfY5)jP5w}>K3L_G5(Vz{pPN!{!m1tL6R)Mk&llb|G z$O_1*maVljDA5ExLZHDErfrEy*>i~P+#y12EGHud8fogxYc*J1`_i*Zuf2INdzXQQ zoc~m@D==-kdPn`sHQbfs`N`r&cPz+#}Z>3@-`gq((GDUJd3vfOVMy8aJl za4B37W#jc?zZIB#erw8+a-i_|c{_O=N?F6n!pAaJYkqX1moG-(uC9%b}lnG&7izOW*)tQOf+Z{e~DfPzkPK=fMqj zdTgE7uHSOawts#=pS&h3w(Gm``YVzj*ZTw7ap1ZCs_hIb zN?CPA9xs5CHNQyD(Ny;b87uVY6Yvy&fu9Wbj> zvc%1^S#Tz$!g$nmQWkCj4Y-9PunO-lX?cT%ILM%Co$KplYHvo=Fk~J>2EaA*E)P9> zq_G9MEtC%cC%7CMO(Q0Sh9QaL=Uz#Xc`yLE?+m!O^5{S}hr8Fw^~ri{?c=bZta(tj zx=i?EJK5(MKn>=;ucAKL{f?U{}&DW|L~PV?5`5E3>{UHz016a8h}{!(%`T1fZ% z9<0e!!7$~i)G})(0Ma|P%JS)!Et%52+G8#@fEhO?GB6CQ^+0Ym^?wJl= z8|-d#Ir+n1Rp^i?AIq%Rn(4xwsHTjpuW31MQ0JpOe=wtBS!(TBB8SjGXj?9gRW!{Y zE&!;Ej3DlSJ9!7If0;wY=DzlEW8W_h{Tli6_8xQNI*b0Kg(ea&#%RLx@BFUu-@LiP z75nEi%pF|65|8vPZ$o;b64%HpyoUIxqkM(!G&W_)o&Ec$5wga7T(;sWMgPH;sOk|x|$o3aFsPA92wk#z2q zZf_5UMIf8L=rv_B?vC14I`Gm%qh49mdxJKWvZT$kfGMc~3P#w}gmjHlQDIaP{T=U% zDrl$fW$J8$@cic~j$zrpcgq0VU2R9xmX{qJrtiLlGKPk?D21-7X7!|uf9R`>?_CkM zFr~c#qfyVLy>EP3m>GnYK|TruIM1T%z8S1Y+Uhw6Z%$`1Fcq+|@_2kb_iEUJ_{z8u zqXl~huGL**2p+U017M|kOU^tiA6;f#{VqMTu1^{h1xx(xon6TdL7~q07PYT%(Og!CqD|&$GHJdMhQ(MS*~P|byc zfSGBJV$;v#UuOa&`(Vq|eJh6MQ<$M0Pl2GB`~QIqk!EQ_aY0i{+WmfF1%nU9dLGuI z2vb#ih^V&=Wm3VUs_i_Kv$B8MH~3#{qyHsK=24_98vFa3M8Dn*Hpc&s9rL(J?mF-r ziB{z)QzG1CXg*Ez&p$jb2Yuvm=l}aJ{w$3R1-6HbOLeSv#KpQ5k!@TkI^qVGPTV%( z^9j)F_c_w){v<11+6qD=4kAL)=16Ti7-Urh#t}%HJM$Ta=<`#O&hO{==JJ#}oYDwT zx;l*!2m&`-<5M_C>kj5l7I*o4K1+rC%byF47^bODrD*MRFm<2>#u?GTjGc+PZ2ste z5D)_Z0{}BqWHkT)Ut}HB`v3rqiHsjd=DA5@Ewa|K_oSt^_C61&OD!x9IRc|~U4EQFQM+BPFGy=I%TZgY?d6$dWp=ilysZ(a z31yQ*xRC%50RWPj04Oj6-g|lP%iX-(U#>=XQ{}mLmA%5s9&J(Xrdzs_Wm(hONmHtJ zF6)rAfsn`?m=i}nHCt=DK&XfzZ(CT#fe_ySK5-ZVzaid9x21Ibzzld)NBx3H9PHRg zFmg`%`=@&lQx8iacfcWZRKmEgbPvB#{?l)b(tD!fl9{(4k4*k zl)#J#xw3=+X2Y1A2Jir`pmKi|QzBf33|_ld-di32PEqS#C=p$mU!4`)J# z3j0JH<;6xN-a{NgQD&kZWS*qee1+vHcWCXn?XI@3`___pD6*fMIFdm)*Mq}E((*%c z#|S80Xkv!kBb6M510ly8@qvG1v~k|OZ*6EVeMfX@uPavdarZzaiNrj*K`18Pz6}Z- zjAUR{+d;Kmdi-G??LcYYnn&}ocHw0M`msg&SSjI!!*}3ePGI~CC_G(9ONSFTQ-wZg z5t(g>+)B%51KesXVke@+HWGCKNjYxa+cxiQEZH-H!M}ft+A1-hpkTz5837|9XAWN? zT?uS6rj$*+zHFSQ-nQD}F4H(MdO{w~QaIVPBYdvxyLL8s{g)11$9S{D1`Gw1a${7$ z)B!B9%*YArfBd!)qg=30n=KaspDy+b*((_KLR~O+r)`f^cN)yo=`dt>ARS6+VCjXC zvW1_)Ojn62^$lAP?%cMZy0-Iz*%506hlkGAI(Tg1IKi{Y&PI-P45MC512UVgHb5eq zWvD3&WV_-q5*T$TMozgl#&IeDEY5B(Yr%+?`-y)1RF$SK(k@eBBi;e~-g|yq(8cOB zg(oFJC^9kyVTHuVn?5FGs*w_HWziJ+WGw*295gDPXMS(|dQP6?rUwHGgF+=NG!-cn zBPC6Qf^!H}<9;zN0QFK)8^Y*}K}G6%yk0>SyZo75VcrGG-RE&F=f-3^rcU-qP)^rjfD1jRp_G^>ZYVhbQlYq zC_J}rNT`pcZ8v>XySC;s=ON%Op)_Suj?w@g1SXZu1{hej91frWYsE!0&=Oo0LkLVA zx0$WYX~f#PZriOcFE1)8WE__SMLw4Ar2`Fv-EpoP1oHoO7JrF6h2JJ3B{-(ymYVO$ zh>b*9W|C>(Q)^NlBeV-xWKI61tD-5w(Gm+8mA#<=H9xn0%D+GFQ z52DZ;*4}4|cSXAZy}Q$q0S>|J?FZW~?rB5+<42j#Hx~Zb z-Y%H}JmT0ih!_mynwB~?8n@ySR7^W(_I4gOLBn)E8hYJ6>??jIn;GJ0TXuZuyK#1C zE2xP^p)GX4KygXv!kGu1pFQu*5DO>u*Vl^*omFFlCb^4-C)l>qQgy@1mpUB5NWvBv z!^J_fKxs`U8(UnEcdOi3w1r}9N&_Ynq*U~cC9Q`H$Fe*2m>fcnZQo5wmZ!h36iLw- z4LKLs%J$asnHMqv3lov6YSscXH`s(yIiqN7Jsl6VBsL+Fu&rrYi|R|uQRdp*%X-c0 z;jc zasig+x?%zw3LI!iFk`T%@-;PEt_M!?Kl%vHoUu9j2~|GM9wv7bPgHoOwaW|I?(;pZ zyP;Itf;Z) z6z+I>T0L2)=>vB#=v=4YNj4FMfmtYlN^3Om?V))Saeb_AhI(uJkTtGXrM z#20*)YPo44>oKsPWWdI=suK-``6+e`zeC3`7?b?aY7071nCrOrdto;|0(-dSdwHO> zRUf+rXE0*dKpq$!NduCaPu72SrmnzVBJ5xD-$zXs=0~#$=OaAS`RpZ1pLX2asn|;U zCZ3!=9aU*-*yKY#1butH_ym6G%Y;>^7}}C}1DxRB#Zp1oys#vm*0Wn(?cE@4*}9Eb zZ9TjC%oJ8Vx+-7yg~QKSftT;JvEW2+S;pb7&JQs973BXxXORQxj|XC2La-s6c}@&8!qj ze+;K*-Ce9rl(Y-s5;hBiQ+Pltr}AmZCvhOcu2kaI9!g|Or6zt(lV=V#L#}B%IjV!V z6>t+jhQdCr>Y7HLsk{rCY-tCByG{s>$;M@w{+sa;8vvAZoT>#$=Tqv-!uI)x_&uhNN zwzS&4t?sMudt!V#qTu1|2ab}qZc*2_0^;H~Zu{~@>AHAK8QN$*$i%sW^uI&{nqeHQ- zy4)%#a^{b`aOo@)_iN_OU1X6TSyr#Do>@KxKf)A!*cRq(08s$o5`aF+LnW<|`$H2K z2bCGbn^EaV?U$fBZQmO=TALzOxl-5UBHTUSyu|ttco(* z!L~1?=R1LvwiyBmXtm4YLq=e!ldtoDOgu(Pan^oJVh|LeFi+-ls3j`sT*fN;j8Ixi z1#3vrKGyzg!|b!GITAE9$E3%~d5`~7sF-t=y&c>8AN&L%*>66h4YnSp_7{0%KQz*? zUNb5UVgN>U$?gljQl1I(wRHAraNZ_DhdP&fvcA^uTwkqit2*z?=Hu{sxN3$g(yXG+ z^E^En;GB*;3(4BCPI_Wl%a3GC!Q)|`+NqqegJAjeuSo_nQ2mqwduluzqU~jIi$EjK z?EQSsyVo_3Jt|jj%gSRP8djN`l;xL${^}_p-^2L4IGa5erM&GKyL$mY*S+f19nCsn z?qlp-X^x!nAeK1qlb$JQ$~~V&?Gzhy#qGw$yH>IaUaJXRY=!}V?RRXX!;6m9S4hQ&4+gN1)8Px%uH&ld zmtEVtMx|@A5#05Iz`xQT&*%eJ%!WmYHub&xPdWTPcApXQ?I~!(6gSQ-JNM;%OPx&Y z2|O>2X9)gF^5dvLq2v1p-z8UZ;$DAt{Al>>Z$QTs3Jf9hY6j8Wq7g4x(@f!6_QlKA zS-&-R5w3Ea=bl^7&HY2?n>EhhV|NPBG2j4t_FJP48!We+AX}>m;%M~D=V?9?YQxD; z3TH|ik^XM--s}xE-#o))X+y9v`0! z@O2bsH~VmN{)QbFHPZS>9SI?1xUrf5jP&W@=Vb>po}6NDlf;xvdXA1zp>MB&lO7+= z9RE)vUV@V0CB%N028Vh8hn@Vib{JH|1#Wg^oftmeu1Qbo;C?-l{>1jr>^)^FT*vV7 z)j#mD&~znp!GPRxe&*L^aWytjx^05Oy3d{uH2}UG`jkGFgM3N66u-la{OOx>ajd#? zh%_<(t3#*bOyPVWaQXFr${-+kBk#J}6NXp^XNNO*)=wl(vV%`NyyH1iQ`;J;3s0k( zre;+P8G7GC&P4)S~&HJHmtA;kMu3MXe3(6P%e$OsY9>9Zo+ zIs#b_EGn<_??kj)TCt=0F=O;9J~0x)i9TdokUbnt6>5z`Fk6)6z_^+g7^x4beXAvh zXZ2682i5y_)XQ7pb9AQ0)5eY#s-sXMgU+7PHmX$wU+6Q@!1;Gx9T;bZOU`UbH^GZN zCc3P+0*{eu6@9O7tRmO-njN{k?wdS!4vG6R>Na2V(CGGjNn;`HYg93}|LU5{PkI!b z2|ox;!Ae=js?*gf@SZa%@f7rkxCgiTE7wx9M%dBhccUZ89LjJ1zfHDwdHAEgS)*p} zHLA?5;-40&H2v+IMkz=$CuBdtyH{6pxapF`Ic;*Nkl3V6%Wl#LnOR^S09Zh$zkp%; zv4Il43ae0P_&b}OF}Ln&dgcG#9rz~$8J@id;$a@Xn@wEF08F2s6QuJ`yR`_%oRf^R z=AmeMwQE_Z`4aBaDsN*DJAGz>m7&KylT; zcVXP``K*Y}X2^RFI^XfD#oTwj_hF1?KR^%v<_4lyhQiPAc;}y2gMRjttP6A1*MHfv ztHjIU{Zq470hc{)S=K6cdB=YrbN@T&oqbZfv6mkogU;%kc}h(>Ng;?zX;8Ag8aH@k zCg`#}=q8It3s3$NN2-2vhx$`~x4W8_=4oD~{xmF+Cg@yxSX=>Fpg$d3?$}+@`+>f7 zdy7Q!p#BH;ZMWtzw|85yGxh9eJFXti#d<7K^cB#4ma72{^qD;ja>v9?=lIjCw-@Z{ zG5NqAmq2_qImexNFMD~!*9afT-MOpDj=dWdl*1wP{W18~(XhK8;1w!Y*^w=kYqRW) zZH?-#7iqlKK6>MU-|CI-ktl!V*Y+kU$NOBNPq^@X{pn5^Sm~}v^yt3&X!Qkge?5=; zCppGj#+~nG*fwH0J6ucrSM?Qt5Z&;b{vbTqwTSx5KmDEh-+RW1)Q8+8dZ%sKmz8~- z?^_yBH9+SS0RUi*lIW62FAy^mszqiO=%bZrygQUm8fmO_KR;>P1?D>V@S)y3#qQo( z(f&F?Nx||4=$gF0clr|(`Sl)1BtOZ6y(IFIGt~Io7+>!ueFRtfj7ml-dtYmitR}m2 zE%YyCvxv>_7pGL{@T^{M4Rr2N^Thl6G*9A(zfX6~qRaah&@)wT{H2%vgE)v#CQiPC z?Td+Z%7V&l_Kw0%$7HwbxA0Gwdh%f2VS4~SOVx+}-_bh?vgKudKE-r8A?*S(JOf@S z8><`d#UB|&g8lR*?4j}@Wb#qfOF7}}zSQ&B4;3%)U5QKQhl}t&ey-GfY2G7#e0>w` z<>7Xc zIu~yAuUJs)g0vzSx1=Be>T->t-cJPGAwn%|N)>xa!b@i6ud8InzJAH{PUM#)_AC2w zzE|k0kq-I&Du&~@zpvmo-qq`(H*cD{pgJ|s5XGZjKk=LNT}`s3$f&b8UMkU0_Fdw= zzww8-XsZaA+`PWumYe;!x|n3Edv})nt|r^G?v}^M2_+{HEhvkD@;C?{Aub?JVk(Y! z9z9y5Q>t8ul5pRZ5WMKMUrAjT`29{3^LPpb^2u26EqlK6ZD{maj6JS8^0Chq$#AV^ zALgu~$5RDl0;L7kbQCfF_=)*9?^idATid%^>%718;vNwLX4L+FnQnV0zQh3p3%Kz_ zNsT3UIcbB{j)MRUQyJcPDJ+}2niGK%*ask=C*c%(KY|RNB+R%xq{&Ezk{b6=0nn za%JxbGOSWib_hW14L&xQ$ENAK-CKMFEm}MI+&dI0C%16`!B^0WoE$hy(ZyP$DG#IEnilzptW@E7#F7IsW=R5; zmL)%6sN|SaQO}xWGAo+EGabV@izWbi%#1i#nH!lriFnG)Sc7;Q zHtI1g@^EPe5TcB*WN8`P(Z*KBnpw3Dp^jhTei%;zkmrn6d7J=rTr46)sbDIhDb{c4 z$3~y;e&y9q^GOlN8v$@I2^WwD5Go9&ULvO#Wt5f%^^C6rbWZ4X?`a4pv6@&tuu>-Z z3{|2 z&IQmQFExbc8Jn9tM8fdX#s?`Glat3OWxSG{BXMt&k(E*au0>5|K7^dy-s$nIWt&GA zL<*UhAZ8#A-Z(LJCG!x%@Y2Q{0|~YJA`sQ>bVopL4`xnNCq+q6PEka~xq)^y8e%uL z9z_tyVV2p>PNehUMr6Vk9#Q6UPm%~{Zg(}P#sJ{+=p@yg0U|lFYi3v>J*FfTMN>33 z-;Linb9S#-hp%f4P~)$u1ki#UK)BCp&rJ9N}Yj zttVm|qk8SQ*5?DqBLU0T9EtNXygZn!A}d@f_Uy=*WyEKJWAxaDp$JhS^bX>7dp*S- z&iWGbqA|24xaeXcdiHYqnsvpnZfwm^<9TnfcRF)Kr9$kVmH3dOc8wc2z9*I4h> z(oP{-AOS%v0x#cEW?G8a$=FeFaZ6cgpHgaiV@qpi`EM_oqha0s{1g+-znadhWj4Mzw0Bm~ zeATZ6bM}fGPd89QphO|12h?(;7-!^53)vrh{x>NrC0-#=d}3@b<9F9qw}?~3(|yj@d) z8{Rueyt+W&*$Bj@#oEDFdm2L)e$tv4>q-(er1>Dv4wL+8lrfVY*V7Q1za9T{?`Z3+Cx zdF^JI+arsu@T?7EHH?WnN7fDs0p1~qWM2WPD#MVNYDPGQ_o6ey-3R9F%?(jwh+u^7 zy^d;>6kEg-VS-X?6_@W6{aY+ZoNaWwvRDaBzJR}0zb!=nm!Hj74g2nHM`%V9vSI!6 z`08N%KWAQxcbtXFzzg3~G|C$ullw4mI$iGvV6b(h326J+1JGbiv-gJ7(qpU-#tG{_ z{lDCOd_Y7O*DvfFjjo$d5XInn&3ODbSOEM3X?uhG>oJnyd^d## zxo{^7??GWkVSh{~ZYKa4GiVl@Yraiu=E8`tCVrh<%=``@E%`Jvat(VUmmwotvF#1F zKaXK(bcn8?#}dyF`v`BQ&}I~3rniFOxG|9tMgQ7wk9BX*6rbB=2bkMW?5`BM*5hA& z+rHb8H#5JUVPMvrIoMRWeOzZc-!|f1F|IG_Q7yZ}cw^V(O!#|kqW}Kk-?dMGzlcPC zfP9~Ih~Zs;v>c;*qH2K!nBBDF!bhA;PM6%VmDP1`AQW$!@D<{x)6s&yv)A%)VUG<< zer`Xu^SA}Pu^NPT_v-pvKZ$>7l5DvjA<%#;o!xf*dCL3RpB^=M0-sLr%s z0amI{VaJ}-$YzMSSU(~RUCzrI~4k(9#K|8EPP7`udlmwYumHD@X60I)tE=83oRpE~-C$$NIiK+VO z&i@o~`T{+T3BGm4tW3Fyg*jEz)Bv93Z1~gdpHtjTUl&ht;A)}RSscHibP5g(#C(FL ztCYs**qoyOgg-)Ob?BkT3KQ_!|NY{J4T|(lgzfO7J{A|pKQ?Q8%2xNkxt+lB7IM8~ zoEMD`LR9^+s8vpRp_br}0Ply*h`Hh@bdjEH0< z^{g-KU#ve~ccVE$8TdAxnVlbipVqSKX*_ssohu%7tA5HfHoBTIW>rQ-6=Q$=*p5>} zj91#<@Og;5b~s=;AMFL#>FD5on_BOpZa{E&{StB)?D-Mq0{^`WPCadT;X zZ*y(_w5fEhuFWdYc7vxAykgbl+&#`MUHbZyMZ&8(S@*$?i&SO=Dm7o5}#@$K1&;bObusNT`(rR+Vj5Vjhb3J;o^}(QN-ax7$o1LnVyZG`wh$K-+WilWkuZ$Fr$rsa9#K>1yXS~% zB8KE&=VlEOJcY*$c~b7JnnMG^sQcGaq(=Vo5|*e|png==A65k2n+vf3x@7{zdH|D= zu_8$)M1YvmYLsO|Al1?fLV&ORRO;-PD|6W)|9^ox%7pp-%I9Jog0FoavER*JOe9=I z`+;Oq9YL5<)YB;wx$U&XlInx&U<;@5PdTXXTM1?`qTGVvA@)~sXuz`U0Z0G z327F@I3G8VGX*EuT;&&i^4AzA*wi@pw=~b_pel#0%}j<&a;Q^UpruwrFoH78{1Ac{ z0t8!DZMK}bqLqEBzb~;fuvJ2=t=oR}IRBooRbr==CZ#Z+gg%rRg{&muzMnOtEW za;~WE?f33}7kR7Ov>bk_gSyQ<(;)OOb9Cu3i}DxtSBEF5xqWO~Q6kS80RBkdX#lWZ zD=oG>OatZ*p%_J0o-1QSZ%3HicQ-NL@4NzY$Z+&|q{FD%S|#GKf;$TiVcvQu6e`9R z4)YNLGE8+6i+rJ0ODp2*VRH`&;Nz^sJsVFkITpZLJ`4szrUsJ0g}z=D6K5;vvtF(j zvGayo`mLs`U2o>wYyQu34qHrFnZG2<#n7^3M#k z9z-2V)Q5nb`pa_xnt#ML^@v}iE(t`1zJt?emx{tE)@tqH(r^(d(;4oS-q;Lz@M#l8 z(FH3n?Aa(%w4`nzryYpltBOmwpiJt4oUtuJ+wBF1mbA< z8BHrSorQuu1U)@;5^Yj#Q&y7i3iLX9bP<)utM$MNu+gkpN6E2`Ywkrw4}?+Bm;3NE|%$& zr2#D6GBRvr!BES(44H*vYo{_^kIqYyL#9mthyVbM%zzm{0slX~|F`??cHG@Ny~TFr zV!4vD+jhHc=Ifevc1A|J)k4s;(8g|o)?o$oU|Up<7{XG46yyo0A<|L^0Z%e~0g`2x zghIf;lb%5O5^l(bu>a|-d%C*K2s1@D6G;R&cp}N;FciQ{Ns^%ik`bIFDBLh<5Ktgr z<2b@3VIOHA2-G532X)w>`WN;!7I)SyHtZ}btV*usKAo**vrw>m@7@pC{htK=f}D85 zDk~7Xz>RkswegPJG!u(+|Guvw?5S-Q8!qo~$o2NRA<7{(gnDlpGCAJwcq6W46pCUN z`cH3n{K#J`g}%W-`};Q62ax=v_kjz!UG{YeX{e{+w5o^T$HvXbDPduWu&j?7RE!gZ zOJzSlygPP(*jK~W3G{sP#cZhDIX{xu+vWF&?gw1k3vh?NPXAY)QajC|{+E zidX-$$n9<{=Cp{Ka$4Bf*lJnd$mxdvhW2!M^}}c9@;!9A{5t%b6L-fM%msw)z;!ZM zV=!KTliw|0iJ1#`EL*GUq|M=2>m0SV|36-P`6A2H!XJA+^efn*cxurBRR zc-p?)T5gh8-aXv$d$^mB%D^DVE`&7Ruf=_*?7fHrtSmT}mElNo6eQ8n1QJ343(*4e z$=47BXQUOof~_WMO7RE=gm@w}6xI-xB!aC25R@M50#RVnDjLO%<%)Wo6<{TaR(wdY z6cWa9vhS~n^#w#Fz&MQ*__!-(U+2|C4rG1z_T#Rm?l>YZEX~7Ugp(xNkkn>X#sM63 z9BBlQ&}Bi57gt+pHfwX*Y|7@z)-toLue@BzB6q{`jW@fa`0j*<_|~FcpQc1fB%Mf3 zBoro3zavozK|oR?`LQ6>C{`DHTXSuTOKY1I8%^csZX{#2HAPVn{Max|A_=8sGzgfJ zO%RI98CYfs4C3YL3T45wtGsCJro5BcAGlgYOflH}CaZ!VO491K_M*f<1A@{XY*GT1 zSz@ip30WM~@M{O*@_XF*>m-*S<HJ$&{_h@0GSY@WFrcf zC(guN#TC2DSA4mcS(J$7vcwn^=o#3J12|(E@h(gu6zT$9-P&NW%g$)eRuKxuvg^^l z+b#iTG8hB{5tb7u`1qso!$2UW-Rc5i-*H`OtF37@xtx3gYz*ppY4i5oY=X-(F?}to z1mkQal*($ROq(nygA&vaBs8d@><$3!0$%qBt!b>a8)>$&DRO%cQdr4w?)2CWfKEOM zF$=LOD0c1dVq$di>uv?>X4n8oAa)v9Pze*GpXtq*Td*LlLan-bwvEQ+qBd-cTfCvL z+V_f4FE9oqnLU3}YK%2lOX!g;r(+5mdk=&0C9!WVS35fuG+egEzFc36ih)Qa$}ET` zSz`%FSYud}i(amteiCieMY&?Ka z058S?q2cBoR%s4bY@I-5oC#uBCbxux&p1RfDTznL2gAjS zu@Wx}J7*L%RP!);jFV7dlAQD(npzXc*&CCe7Zhs&)d4$Qss6&!L^`9U!O)s-=arf4 z+~zP!0gNWboFx<`0b-xK1RT*uObA>ofnAlhr9iLR%AK@il(Bi-B{2>fJg0gi^!jjG zi+Lp+5XYgn%QuA#h)?0^ID)NVUKHJf(qe`n@wT_Kv!~E95*%p?$7=DIucT%#H-t!c zq$5IPQP|dSqpl1htc%@{*9C^Kx7O8fFA&q1S`rG39>LZ|0;xL_EQgl6qhRUm!gBkj z*ecQ90c;sLn-9L*#jWl4oRWN;CL41*(Bq5C5TbqXA_Zs&`~i$!ueFzB`MvVIU6yax zluSx5@UWZOr2N*!gqWd<2tsG$1zvl&`LwbUX%zsz|A0j=Z=LVqxp0Qg;VZ!9MrOsq z-}Np-gkziS7_b@fTcRCP0vu)>z_k6qMK)NxYHuHx1HHA&1Q!FVWK@gTriYP64|sV2 zD1bD!$Ld&CvFHv`};B)g(^9nva%>?4j= zquUpa@kF=^O<0Q<1SRarAK^dw^5>g|=j)g)H>~WfEt>0{vu@2!-0*FU^gC2OtVD|h z+F}XJa853n@*#->G~h9xkdg`6jrxs^%k%TL3ESm>#H1(hFyj$M1Q)Z;S$s~yrm!U$ zr{*nZf;Zm~&RkNLQ3tj`44-DH0w5ANTef#=X;3TY+nz`zIE9rrgHZukTRt_aQHYV4 zr!y!_O3Bh^0m^WOV!$G3q}z*#N9Sh6!ufRQ!@7DFG| z^Kf~d7$^`CKKWakKG|=@HX{*+M`IWVFT(;D;Q@=TkLCu+!#9=*v$@A$yHd4vK zXa-s}3BU`3iSF|97djgP%7X2gxq8_iVtaQV{ck@c(FOFxK7yjmBT6{JdHBsMqGoit z*bJ?_{1*bhaITS^#ddXRyNPvr88so-!L5(q)@~6^H>cd^s_W9*sV=I7BY{HvnV{m7 z!J7YHpToe*v-^!gILyLvHP)_N+BoI3eAcoid^Fc$vcltBHD&d?FqaInE`5jOzpqdi zELo9S(*1l)mU8qb8|!&VI)HwC@)$s_1YpgrR`LpKi;^H(l%qLkZfI~7-|GN}(d zpB7|Ld13m7p&SA1{jl*1nmH0drDJKm;2^KhzDV)^W0;o@j%S2@92p*hq8N$1DMW&gD;!<4QO;`HWdzSAp1a;!i+O24FbBpG0CZ@01{MxvZ6@4G zTBvdX$e?vsEmq4#@hv4;5IdMQ2g3XCBT}qz$-m+Sih$N%#al5(v*w;Ak{cBG8Wm+3 z+S|~$3=eql@>&;F1J!(=Zu*i-hiFE2wLFU2u>OxuLO6ZHra5=8vh^|gqWXymOvR`YHg!c)YWXdZ58hMwY4 zzFfmh@!o~e%f*QEdr8iAvl#q<1c<{tC$?8uI<}w=0LUZoT^PMqjVAGu`isg>_xRBi z1{b8Y&<&#lJ>_{J3(|LD^jkSN!oMeWSQms+xf$YFf8Dd)au~vjP8Sj{ej`S`)%c#P zlWZ2yy9oR~Nf$rKh6I$OTaP2{H}CHvr}TPxhRf!&Vgy!Zcnj z&8utL?BZ&a%jf7GyW_@^i;R{pRdgbrLCHHLb&?7k+HjK6Ib-3RWbNpp7*o5JzzyH| zqJ6eus)Xa8yPY@H4^KBM`{0i|)Q_x}k}tc%>T2+gT`i5qI*Xm5+|gd@mxJLjniS)x z9el)(LCc61qy1b%PN+qO=saQLHjg^)nrB8+W)O;-xV(YsdWXgK5h8LwxjpgncDe_7 zd%X9C^N)B)zJ}ekqviX2_gEKsx_y7^y!qB@NG%Z`;lu;nlMK1biyaOL!yM=R6H*_F z^^e6XpEB^4T8pjXIi5(zUO#_o&3MB;kK8-jd0rW33o{gF+BIH zQO?BK8lQ@C;OF&t_(VlZj2*%W;fU*Zc~6Y>=Ftkk@-m}!QpKE%3ydowN;rP(y|qZ$ z*1y^MRN+9*%lyk=Z#fDSccOv+0`QdiP2wf_=ls~0Z*yD(AC5N=d$d8gxYxN`TJ=M{vyt3|nyzc>(f-5KqQQ#*Dk^1Lxf}<2@B$ePX zK??K*WQ0|b!(vkLu*Ftyf#qQ0ZYt&N#akof`t6#Ljqt5De)+GQ5?6C(DnJEom$UD8 z0^Tu6dYI?;l^X|C4{5idlxo!8kAs0$bNi^rBa)Ea=?!UaD|xG%8*}5&J8Tqrer={W z;{Y%V1ffbMN(qf|?cfkT^eDN4v50GA(Rqe7Fo$q==dT&Bp(Xm1*{lEz^(gu_4MrMD5?ciBFBCu0de!#BC(fKawW zB&?ODV#&k#I~~-8FBk_>niExA+M>Q@ZwbatY`rW`7+_nQP0~dNg{30v`|tQjKj3mR zuQVGatDD+{Ow%hzb951NslQvk>D185kiO(C6I1h!*B1X3Q7bc*qnNMz1r5La5&GJ;TUL)z98E{)gm6nH3|*l-P~g{z*>EpBKFO>Fi1hbA|Dq!vZ=cI@ zb*mMNf4`T6m&Z)RXt2j7Z`If5jXt&WD=wBsDQ5^*s^38?O-skiAva`^cmh{$)OHg{ zK%nG3?jIXy&MkPOWY4+auHPNfWW_z%7IM;h#OFQT$zolj{}?9 zH>yalYsyi{l^;Wp%q}0kO za|m+Hd*Z?4f;y-r>k4e7lc7#UqiUhPq)x$zy_8{$C1yW846-) ze6Bag6R59J@$cQoaiMz?a-!{JcY0qt4P$W6?X=osTu-f8CXv4Hd%tSRx1wanx3|~7WR*`zv7gnqe+79JIxfve_g0@kr=&Pw zq90X9cj%<6x?ewvYpCq%6Q>Ktn)}S0eSc~TR87fEKO!N^!;!$jU5gKXIPO5X5-Cae zE}cQ)5!j?N7HyS;kVbJDXyz*1DA z5`p+0Qxyu4&@#$1kPQr!9(5o0S*p8A1;7@|QJxciauVr_OyVQ-`0jQ8u&rFMy3cKKh6F z`+fg}--u7YmTn~kG2_atn|gl=m&uGHc z4R;EPP!}8*%wj!J#U-myC-^fj-JG5y$Z;c=tK^Pw!=6Y^brbDDM9@R?n*RB=Ni+cG zO5~$HtzU#f4ukQzsb=2Up#K76c}f{ zhaZ{sVUIzR7X+OZ2sqLZWi%hCK_kEW1Yrs30mrBfI2kme+|tk&hVycvkgH?*ZIH^b z&&M)Hf2_tpbl!&+S0|&+8ZW7*K+r-D;+>Pj=_Ydwt(DQRP&x0dAzy8)wRC>90O@IzJdrX8#WXceO(09~B5}sCU`EJFe#ukkoz< zN%G#jgz9M&lztg!DKn!|a{9z9mPJS7%mY_H)Jv7kBXwWb8_uE|2^v;2za|$wh0orP z+iv&1vL<`tz-9#WE$hNYnRo*2yvBCB?X(`FD5<#2y zUGjmcGunEQW9OlsOEj#(^q#+QmK>tG&4b>3S6Va6v?i1&WxKm*MrdP!p<&?dDF$S* zd(l!<8NiK-hg8I;1*<|~sttBd+h&HEaTfWs^hPBDm3cd~vcRS5vm%2CpdXi52bi_-%S|5Y6O<=AQGIP~EBYqgQx3}J|#nKrrtg`M;AjqKIH%_<( zlVmB$-=C)D(v_;|DL6Hw6Jd~peZ8plkXQkci4XS_RFx{+ z7*s$uxc>6g|M;x(e!@#VF*@Z05w^e4tsizbr@pwJ;&IJ$9!lGglx*)?*KcoA>W2~h zvYGbv?U(Z+RNK|-dh3cmO#QM}0>b+{H|kPcrq*rz_(YBA>42FHg=vf5GwfLJIhv)sh7zqzKJJY81Ds`Nv~kF z)}8np%5D*tvNz!`QUCkw>~ET}PXArf=__JXqaGPTNO|3*iqY`912xS>X$9;kBBD-7%c|H92RbG`B!X!3bjGUNVZkIm5}&*P;x&~a1y@8E8GFP6J{ zLkT;G?Ll#yo0ItO`p%U+#?1k97ISr>=#;-ek0uGsBP1;v4x(sHNFqtulwhbO&yf-V zq!2myN$Vi5x5Fa6&Mo}Sb(5t^Th`_aABEQMTa*zS6X&=0UwNT_ zJg!3{lEfb=kEWZdK9Um5(7IyB$k(!=FhKzUnh_Fd7@CQZNGL13HbrXlu(auG_ z`4M+sOd6zr?5lj8biVgDl@NbW6a*=W2N_J3Ca53?IEmv$L;?dWtYl+#nxl=I8XCI) zY!fYy+Flv>k;!)t;-iCAH0?Tf4#EuIO6^t zmUFgNqyMxrOp2rOOF9iXety}w!{OJ$6wK+R^e;AfDC3p5Yh2VB9nw}AJAvbXtTD#1 zb;rO&T0C~OaiibbO@#LEa5_hwhakE=LPnGtnua5H&d76ZP{ut|V{kq^D1jp;c&)2h zTM+t6BqHpSPiKYd&uRt$qhR@P5L3q}wAOMb6 z^S>jZ0#88S0ycw`Il(K1hdo`j9Z-(7oMfjx5G=Pn!5M06W_a*bD?|yW<#rACr$idm zy5Db3zU&lV37z7`t_A#2KXx_aisudD{ck0p@dOzxAJ>q-1;a^>G<2a0JDh?NPWqq{ zk{AicQDGn~ffOj}nGpq=BB+RIdJ3SwP^p~DFq2$LUI?kj&`4jij5&#qBnB{M3}T#= z2;>)X0sw(G(O>;sT5f^$!mc_IsLST%5{`INS)H2H6vvh1mi|tyF%W(|rCcdc& zLuUWhU(0}>hF!snp6L8J1{(FJ9KPKg0gJGUnrvwl4cdlDUT(V_7Us7FQE}bu1T2nr zjor4jXIDfwb(7EIPJb4bfveG0ORv4uk>s&BA@N{3lYnTf&Nyvyh93$%tlt?b$J@Xt zUt0MBP%xcx5zp)`4wnF{QgI9S#OL0Z@;{m0{D{4PWB-qpE+zRI^c_mB3nr6&pI zvv)nit~zuPAC%M2n-caotNs3r$aumpf6TvJl2z|^IwZ0tXA@xXNO2e>N3ayXhiQZs zNN15&6AS*cKc}i>q8#seT=gs7Ovx)am#hquHPy8U&19Y{hxyDR^Rcr6`y8jAot_;@ zO0Q2Y|B}a&vwBvQ>>Ufv=Pdb(MNH7ElSjdnlI&Io)k4NNqr2u-IXD-gn4xjEHp=bk zaZSW75mJ>q&}uI)5*#9akJpwrUm?%e1#dukLpM3~Yc@B^DiELRQdLOuTv877JD^TX z?*Kbt2yz05C>{lfT3DDo3=Po=BjwTzJ{P5ymNM2>wiu~ueOK3m-JkSNZa~)&=-0?l zBhArgjkZjbUaozbb#-zAR z4V&&U*KX@-vm&HC4yo>U*J#RGk^^X+ch2w{mF{yRwKT~O_0T9lgt0so7$guF0-3}T z#y^D&s52R5Bj$f`KsKFRc6nHP+$}IAfOwV@#u-$iMu36!NE!`*2@*-bVY?c3nm4%R zBb%4t0MMMPhM>MHN%F)XoriRFQ8GNqZPAIK&Z;=!4N5oQ&vcb*T;76;;sm2ZL=H(V!vw?@mWO&WS;pAev+-QKJ$A_(>4dEC5e9 zkTfDw2rLXQ$m0F}5*n<{`D?GdeXS43$Aa<(5@{U5V~Q9aC-@)114*c1d#0G)I?o&H z7$6~}yw=yy7yTiAjq9VapB%Y=^wzyBe(al49iKK1(Qa1XkY%&-KCe3ZG%Y=#6&3k_ z%vA4`07b%`YK<58{d#q3t)0lu^!ccRAZcXjt*~?kikO&8v*J9)x!V$Qp1r-GRn}OZ zV~5on6moRrD5rESa-?`YOW#J*HG}RW=NR^EPWWl3F-H(wwVEXEhHtRFh#6b}%2<2V ztESZr!KPp7BQd}mQq@mO<$|2WgqDNclX*93d+P*m<-%huqa_ajM$4VVY^L*=DRg&Z z$$fJla%&}|P#^%VL`RbLfXv4?tOr8{Z5?Uf#|l0CE| z3r*bJC{zCOV_XN=FwR>)N{vjKXSBv@i*V4~uDm$_bNW>kaj@p0HrjizdIg01oi#szseDMEb zmSw8wxL4s|zS?KA&dDO`)n2uJuU5rXtUnxMy|d4M!^J%&3uZm^gq3 z2zmfy7Y)NCH9+tbO{pKh)t|sd%)Yq+nL`;VnV9KZo%*wnn3Q^;;UeFJv}7^S0qnhB zGlb!ncjNePA^qcO$JYhqv|rGGOr@XFMC%y5cf9rmbW??J&}wEJ>x~j30@6UIM3KqV zL`6&s1d}`h3I+lD){mV~|ImALK5Q!U?C5TWE{#oqoyf@+j>UXi%jp@}&1!toEjT*+ zY2%bbQtY;aF}?G#Uni!uzqEI}o;%EGWKMhb6W>oVP^E|GPmaqIg@}6^7`t^++r|W- zjf>CJ{^x{$WCcJ^B4{j0nkJ`4nt&b8fMz5$n1mqPPw0Y-EOzGdhd^a3$o-o;C})w_ zc}Rrm@1tL{9FZqQ>rwD5@=96Io!Ra30047QPTyglk_)|6e(u?U6%F5< ze^mmx4gV2E^2vMSWiibRGY3Ff7D(a>^Aa^gk)$YJ6HDPu0XZca_oPRAZm0I{@xQu^ zL-&LN;S2jQ#C9I#>8lSMhS)}PPra_EU#||35s!2a?4MzM-fE>O**ON;E6*aGs-G{+ z<0aL5()!}iSjXskfInVJya{|aqLvnI_B1Umey^syc0WTsR1q)Wd7nnqS%RcvaX?6* zJhKD|%K#}uqcFKxQCu3(QKf6{V|_XYkV_;Fm3`^ME2N3nuWI_zu=-EkwW((ya&!^Gak(?Ig50$NK}zT8KDX6jLeoBd;`*`aE5R z@M`C{gy*t!??1RYS@h6{ohxR%0%-bk6K)e4NN$m~>%!T-w)S#DM(BX!&5<-3 zJBL%#-<+ho-gTxuU2^r`Lo`@B_5b;%WYdUL`Q{s$^5Db<;sl@L*U|_I`BR^E{pVa; z1&_qtkoOh=P4*D>r}@Z5;&|2R8Z;7Cr*rN9zoe&Nc&UX*GD`(=k(qP z_MO7-+o=;H^ib3i^U+K)rX-Kiq(wZV02vCwW+{mlDsIbqI*3G=O6e++@p2sy5CZ@J z07FwoBLDzjWi-|Pxnr%IONyEI1l@cbnL0KG@v`~UZLyPDeX_v_upZAY-XO`F}-wxG4u&0;ULQjtU@TU%vEMiAhb1REs5 zDixBAT3e|wvQ!XIL{ux%^Trnqb{)1V0*)L6yP3?HFB^!ovbk3df@5T zsJ*-Ng>jJ|{ByLEgjXk~ui0TythLC~MOg+VlaA~0@M4<`(OrV)rq7)`9IihbWZTg5 zaKp~`IJYN2s)spyju5)vBitgxEDVhh7Ko=uAzBEWAc(Yv9YHIaOQ%;(HB1J+*oWuD zL9@N^IK?`bspn>~Rij<_X7_b8Zc=PbX-Qc=SufxYHu!vBQOM&*ER}Y?tUho-?-6|K zd6$k~1J!;;m7S3@Lgv{^l_mZWOaVZmLOwk2M3lNBSuVjAG&f~HgVC@zIN59cJnptF zSqNS<%Nf>0j=&G7W!5lV)Bs%F+EmA>@VeOxE_=sOY{VOtU2#*OeW`zM%XcqUYJ~yw zt!&JbZBrM^vOmXBezo@=ilK!?6io7mk)imeK0_WHipFANJP<|%t4%#WMGnrp`~dUUV;hmbvQ$59cxbMd>BXb8%ib+p>c0FXZJT}BtE_FeJDIv?NqUA4mfE#6U7%YV7e#WI8UqdlK{?zsAQtSjb61#SYd9krOU!5 zL&wK8ez{|;E|H?`7!fUqTPO^HgaW=WLj&B_rBl7eN9wxPOoUnhF5s!fxZu5!;;C)h z`nzfe5C@jPXUdyKAI0`b7{28F1}$VR!TZ?Z?Nzsz3-*FijSJiQsqUlf`q`2SqjsBw zeC-Y;*Qpw_#gsG|mFB!MZ#NW+xl5(!v@dO8jHy5joJAD7I?~SPSW$0HOQx2V*6{|B zc3h*C`7+;j>T~kud0Ss@uJS0E)GcaIX{#{Hvf?UVPCof^S*j~5UwP^%YM!k_c}-vF zSV5VVI>=54oIRQE5To|`kaT7B^aT|=oe%hM0FDa%E=kGd%#(F>Jh{w5 zbp~39lDC3;MnDn!CBa2&B-23brDK$*ws|DHnk`Nc)>g@QR8r@jcE=HCt^?@4*}N< zBPMztS0mU3`@uUNdOFea+7oUANljX7w(ux7OXQc-LiU&Q=AE+0xJa zPchv!X81ynFV0`mQbOzSh5eZr8^St)on-~J;xmMt&w^b1^BlG);+yMC`qk4YXO>-p zmM;$%7FC4|-CbG6P7%2ePe;4iBr2Hm!n-3XeDd~>*Kf@-#v1CeNVHbVtVgx0(joE^ zO7hzCq*mxk&R?L;l=lS!P=HaOP))(&;1e+3Hf<&izDN{+!HMR>`w|O8z=~Xyl!5SR zpzSNNy{i$Gp>r|04OtAjw7FIeY zdKECA<~X)N>iSb==kFTy`>{qX}x+W+2)wR%@yURUn+TBR)ywjrxtti)}c*K!H8+!D>Ilo=12U@|^JB zmWYkVp!=HE^8Y9F){{wB<5X;2&o90lxKaKTZ(tj|`5bi6Ih#N#(~e>8-6Rvzi5zVo z9I&AL!Gl0GM#>R^C_sb=qXGhX3J!xmD#J`*0OF~jlcXWhXERt$fg827F{bTdZSAz} zy63+J6B%&rw5z}1T?5YXww%h~S_<@t@?!v5pq@o&hz)y0tQ1b%Q7{2L=~x-5MPRJz zh!_m$GqO|f!;RmWVma1#`JRq?SALXhcPs9^TiV;cm9SF^GXPpZrN7#%^!{2Hs52g6 z;*NF+0BF_v1C3Pt60*YxRUhu%{jldxGMAj`q4w6Kx#&-?t?^bjzPYQy->O$9kKDe@ z_dBQktT)yHnFqD=rgJaKWVBS$GjwioL`|a5*(JIeyK9zzbj=hr*Pj3tL-%+|U`g`F zzcX=8@T!Z%U}TI_us9pVMWh-AXJKiI2nZzKT(bqVUn&uBoZ_Z?aoLV;%u5|?3i%C< zP)B`>y{2-0`{ypxT+!{YzB7V({S9}&t>86BT8MX9e?2TyS zfULU<)px=@yyKsAY^x(QT&oy<*09jW9kdc!Cg}qGW+CY5d1%10z#6OSm|9bGeq&G} zwOPMZktwKFmnxjDVfN^9e5?9wBXWI<5I_fl!8jmEkj@GML>Za%2)K+2>z-NrhyzwJ zH@e=9+ol=D>o2HeN<5O>Ki_6IR@V=b|G}D7vD85sFV)c_3tPF#g>EZ8w zDj&l?2XGdU%>-l_S@H%gc#<{twUy;AV?8PXhW)+c}edx}|;znms6sz8%@o^l4UUt%KJc2pE6l(#yaV9#P9JiI z|GPfu%N=ch7Mt-PYIi*=DTu20c%b2NKKa)QC6#xKJN5_ww_GL3cLGSl>A3 zF4flS`%l$J!!a(qGCcCh6*%rH6D+wD2ea?yzry~IHZe}4k+)E_#BBd7-^^e zDg&LdAq$kF7T=X&1EPD4qYX?2Q+4`L9-7|jHBd76>DJ&EeI>ikxfaIkang z8}@0z$+|i0gZF&U8`>@9rN}$gnG;6e;`C6j4ShxDh30jF;}xXGD%WC-*3re^MlvN1 z*uL7(QH~8-h$>YYgRV$wDnejg>(DU+{oX3Sl2M2Wy)&^UCP)9JIe9$^D0c_ESNUw> zSS3d4xx zCsN-UCocCP`sGq>#7{QJVk>2ZcXBC<$=vch6)^|7u%I4*v?7Tc<#|66SxSkAZODH4 zshZT{Ei^*0xw6nZjr`N0AOeRuaAB|l*|(wyhe3*(z62*Kw;}rF%WCJ){wGX4qk|~6 zSXSUhbATAbK+WNKY_u8JQMXs+631BZ5CUKrD$(l*Va`J?$Z%$|TJ72k6|qltArv9x zy9M;qFVeS^Z&20pm>5Eqc&#x=T-Syp$Ur%w0TB0lET{BPIEA}bXb}FL5K5!z>!&QS zTJ%EVb|GYm+)CaDeSo7`uGf|zd#VgZ;IMFcLJSR{GVCDIF5{C8a!cE;F_1-8HOh@@ zSkgf#h$>`nP{TAz;J@U^Q$OuaQz4C0!(*?LJ>Yf9IZ)wd0PkOAADygRDQyMAfi99= zxMEeNRPxPTl4mKwTXAb3d|r5vSE3Bd9B|lZqr{_hI%7NtMQ3`w_$@lp-BnO*vy&I$gZ2hOZRc-7!3+-}ieW@!`;iRKhD15w1dHu9L#Gf9>fUrskj8 z&&FtL_Bo^KqV!Jm>Rq9rTpfT^SwF_9tnjIxmE?$oqQO8USqQxWd@E1n`{R#DpQI2M zgaT?5mR?$mTf37Kw!c)NDLIOjym2QhW-&;N*0||b9byluHn;Esf# z)SGm8tvvgtUTChgoV}&0+OjM!O^ws9dS z^(OZ!qN*GQsv;DpE49`(BCD+uUqKZ%R~QiSyPSeAe%fNH#NTWpHh5!BfpPTKy<#?A z#*oo_(Z~5c-1>2F*aTI>7R3ai&YSG<1#AvgW477WR&Om=s+_i^f7v%qWQ9@n5Hrf+ zOnN;g)`4~!a5Xb54)M#)!5~Pb73FQ+f>7K|Gi&(zA=76kc1vx##8G2Wr6;@C#J*Ft zY<;J^!5gf!+!6|1<0WibA+sES-f#0C0n$YpA{`0hcd>?XMX{$ehKS(V!)DlKf^Kiat^rj|)n@Ww7} zk7n;48RIX?I8xr&7vW|^3$~`h7^q}mBM*WYgrE$a0z_k`!$4gV8!oHX##txY^Ts>5 zdKo&;)BK037nNqjUUZWU`?pF;yHT}2xY|>D|Ms_Mr^>VF#~Lcc4||cWqBkF#eRm0T zgzce=Uc!b#WShW91bh@=llKJjnt=g7S|@O zT}wi;t>9wqDAWinUfSY;>Ip|-Syu8`4S)P3C@9*BQd^F~vTUSqDfp$;TR3THF4&{Q za`l#Hfv2new#MUiA_i_|JsN6DLIY{Rmn3-BnrJV0c6r6@73PY`rvN5~dqNqr&3?p< zN7HX<3?(67tLM=bo^!;d5r@?<2AG#*gHc{4^lfg=DmVau-~P{J+Dzcfv|EN9`?II-Aop)t3BAkS0Bc#o&xBgBr}}3KTCb4V za?Y+18+{UdacWq|Ez$~mfHgJVAgF<29r>HXKc6nCqjJ_W*HMjLOc?lRSuDxv2iBrm zbh5_s7(41hLo>vM{UQF*VwvqlE##`=A+J5+ zpmL+QL6%KnEp8kie=p1uU52@zSr?YMVbPc@ZV_ZAVYh83+uD37%TvJ*M8sGa%UF}g>O za@+5}g`R47M}|puur8{XDWC0<;+6bxr41UUT;daF70|+mU)n!$jcw3-HjKLHzr9!o zsvL>5*24VXETM4H8p)sPMXk}uCJAtSZg68{=0m@#W?&H*Y0zqm;y@|~Ce=(U&9=oS$9Uo{!h)pbc<9w~d6Z^c*d zf=Jd?#jm?2=TEI@+3MvvPb&MjaJeWsG4%zR$BmK%>gVOz&=}LWTfumJ6r*_Z|GRNs z|JI>OI&L<>LA?zrQi`ftdEb2&?X)bvB~iUOcf-75c#;bGN~-R(KrtqSZuW~;QM8ya z4v(d;7|hF~8D z+Y8h+aKC2JrQVB_R!kQT^gsQ}2;g_7p!YNZ!6OLtJa-K4UcjG3?W+x~h__t5#C>|Yi*8p2Q}M|G0G|Ce z2qvZ2AMv<%iAcaRogZ*B(9x!ticjbRwmd(H2((qV7^3R(CD|1CoPH|X+a5`W|DE`@ z_NH3*X+uui_1MFm+iBWP({|ssLz@$CyLzli8}2@GJ%y;J1y4m~+gwN+?sRFQ8*SHE zje#3g9X1G|``e?NW0wrg5W})CK#c^mvcE^n21?+FDaAU@o@AS{rO?woKd*m@_mywB z-W-2w?6^y!Pd8CTC`2^$bd$p`I&3$Y271yoK+KBKgAho-Xu=f7HZ-|FnuE!7i!f~y z@*6by(^61W!%vz)@$H{3nK1#0>+lu>p&(_2-=sR`ASH+~0Ukxeg%hJ7AoOxLaLgPH zM#4S~Kovseq6d7~pY~`*nt5^fOQ(bS$MgmD75tdRJ#8_{u)lH7l)qgS!4gF^|33cK z!K{;iUxDj(>KHt|RhUqTo0vy(X!pHS#Kb@KCwOVyLym)Xn6!=+yo6cPcO+zk9Oltr z*MQMsmLq2vX@(XCVmMJ2i$OwP4ri5{WeyYw=IE1vc}0|ZCKuOSas@_iS1b9wF)Q+# zHh5xA0m>Qz$7o=dAgJRAigRH{g!>sHk0gaRDqMPp3WR@jrdpj>X4&Y};2EEC51iWY z;Hx82+-5CVcXFy8kXv$>7HjZioNN-sCy4|igmfPtLrEe=4g|#N0~A>pB=S|b+}vPM zrpVD&Tw>j$L;h5!!CJ}h ztck;RvcVE_Br?rc4ZkdfkJ6G93=j+CQy3gXz@T1q^-vTDk=^Q}BbU@?mQbEgCfFpY z*3WTrE4->FCsBg{D8tdQFbs15@h=Ur)+&oVmjNJ;%D!>#Kq%^_e%jWeJ@*zW^Jz8P zAQje6af%zf+hYnG^SM0446=bZA;1&G;&v!;g9dp(Y5`YSXe{envjlFt0 zeb;(Y^=4m4y2mvmyK}43^g|bgc}#W$4SB$mh>pmiOGN1K3UtE^CC~*277%DLNVv#j zQh=Vj5Q@?%M_VHAzLzcK`L(!E>5Ik&1caLWH%@GWXnWVbJW#7cqQYh=Kpqsv#~?8p zoFm*_1bHI|#1{ggyPNa+==jF5b4CC6wbmU~RA?RdhK*S&%P$uh-uv1OorzpkBEK`F zc)3nCjA01UWeFwe+y_(pjE5TcD!KwnXV^+0KHKq&8~NWYY(qjqA@N(V8v z2Q+=l+*dW5m^74|{w4la>#n>&RedW~A@V#nCmZ}VFrXR;8b6FyNAc4*F_9r1VD1`( zKVD-9hH8W=X$lSaNNooOU%j5c5ZVEs=9v1<3u*FgO}}O zp~*mr0%CTPVPFr(e9#CdE@^W#^K>f4JVrvLOa|Hl`HN+k&HPqhO~zGHt}6MJB0Vo$ z8QGP4^FM0wou(K0OCsd`Pd0-9oLcaa5r`tf;!pw>08W&|>ITUz+BkyRx(t+qN|mL) z_&bwcK`TwStWpw%GRgNd*^mT0C=v>%rFs3>X@oQYj+3w~I|zY>6C)KbsW2-Ls8r3X zs-}-NECsXAvYW2;uOu}?FsQ%FG)Vx65D)_Z002V+Mk4?KPi1G-wSbdIY`}NIyLmA( zz$s6Rz&jCN~p_^?mKqQGLhPlH|=;L z!qX&z@e_e3lnQLMHQEqZrA=&&Cp0mEF)TuK$wExv`4S}<;|Xj*pB5^^j*tnRCXp-3 zB1{~sf03f2ve5_s3{JK!7K%ldB~>bNP3Dvw4FtASnRi=IGVeE;euu~?aE$`mX)_cH@di_ zwSIO4%kk!EDewacN!>+Njl{cE0JwK+cQHoPFPgRCmXjRs>{V5rHM?C6Y+d`?(`pk( z7d*i$O%1zMsp_BNM`$oK?l>X#%@2a5sc4LcJKG0NT}tYsYw9I|tFvn#d;V|S)V5YN zy8;4cwENFi&Ke)Revu`7cnEPj-s6w(0KHY8ffpg)<;AQQ-Y*0UuHDSVq|j z@{`W}&{B2JqS~{(a3vK@H^9CbPRL%9)BUfOva&9)9yO7PszG*nYAJQYk5ylMUCzmQv;E#J{25{Zo{V6}a=$yH5w+{o@ajf{t(VaW-)nNZ_SH#O z6$6JbG@3+>#nQ+GGom_FF~qDR(edlAa71Gs2z>U}IHG;M2wv03;dfO!_j_7S7rvFC z!-n|Y!Zvz;eE5vMqu;>#`G3w)7I_=ilj2&?#jHhJXC?QiOAcqy4fvWO?QjO3b`8zo;lOFoVB65!5uPcWo zlM^hh#79P?CSokwyVX619-jF{fm4+s^O>;m-B8&tj>+EROJ;S{cQtXWPj`!0+r&k2 zbbo95C)`y%@%xGGqoNOH_V1m##6HNHi4q7XA6=L6M2#QO-4<9AUtbGrv*bcxoXqYOI)gtgLs#Q*6}#xcf(IP zqWF|$M||U(Z)N3%F8nSS%(H%7n8)I2zN{RqZvAW!WhqtpF@KhR4)zq`x$VJ9Hdp#) zyi~4N`B`VVe|lG51F*SOlCY-5kjYy8MxH|Tc=o^7E?s>x6jf3mhOSSKtEix#wS90j zV*jf(uL~u{5+c-gPthpk>+a?W@{@3a-|-ydP4qj)*5rx%?!Ilv(fn+;5K`@+OQK>ZcZFO@PPYXbQJ{^oAe^7koVJx=A^(nBe1dp%sLO zjwbM%V*~inDh<_jH#3Cw*YfBUH(}r2uf)}wlDFCxH~xAh3Nl6e&WHoI;w|OVyMp<5 z?X{i;=bZAkpL01^Zz+=+IVqy!f`+1^u&<`5)TP^FdinyYAid_A6y&;1?I5`MM&?@vlrh#+sI!%aW}VHoRqgb%=qU*1m@Vqr z>xtT4=pE*cY-_gTsABzY3`JgByP+b+;?7fv&ck-AK1DA@u+S*(RgEPTHU+At>fFPb zhg%aqQh1TYg~8rRVc9Riq#v$`iK&7fDCWtYzaf1U0>>9(|b@r`|Bidr*X<_V_YvabJCWRe1Km^60i!ifTED%patzP$q zV_(g=&wnlJ0(YauT{bJ5{!gl;WjFx@32&T0hJT3=8*IP#BRNnlJenUP02-8jS1Gf? z$aRUpN#5_{E4?w|(BdZ8{%~{pkuYXlm<4IBX!?m)M{OjbZdI_^VHr!7k(Ik&vhM*4 z5PSv)XK%JKNML|fz4tcqG`dh|6(l0`1_h2M7BWyHFh@n5k_pno*k-Lde)WvyUhc$N zsrFIltW|c+9c0?M>r8r%*d%mz8=a^c?3VR=lxO%|%yoT07$u#=Za(P6@4M;Q%igTU zzyVsneOaMx6?RFG042Rc)zR|ai(;%smEE!9_D|ThU7uR!@Ahj(zxVGu?o~YK;rW<% z1M*p4D~vMQxRb$jHN`opXRh-Eqf+OVKTEmylL3$r?&RpZWEAbs!$i9|i|Dd4Fyi?e z3*^vHY4>pco2k=J%C?{r)bcyJne-eul*icpRcXZX&-m^Ck7e4G@`d5z*m^i-yxE&~ zPkaUp=KnC95}vI#ps%?BJkg-d7~TTxQ&BMMpwrId9o%Spc(J@^y@^ZHBgn1?D>RYr&2h(f8Q)T}BuShS{-HNOZIe zJ(J6Fj}W%b>I_I@y@@o7@wB_WB{^g6++_Mm+};mGy(EaB_W*>3R*>}r%_iR7#8o$% zDrYr3U$KyhP(zgimlMK5>Z+FJ!e0YDLs9591Ujk_dYFkt`)DSlJb3)54#Yfou#kKJ zLjrIIeFoMcr!g=oU}z@?3B!!#Ls+%I9JAu16k)_J6H#1O9@H_$p}D&M$|`sp^Z?_^ z&TSMlx1kO|`SUiE0pLF~zo9-Ejlfdm4W%Et)$EZ+p|Gm3`2#aSSQC(#?uO1s?`DyJ z28Mcn(1Ju*=0B&L(!Vi$G%{1L%r>?Zkf-i^DTg_Q+nQ3}Kjqb`vg{WTG`obN=rl>2P={stL4+-TUzOOUm!S%(-LlN|t=Y?WLG97-cJ}JJ~S&O!$gX&fMl&A4Sf|5jJ14%ryase&JyxYR7_N9`>XU%M=I7e{~qLN57xTOdFL z-=MhB65TDR`-c5g-5nxi+1v?U0_JalLx19Hzd27Z=wELc~K)M^;Vj z`+7q;2lWpV5~q*9*4F?42oC@}3PkV?x_6%x`1+eUu#Ol~3e$k@Hmko(G2_fVG4WyG zevfp&Ph@10aS*dg8V4H+qC9{QE&y>JnrldUxullq%UA_fJQEYMB1y2#k&qnlh%%2y zZ_Xh~EFj(OrAy zre3hoKyI4{pbF<|`#Jqbq{56Lzcqp&T*3TEZ|eVuyEkJtV%OyE4stipr`DE6C5UQF06U?oJ#qF)y14>b!KdZttZ62z>1K_tjk`#ci6 z=%*w-nZRyO&~+QopacaFCA@J0HeAv^GHkIhV}p?l#_^jEkyBuT zNAi;pqF0g!OM@z|+8r9NPCOjR+E-2a4m5{F`R%nB)qqXSph1apau}S->xqFTLE`iR zz%I@vdWbIpumi&w{chUtA|HES-M7Be!YhT=kH?@3GT{-Jrcmu3HgJEVObE?$bRF)8ErQ|c-S<<*71_0Z~Enolu?*P>N^ijLdDPf3Y%mH=);DI0l zVo)RxLb3qRfD8Zt#(fASBWiU)SQgFo>P@I(%f4Km90t3T%H2&zEEBYl06%c>sY^p4 z$O6I?veZC9aRO}0L5xH-5i*j00>xwzW;oUBu6nC%gQ9iRnI1#4eIR@~FMIclcqHYp z1P{cp0syIbVI)bS2wPaxjkJ~^VL9pSq9&ndc%PDqLB#+7BsLU}0RsSL|9=cMbMImA zSh8rRfOA3?0}!sq+LjWXQ{u2y5D)_Z002WoMI!(JURA`Zx~t2!?Mh6%`ycD}g2w&2UBJdwz5b|=?Uwsq~dFRImc-KV!!wx&(54r{iWHQi}qx3WZ~ z)ilwAx@c5u0_abl9U}WYn%)dZLefYc004{t7105JzX98O`|p{%+qQkb{hn@YOIq$` zx#c&Pxg@h?%Ox$Fja16E6bo6#6a`pB$c0pjBeckDxfMWLlR!X(5gv$E5U+uNc>v+X za1uceC+D!U1PF+>xaKz^!Ved8ZVr|U_qzB_AX>=Q9Yl~M=v#Dz2iOm}bZM!~>Znl} z*jdwd&01{y`^XLm7jVfhv7IA@swynT>Z$ki`*K19g~ie}cnVia!#&ZpR3z>Q+7R5?9ev6gO9-;0I2+r}?} zeA0;}0e;``fwQ0YTvHm&dFX$)KZ%$Yk^$Ck@4n<(<=<+w-jBckUla&`WDAA+yJDoI?5^I{ zvVT^eVdznJA-sQ2_4(Ajgd*kxhIc5)bGq)f%^dG6htYor+~)Vg8H^91xqF9iX-{o* z`ll!@KpI0EzTR_|CB5Xt%KLuizpKMlxaep-Xdh{UfnghVm!$h<-a6QRiVY$#e7V&h zSA9VC*)z~`URdxA@}#&{<@%&Q=f1aG_s2zPd_~D~=~mPUonNr_>CcciKiRhasomgh z-jApzp`@gpMR)eoNf-5gGX;$+veQRlEd42|G^nWT^{A9pM9kUlTi08wOoyq!thS=1 zeBf*V#aZ9e+64bFH>BVR=HNBh^m1x9f%#Fs7FL;&RXjoM#f9mF>fed29^&mDNh{06OF6Pr zJ)Lj!G;8l}Te9c}u_GY7vs^5+b||e~Q9IlP#L?VORI{F!zNKXsm(+on+iEJH?5pkA zs>tzt->}j!1?Vk}9X(@B*dDXkTfRjJ@K=kd1|vfilUw|bg1?=4|BPYJ`_kFTAm1CJ zTVFkI_*%^2u2&Lqd9C(9ecf8x{N_Ikb0z>s!e@LN?I>G{;?ym^%u6$G$DfJk)~M&S zh}pk5>*X7x6r>={amD_hZWmKGd*6%Eq!*!qwJShLbDb@hnzSB0S*Hd(R<^KpJSFS< zadgFTWm)$;wAb656gO(E4yji7+vsWkMjP@XNkfFn=7&QF;paSy@oam2#kt?zvm+V= z&w@Mr;G?3Np{dkkMOCiDoKb1ksi{P0ot*B5ilA*98WTPtOb?#M?wZY)zOlj9KEvD( zJq&f!zYygRNNPpz!(wGeINY&sN&j=5Y;?`OH^_HzvU5GQEPWCe+A+A#rwwUht~JaT zdsv-5KFDh0RDb+Ctkc_Ua|Y3YcIi1;Kl>xn8(l$G(#bK_oun!@E-2ui*3M2)Wx!~A z@zhYTs!l#y>y)>moETQ~{_mQ@)#SfL-~;V>1J3Fk{HORIqg#tHUdJZ;HqxtJ2E;SO zp13ha%!x}vLQI>p$T#}pO~~AMrQZ|ZKe9)+w+Pv0#LlVn#6HmegLqC|AVkjIse1~^ zeKfkHu*KL}8gd|x3ZO5eofTbN;Y9dGyl!kq(K&+|?PN|{)Oahn#Q7vt?BVTg^WF zr7V2+f1-VIJE^C^GHH=FX+u&M&oh62G;#<-Oy8 zFi&EB(UtH1xsWUHMM%-UF_p;hhOwlM&S?ueU5As)G@YaR_qrS~J=l1K@h9a-JH<;o zExtctkxy~(RpH)PdU(HUKN%m_7u=1FbrQ*hgati)3#x9n7($4Gq*XoB^GkY;EqUs z>SiQy!RpHt@<~bVO!5&0JiG*O%;M|{B{pKyhH($~O$W`m{<93GJz;y`%M9IEOp;`e zchdakUL`~{ukCJbzu+VEW4NqxOm@if%vq_D+qWo>CmKnr`v_7d(dua<3jW3pv-!cRU?0__etV z*BS)#b-Xy|p41*ZCv&gKwmtvb{jYVoA|>+(yaSc%zqE5D9n(gmSiJdRKro=(Uir`ykIkq#t;O6UClh1LQlijS$p}TNFF6^Y3 zxf->9U-J84ZjtSiCgFuLTTn?c>sDGnQ#aINwi>ON0UGj;TRl}LZV>-<77w=B9KoJ1uSe4R6s9n zJt}S}hnYu$$4-u>LHPyKPNHL_Fp&%76ER4M;qjp?NTFf43zhiRkK~jmQIR_83JRmj zi!P_KtDq71FyECeu^`&OF40m_QT{&R%@Si2cS$)yxNxXdU)UJJA}!uys5YbYb+uKM zELF!Cc%H>%m}uSOHYB&( zvMb18(QGbUW;r$5^WeXX6YYaa9(_ToZ_1%@sCx9CZY1&%z5_{9U3m%5jDn2uu$G9C zK`JP*=xH#SqNOj~h|-ZQ=!WQo)0N^d6w!s!=6npD?UNFv$SHe_#^@Fi2mF&&>MclR zqG)NSSQ|vqk_32AXut^1lVTc%EkPpgrW{rQqfMfqqfBZicq!7Mig;i^l!|du1diA) zNz69i93lCFn^cUbjg2ZBNHs)(Nqoc|NF*sbx{aEsEd#9R)H+9A6YNHb=Nzk!RMA)} zpvEEZ`x8O2CiPV>^Ddf2=824pcU(=lA%~;_4p39#5tPPA#R8HN4+sVr#vP9=&VOj2 zgIaDMtz3Bz$}h336+tc)7hworLA%vrG?+juABGXGD^q;8@)Icz(Q^L%)h9eEFYV_+ z{g|H3eUq8yWJz%{uP}p_#a)sE5di(-Rw@TlFw6kbSve3LKjaQGB6kNtS`OkR@j+PW zTO?F#QGH=Kh7&KzLqO39`nP}-RjVmeLsA%2S=*loSG|tZTL`8W(RNPQq&xvmB<3NM z@QjfVq#Pz1VGO-PInveVR1f!P4reZdX2ENvN3R;s-O0PrqQOe+^BXV1sOj8({x)2& zOMRL3HiEby0`gjYDH9=UkjMp!0BDe;%0dE7q=G^*?3xL;AvrYA;;=|-H6F#YX9?!W zRG?9P13i#@AF2^p5_^L_NO*+^a#VYkq|zlK0#$IN%0~55Y%Wcrntt>ZH;8`w=UfUZ zi<C?RF4ewY5#z3$ykIsWa5r%FZe zyKS^l2LJI!d_u%mXL}g!_Jv|rzE`WxT!r*BBVje$Ij48;`7zVV%k?c%lT$MDZ+SH` zeZAYyHjNc@wzC~erDB%H|jK}65*xGvL0>l}W zVR2%n)#?#(7Ix*+AQqtfmTRcEQ>2l8{VoYZRP6gw$k!~o^}AwBS?{lxpkz{C+lQ8> zaLxRXJCsN7_4Vw%iFqv!Zr2=x7Grt0EbK5#b?{j4`C)jwOMCD2XP_Tq+P<{=j`w1E zD*ax0&#b-U%yk~2W4^W5)Rxgcd!TQ-V~|PuR#TKZpVoAgn$}v=Pit#_ zbi?4AAR6=OpAK|%2lb{;a5|fgro+0qiGcw*G>p(}NT37+%%dF35SN-@*&Kv?Ci1{Z z>nm{PV*Tr?K2}YuQ%&$g-zF}GV6tjxDA6yO4$2I0+Pm69h0lN?hy*qPVWBvLoTL## z74KbD`f7Ryipq_FdjvY;#t7(yj>LW3a169y)KCVNm&5S$ATSGv$h@EBW15=K#eq*{m{5#etq5#)~Nm7=Cy&kHn2dQAxl3JKag znnGw_NAnfyzgIur=vP1JLShYlhc0aWs%LemzFss_VxyV5Rhgzj{dhZPD6dq7kpc_B zs7-r0Z%0Uq3bZyWVFiH*0->!F=11hfjVgICZx-b+M7GTNl*CJ&C3?%$ z(29By#POv)wEA8F<46{QVNz~DXfc|gv@KUyn#~Q=VTDU!DOiLh9O>%Ms*frraFlx! z(p|)mjWfc8kcSrzs;YOE{y0JJJ4j5#6fX#lO4&Y`WxiSF9G%Gt z@L^?wZ+}BSrD9xI9C){O=9u)q3q5-Dz!X?C9E957O!2J7FHZO{qXP=2#Nz;V3lFhU3C8K_LedvK+k4v(RWM ze-sjJ(xaw^evf2GXvL|LqbPGCgy~12!-Or8PblRf9(LzBh@=Lk;ytTM3#FYx(QB4! zB!^-21}a2|?;G33n4*Ugs0nGJyn{z3NxbV(EEOLnuNpN_c&y0xOb3ZplA0-sl12b} zoOUKj6B_*jdBKW7(zl#QQ+}fYhSH{?&Y(n}2)HDUiI$QoCxkMbfC5pyB!CICb07;2 zakMedR}zupL(4rXYACu|4UODiEYYIzj-@5O)s(J0~TFrbI_o;;53Y+?JxHDwCj^b}mk?G)@weWF#mCf?>jY zU7Qji%qX^;DzmCuiki>AN2d%6e3`I5o6t-)SryE0q(ice$2SUw1^yaEvKU zN)eJM2n;Mrx^aNl%z{gJ89BQ&yYpIpG9|963q5I7luUZfJTZW$69ZL~0g^n)h*!qa z_#GnzD-bEewHaSU?I2E^Pz+fUeE(Ov1WgnSZPPh`P$3W*9E`y&C($z7+>0S;C{^@x zO!J`c)L}&`!Za|a;I1(skgr1W1n@@m;sCF_+pFnHjtEq$s;tJMDw?R3PK%yoLR0`q zUN<@-y9S}&0?wxvYUZUc6lke3siLb`_y#+&#fy)-4*?{lU_f&~^2R|w8PQMy&j71& z`9ADcGNL=P=cQQuN`sqSVq78v;z53%_(Fv-?i!OYe!_Y97|4(Uq{^>2zqO`BRaMCE zt0$u8SA+8b*8S~qnZ=T2(Fxd}#}#6c1t?!CC+K*66`%2R=)Fkoh2W;Lky{flirS(g zLsC$}4Kjtq0T=^GFL4{>o+DA!MD-@&3pfP{%(E)9QDjuDQ+VF1BhJ+cy&I_=5G0eb zK^Q^d762Gl={R3wkw5^aO33gma*9+riHqwNA5~v*da!}!*+5CNmKfZOWHq(`+u`XT zUM3vda}U=buXKr$@bQ0dg-dPHhXap-reR~W^E1kkew$Hh+nv?aK_dfYTakL>_REeV zMhoPAl9mfvM^zjtwh%~F(QYo%ljoGp4s^ib1DG$8=$c3ofRntGMaI}Bqgt5i_#9J@ zlEO)6i#jy`z$Pha;nE&afJ|YKzO-`GSRm#=0q7pcC-Ff)6W&4yUKX67OYtG3`(%os zs)tSwIW?Q@4s$;_YQQnT-%gN1vG?gjkNGHPG*FS0E~+G{Z5UIPv>_mM5EOxqI3R+7 zppfz~w+K8*OSA?URfLr}R3F)Yw+xUz_v9qT(Gy9k3pgd24HaaQV)M!mU}+Pseg$DW zROOK>8^LHPit&;ZjxnZ?8%ARhF_;=9&I_S@9??HXqw0|AyX8K~0r;l_z%oq*25ek% zR0L8Nqrf;yBV#6SX~6rlx^-!>&YijF}+VJ&CoIo*`h zsLCe$SN!N7Nemd+3|Qx9{~gw706tN@J}bgQg&_Zw4W=o;nYtHdrW>|CYctl^U5Af< zS|#!fopW^qD9=4n71oDirgPfvbN_Q*``f)jsO&Q|(`aDoRNAiJbM+Jhykl-U@P#{J zJlrWYSEXYlRh+U#Og0P~@y~;s95Rs-?yv2%b7^Z%VlWvIq1&}?(;itBa!MOakKPfk z&;dvoM_`&T;ZK|J_`j&0xcZC7WCVR=RLnm+(+SPqPA^CGIig5)oFcqFF8TN5UoA4l zaX$RL_YVF2(00H$=UT|H2DP~dGFjsybAEMh(O$Qx*_IK)>?nVm=O2IAXfdY5v0Hed zr+dj4UC`&h*venI*CSo4v!E1- zpEcXa+-*B?F7a<9UaLC-n~&EGo0X1TrX- zEh4Fut$jZ9)8rlPUchE=U&dy?`XAs^vI$_8!vBAxzxmnQY^+(BN-WM31a7GJ=jc?h z2=3K?54PIFRjE5N7^A1c#_hIWuS(y!14^Up25(=zJAp?1*#ZE7Ff zacgds3;5w^(yTUa0#?mNhLdMa0a;m)VbS%(ull|R7yHK`d2E4hua6{O9K|6k_7#2Ka zeKFBT5af(mGlN(A5g(45789L$!9~Jt@T}J9%Q)FVM0&yd`PhxZ?{NU(-i3S%SRy{N zF$vuD9sAFnUp*2HNpdP2wm?9l0A)a$zcdV(L4e-dE?yk0i}nT?)wxplBxIq_-!uPje#+Z8c&V6E8uXvUL!qk8+?A4)m2e?*rXafNL7bc5O`>!DHydq! z=iIO%;dEzuLu^rosX4M~L0n3oax3o>-T-CAmG?z!UQ=IfU9I8Cd6ZXvA2(Ff-Q#dY zlls+d=nL8g+q&d_^&_Gy%zS{_IgD~u-eSE^UKjvn6YGD#oR#?bJeF(MQvGDyTv53A zX?)>xpcNFXq^GnH-W1HCO3V7!3xD(S6n964*4b!4Y*3CcG zB$uDZiEJrrI?_*Pl7Zsw$_cQ2CS|L6E!Ngo6530Ym=8Kfh$r0YRdP=yfb4kyf-dCkb&evUP_SZqeR5r`Gm zq5uF903w=zGlwhpZgyv8=4a-&W&M7)btWkaHoN^?x%Asvl2{1Zj4{L@NCb=od_iIG z3<3)y0e~mbRsjSF@VzT~RzAibaX0)!u-LOEO5?}=A^5IpNSi;&{sSTmyFZy(Co&v) z1DD*aasAj~A^SDJ{khqQTf^nX!6u6rQX;Wt%DWtYGTKDn;P~lY zorq9}k?^d2EG>*lq)jNca=4MktdG1Xdr-5uP#!KIkCZiPNi7gA?9pa~;6seMMN~p9 zWUFi}@O#sQcwr@5TiU~~rL;xy$AYH^G+7KQBg1n|?2TWIgP)8&O|$y8xUjCbv4KK@ z`$EXjPIos8Xx;$9$ryjTydQd^k# zS0~Q_(E4v7);MYCljMSSzm|LhzT=CWddJ+78H^>!=zokm2>X&x$rehbN z*q|vU*=<|@rL~k@U-k7w+acw|NAfs)TIE#Hg+u~Ji#}j4b@#{>s$pmSldQ_$pD|VZ ztdJQnoBd@^YW`dw@sIw8BSWN?%#o5;X(F7y=EIvdxUJB;yAgRfvNI0BYlc~o6m4r`YkR-gSRPCM z8_&BB{%#x1}MsUgg6{p)6= zuF10t312-u=i}hg z7&K|jb6yR6#Fb1uH&w$Q(Z29>tJi?mt07TQ%@NgG{f47!uJ-vH5nYONa(6{Q%RYZ8!G$m0*MjDwStjx?^C73ZbqBirZ zyQ}N0t*R|p-SV}Yr~_X$S2gipb2a}aPt~ngvvV~yS653IH7eH(>Wye7W2C|x8&af9 zA!sU8=89#lzm;u~^AFC2A);TDt*ER&a#sz%;#0TFa0iu3(vbCPH3O&v_0G_>crBST zb5p@8#zId9qcFR8s3{ilIT_}5$6dL`d^uNLx?R3}NHOn`Irv;TqJ6j#%DxR8_io-| z8*GOAlONNZDA%Ob<$bC=eS3A23f=>6SP;Ax_AU4!#H#}%Uj(3u$fmgaLGuF2akdRy zmw_*P8(t5L-+>b@eRMnRxXF8;I=(rN4X*iaY*Qig5g+SrY!|mZe$@4eHet_c?leu* zh=IE09hgOV>ReVot**@TuH##dPspERi$8ObqTS+@O_UO2b@6#( zy;{Zn^|Eq zSBj7mcB=fC>)aKH{zhoID^<&nod?M#0)Xb@$x*;wW^JR|9Lb^dlX_s8Z+=vd&=!%i zKCIQQ#OOu1(!4M3jl37wcPi=lJvdJpqkuA zRH2q6!JT!F`VEvj`pf!o0q4-rg5mnZ^BxP%`#s1!&aD4mW@T`5 zAQKeD_8j`qJ_)Z)X65Z<;5lwQWIjXUKPm0=PWYsL_?b5E*BSS>>ixY@jUP@e3iHT` z^=MnjZD@vvVO@7=4B z_+CcM^?jo6Ta<2$_2N97w@ji~ImnjuUF@%MeT;WCPtKWZMJYO#+Wc5PQ+SKwv(VRo zzJC9Gzl4v<`NH`Q*trS5w(g4h=2K+-Rf`0-oaiT`U|lag6yN0mBR~H9Z{dfa=EAcf zR6pjl&vNlVQ)JD=M#_Uev$!O1tvP=i)x*`UzY_km_fn_}t~G7M^%?e|AKtj;&(FpB^5rTL^zFiO;OgdZKE}Uzh7-uLSHi51VIBT^*Ke(xL~?{- zBjaPmUKAc>S$Z_O#nj#FKhJR z)S-FmeQ3_AZ$~VOZZ9{}knW}kw+iB^Xw`|k z%td#}om*0!azXu-i35@YA*j}Ja(A}j5JOvAUHd9Rj%*!s4bs{-lS=qL@y^3IT2;VfW}PiNgcytLP;4w*phDV`sa^*`lZ4YybPyoI0+g-d z%oN2;nT^W5X?IEl{MO5GuXhyMSHM^fgU-s?%C6_2O~L76Iwf$4zc6-Vc;M+<7$JL1 zn-;M~G9#d_))a6;cLIas)@NrQ3Vv^9?KQ|f@2#p%2hl-af~X3f%#~>fFeucaFGXEp zmQsbRPKO{*0X1#8w)TXD(+--moVcPHlt@%vqKRoSNZzh86C_zSq)-7`kHF#bQ8IM{ zN@CAJyNJ_gbfps{Z75(7th1^bY;!q)B@t%Zn`FMu7`XyjnQiXM-iF1egXj>Tsw+&i zgl+I0+omDVAchU18|Y149eRc?){X)@>q}bTY(rXQgWEJ(THIONS-e^d_Z_rnIeiQR03PB+fp8A8 ztRkW3s|Y`!qA}DUSoH$jt{(+0G6O!-L3ErI&YFfHra*wv<*rJyFEqVC7?{Li*vf2K)}FK1E1!?&! z$ZYIu`*s*15PVf&dqInk(*txjELRoG%3cSmN*0wC=D0Ei3^xf@vFj00gt_E6_&i4s_Si)vwevUSP`zVRTq5Nw$QBNbP^qR5v)uBi$qm6hL7qXNMivH51_!$ zR~&;B;UIZ`Smu(W<8~G{S0Ru=TY0!w?K)@>tMCkU%@wJ3462a#e;1QWA!BD)-eT)YSw7WIQgLYAFy# zgBcoAz+Bn2K-$06+7!#svgC9K9p?;vv~km@Z6u<>ubIoRDw>q%a-lWc7*z!n$@bgo zvSPV3P&geEG=g@%QbP}B; zD>Y?A@=Y^$)MrBaIhzu5#>%@r0nP=q0%T=$eb|k{+O@506WS}B4!U?6pAVuWMy!v2r?@j#1rM5oL*s&LfKIbVTh`LK~4> zY|=*dfC{)Hd)j`%!msCOvT!<@j?@b#MHdc`Hcl_tMil`tVK|c@RR^ep%!DK4Q6QRy zL1hmPZ=DrYClnt=vFV^X#e`!>&npl8PFvHI6-XDV4lKLOn9`%9-Gj`)A=p!PnJ@Qg z1|2lVI2G&{g_vSB#(E8$iP6i_+`0;33J$#HY>VzDkdXijjViE+s>!it;pNb|^Uq^kPW z1<=$c{3gNJk1kdKfVOKC;s15)Q)YGEGL{w`Fz*5m1+$wLp)5!SsukEmh!B+^HmV4V zlpv;>h`vw=*RrDwlGon0wXn3dq&M4z!%Gj3Vyx^Ansb~+pui=FG+d^vXv8QP(7|x; zGnhe8+Lr;N2(+{Jg4r64WCYOVkl!Du4{OmkI2BxY`nDzG8Fl)6`&^1|1)v~C>pok^quoC4>qlzh)%oj@FY%wb($+N&t&q1qn(_wT0 zAXB=xqwWE@h+_UPwYC@GTK#}4x*!71&fdx! z6tUP#Xd3!F-NP%7LTUjqq%`$;(adsH3Aun_Q-C64g&=ggw1)riJi|*lrk{$V9W(CX8fm7Ixl}*dSZV&!!QJBrcrl8q zZlF#9q5w6Fz>K0`kQv@rlzS42)~z5M04d;d_Q6-7uSE|(w(ge6Rlz&7>f zOR4Y!2r`+t`&{6s{Oj+IZzLuf4$W}U5T>x_2YkfN)meo(7C2@GJYYs)uBa$R0*XLH z!PZD9h7@oy`*tj>@;0JdB7a4S+fEF~n6;IG@yjesD(^wT>}6PQc0bD&hlL%*ous|Z z4`$^&;gn&A#0wkHY<+&sN9df5GG${iVZxTAL}e>RQQXc8unjC{foH)Ka5ejFA!6(q z{399N{=hbqL~SE10)K*CTfhC)6YzSs8?fDmE{ffjIlZXFH9`vivI*SGPMKJFUo&R>MLY~n^*Z;SPn~q^3UCdwYx(P7E>eQZmszgoxEf)U4?O?L?LrU0htKJ zSRJFkMg*B1XGjLmY8jATtS+!dbjqRGGNjm%Lew1l01hg_= zBb0G>iRpL#E;d1(GJ9{Y@eM(eNJd-ra+DZNjbX`PJ`=UDkOS|X{t1?A7?~u`~i-$>Vq_Yw`@E7>BW9^w_ZUmQ}*3YuRR=!6IR-i6%=atMu0|y zxS7GTFA1oCM0*YlVE?XptvSGB+?_vge*5_|CSdP6o+o-A3l_dH8DSXOX866QgIPB$ zyK1s@g)7&e8L`|n!n5m;izWttF0VLg=R%&Y%*10!Mk95Rbk;xI`hln^vrd*CD=5G) zGu#|V)FzaS8YBpKZ7= znsDSA!FV6Fo4JkA4Q*c$ZRlZEoU_q|(GW27)CMMsD;FlfAMC@mp~6a+%T}%CJ?U}- z96g@VoSsVHCUt0lJfxS|sHeC2OwUngp5@brL^l%W+;BWj^mQvVxfT6nf-0H+Y%(j% z6p5?7DVZVKY#m^lYW`6-SLXm;zu_1_9)(c^@YmBoPCNUG&sRLBLa(I_|x;T27n^wyXaO-$#@p3PLMg0~i7AkX3 zs^=**)>KgkNE?T9?OdLu&z(55H9Gl{5JjUAx22ERv_)7cT_dDxP|C6{BS4REsF@F+ zf|j)4)n{=1%*7h%$1i!-zU8?ZjhscX;v4KJ02GX5LSgf)!dFG8ZbhG*rZ@4w7 z2f2j{`TP$Mgcew>JoTf`JDx3(x-U7ZC<(N&!ov0;QA|&<;-~CbNSUnIY!JO;099qG zhDJaz{v#PxX;r?wwE^egulI_st8NMw3q(%pSDuT0I2h$atZRpi?FOO~J=%(GRtIW9cH5Rh@cG|TAL#$c4&_-)J-SyV<9Fh3^oZeS2FX$n3yObB76hcG^3|4 zN{Z_maP4^G@V5J*y4xj_eYu+Uz`AVm*Y&6M!+otZ;lEQQTfU?Kv%idn&d+&4imQ?;r7s_~rXRcJTYs2o3d0Av*Y%CyqX8FrTu%`bO?gLmV zafoT`YyVNmS=qiKDwG;0){hluV`WXwz)B~;iVXo+e=DknPngP`2?I^`2(Kd~`32l{ zolJ4(FP@lw9<04w7#sd>?k=7udTmWXPGaIq!9VNG0ONy2y%N=3U~@?!aiv9r{D9M} zdbW}LRwkA`Oc*EB?OS>H;+}4$Cp}m(d;h-?VM4p?J@JURy+`hu>SY7SJ|HS8y(WUz zbc8*>V9G5c?CCTT;2+`Er5MY9t_h+X!p`jdFiAM<57oj8ip7=dlJb8QXk{Qyd}?d(X>qNfM*dT-If=H4^+q8NamlB1&)N{IO79<5?+$Ogs^R4O(&%T;h2lh=wvvhxSwSQarlns* z<`cmc6QIgO87!>&Jes$&oB(%U=PcrFA&T?Ts=gjR`)49&hm_YV>EcuS-gow;?+LHk zM?~q*f+eR`oPa%rVRdrd#97%#Mb4YN;;yV?x`+^+3KtP`2!F{QiNnr9Mx@z>M(pf% zDhOZASrC+Sr2B9uT56WsQdXRsJ#dv~VPp~jq+ApkDyBsY=EPKVpPi!=bshzZ*LVUB!j1BqV7 z*_&kB3;M{!SHP#XpVR%nFM9GVvw{1g@(7}E>ztE#AK}RR6sIC}j3%nn`TSTPoZy3? zJy_T;)0BY$g_u}0BQ39;4y;35e}GfB(}eQYlfhep2Ij3MC-x72Bz(`$Khqt0tS>vr zcxB<{?(NxUG3k#z(#1*XWBSz+c}n1ypKluz-d2C%_Qr(oF#cPXfIa$T@%uk{R*&$1 z_%>tJ&=+MDfB0u!eDvu)aE~d~l9QT?uYI&=hLv$D`}j{V8@sv<19}Sic=1KNkinCG zOnHkP9_v|qP?poP6~^C?KdaV7&X=cmr&6D(bsA?i&W4!}&tFF`$iw{DEQeg3N zhodPaVQCGphc-#hb;aj7sZWTw&UO}RT=TUTH<4NV-IH3lvDEy|z=g}TCc$Fh)m>d% z-yBf<>`Tga(Mw#bHH*i`tKYkw)F-i3=3O-kmuhW;#lWk3qPD(5p!um`m+_abV64`i zShb0$zeyd@SLzV*Im^yQg<`d3NkE1o*wKeyrM1OVvJ4seG zj!bdyXvJ-(-wUgG*&agHR5CZ@N05e2XGXMZy zRj{bKE374`0tuYXCI4Tt$^j1heN4T%a@4`t;F)nkP_NzdEQBcTCF^_BY|R}lqEaI(P>hMqyPYn z01?pvfWJX+cjk6x?`G%SW#am+mfKuDGq%jVRl8*h){!k>2?17%04iYuQbkdA01Nnm zl9pA#W`tw|0r-003kVQ^f=Bs2#0ODC$^ah>`Qj5FYtg}aIFnzsq9YPzMGDdwi!#?j zoNgpUJlLX(m<^qy7HQU^=kk`7l1Sk}*S8Hytcbe|XE&>tw3;S>55}*ATB5a4D zZSaM?u6>|`GC%Z^`XTL%?;TU?>ssRiJA!T~x~jaZusOP}ue#Lkaxk*e^2V~s()iah zejzG<(!>!eF7@gjSX*CdaJFgx^WTjeKljDzVx%O$qVXmg_@RnDTM-< z_5(~nC@5p}*53?6n@g+dO2li$augmL_c(GD9v;3eIEQP)ABgjvH?exXBj|(QZK>YE zQWZ^*$aIcZmN5^hqNxBb;ON)CysbXUr&iU~SIu;iuTgB)ReoAS#1dQLbO?1UAR4jM zd@l0QtgtXu7@`|$K}5jYu~N%bw^(V;^3~0QmC(>aR?o0;w&MBcxA|~RQ=^-U#{lPT@Gribco;4vMsHc9!pcm!Q3+_(=bAUq5$ey zf+wwN1dBURUA##wy4oC#f2!lE%I(TyUAM~IE?&+CiLfX-k9e#N88bh-@P4;e;i3vt z5W`I1jF5bl?`Av)5*FE(w?lOb>#oL~>RmO3&3&D{b6r(yV-~pS9gc}$A!`j_G$<^v z?TEYWSn2!PqZj7{W^hQqo8-j8#TOxy7_C{&$l(z>>so*kI$etWmTDZ?oT|B#YZ{yE zrpWVqbiMZyXbg5&c`{uxky$lQQ&G+cx=jPdB;^>+G=7^Fj5K|_S(i5AW2}L7*fgVY zJ;05!#b@Ij{7=|$ZMG=*W~l;eP&kUse1MDAby^T+f7qtUOv|C zH-cs};syfiJN{bt+GXvX&#sdR5G6H5tGjB9Syhugi7@IQ6h**ql^ZT68xvM(-InXi zSiP353KUD7aZ@O!+;N!Kgf-2@x7ob0d_$?fO&tcPa+9Db3L>O|rRg$&AQMPZnJ%s? zyUVIOOB*Z7O3w%&9{1WiWub3AV`y|AP)5flq5pqDXi66B`G`lG_p#5P2vMb%mmZx$MQyTtOUt;*m6TASZN6 zE$<0E&dWG7tsVV2^}O)fU83W}C$cVIp! z2c5{Z3gMWi%Q|m2TkDQ1tgI3S>kP$vDy5O^0&r~bGSbJ1bq8rp8iSkhQe;?90nOpxhe!a*Ivj- zpeMP?>`Wprz|SvZr}t%zBir5Xl#+dazOIKS1f}on>P0Q3E@qi!0tnNk=(onPtg_C? z6Vn3Cs(bi-I_p&IqduUOo@s)YCTq{!yTS7r4p**diQjiPIWxmRAuZ_IN$v_ zTQ1v}rrZpXjEE>D&=DAgCovb8DHTm5RYF1Gt6a3zvTZx+mk2mR8IbtF+hB<@12vsU zoIWQ{=?gTXP!k{XT=mzDPK8Ru=f}loVFy`qRI=5vO`X{_6RQbk3fvFE()2*?S}|-p zjI$LfDnkLg%4%;?VY2Go#bYMA+FlMS`~~ne2dM`z4pUhviEio=9g?T0&?^Bu7y?=S zSn5`)wWqP7p`3DR7+ari(^HGVHoMFbRK02o9Nsn`rK&bXYX@^ssN4gTKnXW}Y7zrYe?+Xs~2JChJof1J4Cto1X?LWUrcAE#s$6 zbD~3fWGZX6`dO&iV#~W(=LSo;5b z3>g*J@o!Tan{6h8LC|FNYnx08p;sUF2chdgPtGH@0)6&H`sk<+yCr|p@&4lWeAeK;KOA`Bqvz=)VKf<66~Q)W(I{sGDfXl#iJHZeu#E8X-IJXxBURfFmtN~{;*%|T z>Dab=g`Vzxd3zxV`=Rw@o?`Mt%28ILYvXcx+g(>@Y3E+><@UH2i~*SA6{h{TdD)lW z>T2#KXE6j`I(W&g9)~&MckuP|^)}wmps*yiO61gy(_e-BX}F_=$y^{$=#>NfM3kYd z!prg?HVU@6#<)al)vBSbcws_%hBsTHZhbyUhRmGGLmn%xKgW_CEg(H~6lz9dW+7ZV zZ+J6E%<#`?xn%vX>pCrdxAtIG(rbAe0`JF~wlgzRvi{PiO8#ekm)K*ZSZz z)b5&ai0pgNTE3smIiAxNGLUp%BtRIY>AF9B+4h-8o-iL3!;Ru%@lTa(^Jvm%Li7lv z&%|+K%wMBI7&7zaLqgMJ@j);fS-h$Wm6_Oz3Emxx&m8skhgI(nXmc=}X+5t$UW1Od zJm(!RA^EZB*qMGk>Lz|V4)!f`c7$iuY*+GvmJF$lAY3#kA>|spiy*I3x~<8ZrOIX~ z5m0z<8BWef`?7$#94rgVmE`Ub2c`sbC61|<1naCkD>2u0vC8U&zqFGNwEGOsvitI! z8vTmHzjQZmDDM2dIZBI~S`cY7*O}zwst~{9y&xtGg?T_JmPutYh)tF(n{L`G?!`X% z66kdobo=rUn=$UTZ&ZL!KKoB>LQ6C6M(IUX_Nq@7PhphQ`jJeN5252*7xC4jkUkVN zZ_d!ayrILz-OrJSFJ6iQH&p?D^Df2GJ8@*zbyq7yOP~s5qH<8X&joGd*ShXjo=bqa z{B#^7eXPB4H{T$CE5-qDB)cLKu&?9;)-Sr|gMPBo>K9GiDia?LoV!{??C|BgsCogT z_vbpVCCPK);UtmSSWSZCq&dVSoJTGSP^pyIj^Uq~;eM)SS#sv4UT-936rv}D!CvAh zKlpmJAL|j3NfJ|!JHn5d(ov!gfsLZg;pfr%meso^j&)3EFUfkZ_Yaoh;|J{BFn_OW zTCQn;uVn!~jm7Hra3$W~y;S8}=Dx&h&)^*P9|zli!~V0^VI*Ve8tI#Le>c_I;OSE+ zjK64UTki}0foYeJXc9wwC-Y0d;2+o9E)FKXEUDST+v3vj;CHxZ5#iixE3s|0euICx z!+YG=$5G<Jt!m()+_(A*?trEgyOI;r8Lr9y@No7Py=K zH@%B1L$pix93#Y>K=Y|5UqZnfd<#a^uT<=R2B8^b`8Y}ZqZsnbO{43?>$06%KfHIH zK0CfKcO4WGeXkeE@`RoXLm$HqvkU?jS|k)e_jzft|ArIN7cJ&CFV}C+K8E<@&QcPH zt85;YW;JKimqI=1-sVzp$DFKgQzZCc#BD~Ya6iUqx5*4-4I9bD0_3r|AG zBl48$YV$=-H7Qj=&QoqRBK(W23g@)0Dy(Pn`(}iGJ6$93ZOKTNshz8i#`@05q*Rx|UDo`|z@SY5LLtKr!)HQvZ>z&1;8UJHq8hKu{& zS%O1FY;nBDvwmsqrNQq#!dR(mQS~7_dZsYpfGQ1kEzzx=@c|co|9C^QMecK#lAKS? zV&!hjg7qu~1la#OH&Bv^JRYwU{?8sC@Olai69m*EWt*!sGIZu6pnjZCZsxCosb&i* z7JMX9WZie6ILjcOVLC@A`#S|O*C3&v+woyDRLl;qZ?~Ib^zkSYjZ_NeON%lVSvga~ z3s)IVryFbd_vwo>L9gytV9Zqv`?K}THybJQ$I~@F3rq)b+90kWd+P<>XBv^Iy6wiF z?QHSz*rFWTx(Iize=Hv^%%Lr24^PyX_u5|rqQnRDYYC5Gq1&?Su%0YLsIJO{-b*DYlETKAKyJ0d9HVB^*VXq zv-N*WGT_0}gNUW;&Zx#DKynfmB`Ud<&uhgv=&x~joa_=<#9Q_J*csiIjYm9xyLEYY z^G*BnojpJe2MMb|=Jf3BSN}D-VIW)EzRvIq^6T`iu;&WzC8rIGc&7eC{L$ai#@gTC zpFU*mg=A3f?oocqvMY}Bk3P8m{?|D9*(q;VNE<*N@2N&f^cXnw0FLwl8a^ji$lWhvpTpzzb zJf0`<3VX5J2;0kO{PH6UE>0I?=~H39@|?s1KZWP(>4g49Ymno6l|r&@*9Wm4(dYRU z4EU#aQ)HZ|o%Gr{J-07A^;S5{V;AP&OapQ>U;&B%qT1>2-)nHKiyANE9y1FkWPh+~PA@7k z@{mmkBLC*vY>El?1&t~pKxndHAi1FFvkei&IQ{NPUJB(TuFHsl*YBnHgpga%ySZ{n zL`FD|h472YM-%BpF})?A+4}1n!~cSNHmzcV7AnZHF95Gy{|Wiv(HF}<1Q-St=h;3%u(`{6 zJ#1g`oqqf5C?XHZZ}4mWNa$8hm+2thqc*s{5Tn8*^%SQy3#OL%AOM?^U;N3oqiZ|^ zlU!!T%8n3)s1Jj+Q1y2F~wd2AMfC@qe zQhQwze>fLV8elth*sETKKmWzMy1t9w``(IKUIC(aqJ=l;>FGLesQF-TY5&VpUndW6 zro-j~&e++Sd-zJ)*j^Em8lMj>rV&5#xc@19f7ZX8B`C?bW^KdYdEAwAb;*kB!2t-rHUE3X$(H8urXR# z*(r@NW@xhGCv~3zE=~Wu)2^_vwC-}ItH{B}6Y@i;X%+nwyMbK{iU8f&@z!n8=(K9)A*hYTwT z^xT5Jy51SPW<3RVQXNUfr#*tjfq}uy;`<(h0}6UxVV6I6s7Fuo& z)}1$m=lo-V*LzOBP$_+w{kpIMtu7_{XYCvJ=qYo#^QF5yObK>GwPO-~lJ2?Ek~ z<=N2lgr4RF@%66N>p(0JX`__JIBBvQ3jxhIl88(I?2aUAC8AP^6sGN~E8tg=d+oNH znT(lN%s=?SWL@C}0GLxQY6?}9ATl?qE>uJAuYgStAvk8{%n624y7jp_7A9^08R9V}FxAb=# zUgWJzAE&Q!EHMB{kPD1;C}tzZIjUhXR1kQ1JjS6(+2nEkS!d#sK=`p?W#CI06h$4q zrz>qe8lPH*CnAzBK()a?d8DWx6X(OLZhCqmo%RtS;xcGd_+QNZ;XI@-O!x8()w`Tj z&=Wa_si4#YMfd1q7ZLJ<@8)(M=f%`}=uHAYPc$?SGqI*nq$k(OVBCm9lFV#AXjuMM zd_vofD?N)2RIjYhtN%9#FnP6cak$aoLdWNpQor{P#krH`cZ4Q1pycQ+{7jH7MN)CN zeVib2%mMLAvW(Re#NAM+#d{dleSMQWC$pQPpjw`EHdyobw}RasdxuB2bA_+DEixkk5CCkmqqs| zycc~UMviPp^Q*!Kg^$Btw7GAv8W(p3Bj1b7 z!SwA?J|5JSOsaTo91HK2+Q~hT6ZX_b-kkQl-DCnOPNpG}+Qm70ioNXYRIyC}B-Y%9 zNJ~^xu0(|ih6t~#vcnY2sZ$@lTAwZBxO3~ydiXu;Vgc;$%y)04egVZ3rVm#GG^jyz zZ%!PWY5(^Tsg3?r6TK(!OtyAjR0eFFgol(|`HK2m+;U9$I1?@hU8&G4?pIX2Kxz3v z0mz6nhSZqi5Ll*B0x30c(--pQw5ax(8+lD1STG3R_Vt(j_15yT6i@n~eMb0%JYf#| zi3i!uh5zvQ7PH)7@)pCrE^y-#wyM&c2!F>~sDoPs5{*1BKhjfdDD)C3BH+pjZut%IgYcASF{nVV>7ZC2n$c44WXB!d##WDwE>#3kQ}s)g?HTe zA8j2H@9!EoMnW)oEdFkE4}u?lPjjKyv;VL=+u+KwQR6>V(akr*dn&&K$c>8=K^@)K z@?v27%ACoMS53ak!A{8iQnm}v0&Q*KaDPGx5`HQBr`n(UWLIp=F@6oa@ z?ou+&spmo)|K~^f?3XtiapE({|GO*5jRIZoC2&Jr20BC?i^2pVArU2sTv#Yb$&nk% zS0Ac7_p2(%-zIHh-Hl`WAyMF3(7r0%mADgokvzY5Cxri(tHH;6Th-R$Fm(rbrqr`P zr%bU?#cF$@kqY#jB6rDO!pbmDG#Lly4+8aF7X0DbSB(~n$0^2%ck-bZiswIo3t<8U zodVJtgIK5Z+>F7mC=ec9Vw7pn)e3-ccb@B+nDd%HwZSy~g{{af{phL|F|U-81tbLM zN+=+a5t2o!RCmu#(y%C|3L|}$N6ljGPvfO1oEGJTzBSDO@A~>DO{h7!MkDNNSI_9` z>-!zj|9`Q4U#u(jgZ<1F-T0a*Vwzy=J?@e#)UA2eYpnR{`P=_Gnx20P*x+7#2+rTO zq6MAkbmylXJ-JqJufIEH;-nYW$%jKv1JkCqYf?J6zt7jSI-j?uYxsJ&rndg&Mz*)O z_6;d0#Em3W{hWb0=V-Rbe}PHtZF&+g5%)0!U|30_X^p#z|Q)ids zSWd9@ESg&E{OClOwDbM%c6^*B>_JG1Xp=S4f+I z@AjQtx}}&u#Qmu?A%lX^2&wjN-DZ(rk$%QWqjRd~q_Er(SlW*2{H(4k`AY3j-q9S7 z7XtU8ZLg{W$NAxo?U4C<@n8IU`YVSA$qL55yXETdd*oGP2*M7N(iIV7^=_NZF}$x> zNJv|YzLn7_c+c$gxj`ZVdt@Kfd0LC4FM>R$PQwY;L3-(4n4eXf(hFU^E~WQ{V}O36 zU?ndH$5%rc9J7Ce6T|_%lg%!`-+9pmmhXRt#Qxp&w`B!?vmov*0m7fT`1Sw#yVv;V zxAs<`u<_jeOFj!W1EA2821z2Cph1a?Z1)xEMQUlC=`hlvtW+&X;geUUw3;q z>l(sBV}ZZ(AO6>G$Q28DgZhZl2`^cl;8%xkd@;e_9>w+&Ubh^yDCryLpGp z{kx`X1-BW7&xN?&f3tfAjXDh?knX(2hufI2XB`a-Y~GsA$C- z=Rk3--E;N+wio0N1)9GkxkIjhchX@;PhN5m=F)Bh|F>N1X^S#?OPCdjx6rdtvCfCE8OP?mFf0^@ z8T|_y;8e(%~$QEN`gU5#78hxU7EjIv&KzF~MD9V3n-4S4I zzo8F@vBi#|(&P_sNzk8`;1^dOSk|)Pi_icB%Wg)PE`YE*yV&})qP={STYrJlD?-SG zpp-^dpbkr}#~i9BrX=}jD>AhzKoSLgGGoiS>-Oz@LSOJhOl7g9%XpATK|pGNbgCl* zMTsDTKEPqck=$|uQ>zCgKxV`+23X_8&@HMBUbDxBr3P3n1{m|jN-YCHKy{tT4mD${ z9XbVkY2#yr9gzN%)|D}(SSZB?EyL>V$ca;lP(iCrWaEk?{zEZr6AT%wrBwrx zt+I1Xv$%4XsjYxXUTo1abq%_R_XyY>0Z1sq@{peTWqkij=8{s47nB~M%oe>6;Tz1 z0zvW&61o9NOWC5X!j#KB*Z^g`D5!{a1XM+;KR-ey!c0~WFilV@6qwE*0ZB;Z45U&s zHozG#f@}atK~v5p1Qlt#MCd2aZW~MC$gCf`fWyT_+NoB5J23b%^jj4*L z8e|M9NF%mi=X-0aSEFRZT@ugj6ar6KO6H8bPNtSITx`-Z3LJoBI)F`Ppfd;rhBbnHMG8uB=lTK3h}kd<11#ZUj+W6tAIn~< zLUPjpgeXJ0hcZj#5GnhlBqe4CUl-FWE`ipym@+20u>s41R8TAl(~);jwka&K5{WE} zl_i9Z(sD?81CnCOIPOlFnd*#1#`Y~UEnhBO3c8381)`F|EwV`=A&E2*h(cDSNY2X+ zOgUs<`|C^inCk$e#@MK32uPgBo{5b>hnHMb<%Ghej0e^&Rg&av?0`DOT37?D@nV>k zP#_tIMntVpYa(D6F(461NJvu^t{+IQ%nq+}mO&d}EEsgP8j}(Ss40S!hKRvLL`Fgk zxS+K$pK?ip_0YT<(qfaD4Ngltnr zIT#yYY#5=ptu>#t`fj)mQp#%B23Q-$NG*|RQ9MYr(xL(wIG{nwLsd(JXB2+QBpqc3 zQzRvwEftvA`Sm*>u_{>AcRW1Q{M2=~ zu9NCQ2G|S6kS(M&UaG7^)pN&Tt2tc#O0TN48TZ8oE$ayaq@bLEF=9mI6!M0s?068%uE^9d*L(p< zQ`r}r{(p+WE0{6B*q&v>8nS@7g`&I0NR3g7EOO|gNT5>bF#)a0kc68Z+)nAbCTm?= z00Y9nn9N zEt4p*Ho)01$ZCloML-3DijFQwBS2~+WC+1POj(SsNPa303e(xzF;jq5VrN^-yz%DR0Y1smiQ3IqvLL`M_WCRp_Fw&9^ zSm36o$NvW;PGtbA5D)_Z001*YRU-fZZ)I-+0SakiRxfMQN7dCMk|umwA69U|Y1=69)Wt=P zgoYK2X{f0P#=zLSo2_7|XzksHTd)%BEQcNz2~^fHc~~i8bvZt6B3+hKNv^2n<>E%x z;&Spf$-tz@C2u|mr}zK}000b)fZ0F+-#)%=?b^Ney{-PgTkDfmgt>P6cfDReUAB9E zm!4-Nkc6m+Ab|lCf>FJ|6ySmZ@5BYG2^iKn51>GRaUg;LNBAJj0X_g92m5Wn)~2@3 z+UoA&+Pdo2+Ty6s1K9dy0G|r9|LKsS(XG3cN?cp~x-Mn+y6@YK9fk7&gYSH<@H+!N zkM}or8GsaK5ES4vXclS6oJD78Gn{e=BxVC55M2u~yTRY#@LXMcybtebAh_M3zeE3!lEo950s!pP?S)Mm-8`6LgYQq z_5}=oATp|4$XM19=61^FtWZ?0Ls*mbZQcZNrYJZGVgSP;c^sri5L7PMLLnvhQ03?b=XGlXCsgfXNSx4Q<-mbpx=E~0AtGBe3A+i>h{ScXCY@wq( z-3nQl!a*b+X#z!Xg9j<_v+uGgTT3Iz%8E)$k2n(``NJ}(AV=1xF5ic~D zX~t9Xj-yMoWyGaQfs{QT%hh{JD?6>Ng?($@+iI+xi_cPO{KMVi==l*EvTtny3N)3JaWsq$I73 zq>Va6Yu9FL{(#D92#d0w9#S4G#w3y=ZOYd|HXO&Ha+oUh5gK*V^2K=s-c}b%swW~W z$zszPr6~j0B2pS=O`wJ}Gywrmg_8h?))C)w&8aS5Vf#e|Muah0kTxWMbZ5ro zRvt2L^QGdXtE4Cz(}+?fK+dy{@A45fSX5O+LXt3P;oV|}V)+VQq#g?t4w^y?4T_2E z1oBULm$GQns3eK7C2OK}Nr{Fjs~IY^y&>!xB1zvBh_tUaNZREQxNR+zR8vG)lZ8kU zkEP2a2s{#JjzN+TMLjydD~W4S=Uxw*V?{It?(aNt%YmW?B*+eMHN4U(_75zuHOuY zD}SRD-Uz$Nz6?j#TWPM*JlQyJictF2(~Ge`(YTAdPl!7oM6e)^2P9T%+IKDQJD6DC z>+di3z8xZlWCNhcpp{5YnfSsyh@?>LB%$i0IK^W*O-yOZ4>yS%@t%nEeZ;f#ySliH zYdA2+mfVXubetrRe)&$6dpJ6X9cTKNHrx-OVx_Sc`7F9c)15o!lV#-&^3!Y$Orrz- z*xVm(=iX)?jm#B>!o%rFpBncMmm8Wm95&Ab<{-npFp19i^cY%=#XNrPF#AwvyBC%X z8F+&-k_HjnAi)}qr;F!Ic#DF&XJP;z_8G*&m76m}cA|(tcCmQRGn{*J?go2AZ(64c z(b=rA4ctoJBra#9dUA0*BrUU0H+0rHmp)KCZ{jp2=LMvHlOhR>l*#IWLLn+qiOvG) z#UgaB&-dA!1j(EE3N+-mM|!ig<5b0 zwBpgSNwne&pXeugQ7GZNjVPrMcS_Gd96S+#WM+P>_qDj#*tNj+f?Mo0286#wy{#-1 z2(`JME02;2W-48vX~s~gDh1Udy_%XJ<=2q`ix7J^^(5Kg2fK9knO2g}cK5}xlQ!`b z|3X74ZtVIzOAtq6LzLDp7RQ!a7FR7RMD3pDQ0!4joyYq&2nSIR=@2xX(HD9I`7C5> zlL&Fl4&9#_oa_VnruA;{gwDf%Z^k}K8jnefY!q;|9jj$k&WOk0Htus(0Dz!c;h9(dI?Da7y;YH`rgw=u# zP3bn;+yrH@6rBX}i$yq^Nlxd1PttL?L`@v$T2T~lJ_p}z*Cs`i=Po>gt!q#ubF*w! z=oh-0YkDeUlVlI0O;kjT6ov3&QGyczXAV=#L?Gpw^%wJL+I=`aw65OD%;FZzwyuf(qAb5lxlS*=udlL?GarX$8{(H}|LRMv?1fc5cM# zO|w2wi)C-Dcz5;wyO0}?O^bo{@B7ynF&PNVdRqC=t(L}P(!vWxp;CmbX>pRA>4zk< z6T+O(CgFxYmHCD+Z8YQ4L}c?M>?pL{C1-GSzEw{T5ygV@l0A|IGg+c;v^~L_;^o=x zCu&DF(ovvPEeX6QzA5y&(emdzO>FeDLR zTAv~hEj)uaCO?@6W*i*U%6LdxZJ|9%NNbi8*KlAG34^jj=cxKsWMp`0ier>U9IuGZ z0y)JZ-LOavff2^8>HEYfam!ySkO|0mtBv7JIJpzwu$LO1GdasLi;OLoP$Vj~1|cDv z1BpbCNTWG&7p6E(2qI=z5cno=JI{cOYZ0Xn2cPqtsV|R#W^BcB9zJj2v1HO@TN5LL zDck9tp%|!TgiudIp2vl1$L@nN4u*C+B~4l&P$($D@`6h-q<11jSnH`fXM{IOwEyYHmE%Cs3u9>&at`8f&Uq{M?WE># z;1kUUzbE-8U4*;I#@6M@Zm6vJvWJa~&8?l?t%cVrXS_m`8Y>y(uk8LXhr`O&zd4o^ zRowP3uvQj2&}Uwi>-{eK2SkX}{WUM*XYq^s%lvN^6VA~2AF%(3&cWx1(4i!f@1z_h zV_v`OGWoxX`~9JwhRxMhn2a25>{i?>JXfN3%x-O^{Q(vJaftwXrej6?5F9h6hKm-G zK9FH5a;C$Du!mN>c9+tYS94p~b={Od44Y{m<;pnQE?&?y3?$4=4h{YNXYP&Y>3DjH z>-vPGQl1%cl$Qwyj36z_a)!weqTuW_J$|!UdcIh>IzKx3;cpDQY8hn+)m9NwYEWYw zpV?2gsJvwz0&#gVhoCTuj~;>42;x~8`QgpBG3Av;Wr#FE)N1qkQrpJ^%F1mY0V=Z` zXQUzTM30GG)PKr|``K^;L9EsIcYB~3gr=OyB{op?x=6;hKcTNQ&$@`UVcwKS;)Ebj zGSECxBhc6w;~^-bre#ecGjo1D$rHrrPV#4pqJG2$z(MyKW>CKfj0nGL8Q(imjH z()C)ej3$hn9@(1|#C%Fv}?q#?>w zt&z@L>o(h}rH!zH_Dd`%zC91<2X6vDO-67TqK_r6{!_~2px+6D8NN+nR7e;9?ZJUg z@rRm}fvR)5>?W9+d>_r2lb98$8vqIWL*4FE;aSzwaqu4flNu-o%Nb#eYHV*e= zDXJZwr(?uQ#glA~J3FW=t~Qz23_i~J=55Z+EPzuPle3@#Vd!dZwv|#fiw&DGa9zMJ zUZ%$_fed}S+$BTYKoz{sy45iZZN`?NT*X$b*52(QC_roLwN%VDQdJWJ$uTbPeG74- zR8T8B24N@Rs#V+NC`$Xr@&qTKTB4Qdt1G0w>k8bJ-vmGj2dHQ7|MSXfy(+3)zLkhV zPjE}4qM)c-AZQWHgh6hcN|Z!55+uSRTmRWvT-jUM{#wcz{ljSs$6uX}t4Sw6gq&xZ zpq!(T=_SlzHXVm=YJ$m0zTupLHpd%l3oH9NuDd&@sau4$IZZvWbyMqxi!LU*Vx?~6 z|G61P{84_xi0Zy>9+P7oV+Z~@?8L?GrwPOB6gi})f;ef2DU>swyBgbAHg8M+wqUbQ zdM^7{MSDTlZr>YQ0-3g-48w||iA+|W#O!l*p;f}TM=5ct1s%4Y$Q!3(JeO#(S4^#? zNo}tTSsSCZaWuTl?a&;cUh_b)TlV8so9cgnXe`CGfpZI3L>b6^<4g9&Jy3}43ox4+&$l@1UI0eQen(w~a+tPRRrbD=KvNK1qD4yL~rq0V~o)n~r zoo>S2OeB=4tuW)^q=#wz>tk;#AoU;3RYJiD@#eO2^4Jb*?Qmi+0uv;P+9=C#HU@J| zz6oold6be~XW)R1PQ{1PI7|N4R&OCEfGG4VDGoacriaTC{+z0*AMGn_v84s8?8n$$ zTwGjRTy)W>v-5rDSR8vjD4@fVws~3y$T|ZWJM9mp=)t&P8YNV4DLp8I&j;wZ(I+_; z=B9*!4kcO8%&2MGEi?LHRuM^i8=37GFsBVe4nI0Wy3D>ZI&$%AItiE#7Cc~S?qhSe z?D|@Uo1qPG+hKwmu}ptG;()3Cj^cmjt;p0^=wbb7z_e;n8hpw_pm&6(Sr ze46CMA&v*=GYO|dN=gPD2}lhiAW=&io(XS0pZu+DbB)&r4@vk~zgjgn?&Q5=eT-=a zD2UKdA}k{{kicTXXod?H1vn82Z;=rjNpGxNq)|-)6D74MX$B|^5s3sbCP#_{&`2s8 z=nGf2#Wud3w>D@8Ri==wFh#{pgOs+87l|M2^=a=%?)0WUV)_FOGeS|203}Nkhb#b? z?+^~RFsRy;Z|H^`?9LCiTH2YUM+6%mb9i=!go4p5Ar=Xl3Qp~w93k=pApc7N7}c3z zR7_{{mU_H-#5tniV42DZkOYZ<5CTE6bV#!(cm!UVsYlWieELhx&j(3K_19P8Py?xW z()Utp&Wa;B<#727>4Q8GDbmx;CN_vVtzR+?E!IXWqg=CS6x#r4F|)m{5kI1R4Z_p-4>O5`+mv zFyvxzP?VOGHfG(nqye_KxwYo7r40MPeqzh}l~+fP6el#DlriZoHxOt*15y!5nxY4t zI;}Jx6Qf8pv^K+(%)0VbRohq)F_giS*^4Tc2-2yzgMT+7!2X=@%Q z@7~pCSlzBtGzP6FbJ8(t*Zo0HUnh4H6XTwZ5(F1+vP6L-FrgM`MtPp7-*)D%l(2b# zB8T;CEW*GuPsKwu)^gz{4|hdOE|@}cBT0v_43+>n5THB{g007DYnv8&IiVF)ExX6B zJE?1pQ3ft4wJ^k)A+2`Q4)c49e%Nc5bx_5F*OKxmQo=hOuAa&(O97^_!MYjW-m8v0 zhxF%5o2%{2K1=JA{stB`j5f(ROsF%|&qbx(qsc+F+H7D8@;=(bzksLoeWYFH7-*W0 zbx$2nJ!9qI(eT9TW3=EaaN~*8seh$rHZDx<>}y4lEf%o>B&N1ftip* zPgD>Vn`JPDh|n6bW?-SL`r~4?na?Mxa=(jIGL-P_nkAE5^AQN++xn} zBGwae3K`5uO&~7}UPBHOv_J)cr6@`Q=nEUSR(gJ36kpX}&jcxHO6iF}A}EP~nZlT` zv#0Z}JW>(!qYe=wB;YC9{d$_WteKq4)AQ^Np+4YOz> zh8SAxSW5q!V@VA8D?uo2j9AW%_ntVHFwRh4Yg7!X)Q@Ibx+(q8$xnlJw zz5_kk<{kAfO!raZ$p5<+=IFrm2#NjgxgW9aGat?)g&)xW9n9z0rOODB$^Yc%y5iNn z>GPyMVb6R(K{4Zq>w3TeaU8@WK#B!*(EO*8@V7#-N4a-CwRkm&dUwsw>SNc3s`Gk` z`!BiI*8`w`v3=P;zHN(7teHjG2aoy<%`yc+1xw6B!ovvUK^s@8gx~|H}I8$VwZ_~rrW5*Kj zUChL234T9fBQqB+&Iu=ka`yeOM6OO&#FDaB0e1Pmbqjy1i4vxF6le#oJSsOd@hZ{zc_OX%6;c}j!H>wx61qw zb?kyYW{wy~JRxxu40=0v@N;*d?Wd&kJbZyTtjbZw>A(GE=M){1Y+`D1r}QxxrXN0P zyh$09bNyg_l9znnh1AA6MyL>aO&M#s^log|IS$G zCs>B;F1yZ;RkWW(b;@t_;~uqIWx;>^k6GV>xd%6ON!n`~mL6BL2VT17Z|fW*o&Rk@ z#e9*r7C-PtMiK|@jz~O!OUI6{hMl%uO2{WBZP3d+_r~UO+7JZ+G*G4jwU>H)JEuS6 zcfj>8{r5S>j=uP)+i!;be*0JVqHeDc4+Z0LNc{o|J^&5z!R#Da`hJUNZx4t){9Lv2 z8BkLbhD~bx5UwvRW+_&G3`sQt&F1va4oN@8OuB7)3%$pq=A!umDr}RlKLz@_fV@8c z2hVw&H~c*BxtIYwXvYf4+0g!Bx8-l)^65=gPYM*k7Wjsu-E=LQ0JQR_2ur~RsZ?_`S-bq`?*bq>YLtcEsq8^ z>0`3xxIvc#3SKgPMiTb;qvor74H9{kD?ruwByHNp!&?nle$xq_uddabP3sHQ`PDB~ zZY!&4r1^V)nTL9q;696ikkvo6IOi}3fj{R8=tOxx*c>O|;Vy;F^DmPW2$YbCSvn|WV7EuFel_^j4qewzt4%fcM_7dqiOi*Ig+Q%rbSZ&-bQjf!-YF4=);;>2!i>km^ zf39*SCEtfoe-^rhV)vo)fsEnO26bfW>8b>v)ib@K=zq{vkni&LZX=LfhZsKa>0TT) zFHue!O)Kg9I|fbSy3PvKYTlt~Pt#~28dR#9ubTJqziD$$%T-$9lXd_@{(8{g!~ZJX zrVfZ&_;Huf4f9Can;+W?BOTrIP?sbLQz~;WWj=b_lJ?zze zjeAqSX?5ObuTUiWtJfm*6JuIf`LC4>H@OzYHi;VW%sPii{A%}dkQnG zsJy&l(~@`J;K0Ffbma);#{sOx!XKM~8mqahs~Nd2k7=ee=OJHj{h^mSdZ2Ll*7nBg zDY2p`eZFSBw$`4qUmiZr>H;b&u(B!mTkx&_ye)m?H;r0y%x> zmY^`Pn!kVty?t`Jt?xqm+}X8HKI#*F%W&JR_~)q@Dft^d-sNUIb9)ovTJ+oTIx}v- z+5x1G)>TAhaJ>CByJv51J8Rav2P}ktzFg3XwIiOF9`CO9={zpqn^*P?t~c*RJ7Lo3 z=Lv1J=i7~&j{yHJzuSvV@DGH(&3gw7p`ac9{P#GltE{jx;U0~1Iepzodw$fy(43KC z?^>ah3z6~nL2HtQEr;Wl55+_}pMN|6hu43h>AH9O<@ynh3}$9s$XVXS8cYg3N&6fBE|asoWC#XI&{*JOe&~zF-eWE23n! zfxcZ&;2w&03^}p(|8K$V*!#ZJ|L-m8JB;q9-%GZof9nY@9(Xg$-LcUxnw!6F=H-u` zEca)`oP9n_>Z=QB)Z<)=FZfP)IDgT@W-`cOTfgSNO!WPNHW<#zl4E@Rut|PYXZBjP zzt4XSz7<$#5hdprkW&Bv5D)_Z1^_cOMKb^ZUu7WF|32eJ5I_o%yGM7=rF965vE)p+ zOme)kf=HtXbIz$;Y*}o}%x%91`tLx11_6df0Id!H$pQNSFp{eOUP1!|!cX{XSWi76 z#A;dhH7h?YZ?5e@3u~L(e^s~w z31mAEjd=~4XidUcumWXMp(IAuS(GgjC2cxveT{C?JvLDn2-qkN$z;0rTjCr>ihuof zYIk17w&z4WilBQbvIlTUXNf|$flPtj9>GxYviHhiVe9Y6y~E&%o(?wBT;C0cO%h&L z#EfMzrAxB5W=lyMM~8*-m)F$jXd4lI|+Jhp5ffM`O2I50O)q>TL zX?*^O<4D0rE&4c4Wlzp3(JkjsFDg)V>S|ppa(_}4-6!cdanNV)!rz4J+cr#-Alpa#o&)Z?<)Lk{x%jhDlhIk`ndX(DK{B6&`Pv@tv!!L!|tu9 zL@jzuoE_e@iBAKMMMD>Lh>J)j3X<(!z)S!8ZhCK#$RomVndK>@oaC`$Fd zq%tqUa%~#nYiVlisBvL--RX0>%8kk^z^VmLM94)?kig_EWKzJO2{N`vaJw^&`YP6% zQRv;d#yJ#0Sm_wxJXeUuX_EvHY35N?L`_|hwx@7AW*X{2TdWPB>CQ6FbSCfOb%8xv z30hcHF)buFta+$ON%oHI2Ha?x&<%5wbGnI)x+Vx>QP#>748y;wi(?rE+`$$I6A~TU z4Y(mQi8mV-r*p_STpU`o*a3Yk6d;93RF|HrScUFso5~&A4Y(>Z>uaT3+(ypn9WrZF z8Ae1CtjLQdG79Guk_Z-y7;bykw`4&v`msLu1R2VrlCF;GvFn(1TOw4}b7fGyr=8o&xL%q@on3T; zx`8@fMFvm=RH{P6E-T9tQx*IAl?l#nFCKbqG~j{ETwe*@OE*BL2FL*!q8fIIE)H4i zs(;PsPhoR;S6FN-;K1oM)t=b(>bg1|K?doTp}>*|04l6Wjd~)Yv$|#hm7KeR9@`DL zDl;l-rCaDm=yVksAIq0ZfFz!&mF&P7?Ph3nY@>ZJodv)S;av0@Vb4f6p&OvnBV@EG zO2Yvvg%zVnWeQ`+6DUSmX>+rnl(q<`OVd|f_EO0wLpMaHdCB52f$WAxXvSX&05ol% zr5#cU3?H_qaK4Qj%qyH47tZQ!x*v>mr;0K3%ZOx+F*3TioUgzy`BlTT@cfu#rrDKj2;0mr7( z&16CVL{a9>WSOw40eO0hPLnlR7ILzCm;*IUoAf53y7A0(OoO|5uc_rqf~}~?E#28$ zj8S#kAw`c!fNV72>da&dV1Kjr97SRMomI)YWvz=etM!0L5qYZ&{PP`+XX(m{? z-H2XWOkNs?rOU#LimE4sQjpslxF0kPHMJQQo|v(xbU>NuMhj=&oaG8a1B!ljIUmyE zvSLt!d-w-rrU5Es6Uw|!H>bs%h|>yWe|d<0<(_5!!3jR2_2+>UnT9HuH^zHo+ue(jQ7^V19Lu~w#sf4$$UFJ05nu*Zf>0eB27tsBw61p_8j4v0AoR^%9( zW}QHF&5OTwN9c2M@TA^_sfX|kcvWk1Q9in<}5CELBSx+I;oY{R6&)KCP zv~|pI8OiUx{EqK=iNKJ+lKzjaNfS=TBsTw9Y9kFOm_DD2V;qsSohBy)^+kb$^ip*sl@e=EFg4>>RN$(EzH-w0K?u7wu=4v-KIg+NG6WB2?Y~;6}ZcXl2^90G=z0a-#AX zI!qxh)`F!+5iMoVa+&F8CFC>*=pSQ*L=N~ z50T^UC4ND}9V#IT4mc+xlD+TZY7)xjKGqD$0|&>Gy?`4lYeq|-bq8<*Y|+A$A~0xj zqu-n(NJ(|k3o7fkaFAsb;Zz^eMp=U%E-h$7IFvWxM&iZKN`^cVC>RSrh01dLO^x)D zsjXk2V40ms(A-=mWBSH;v0fE829+^B;91las;RAC!1I5YlTj@+gV{8$ddihM6DXx#? zfVS4m+L#(E;6SCOc0zIe0*gFg`pb>s5mc+WHaFBAcgOw=DeOEwRD|t@`Hs+JYghA`*I9e^B84MfbeH zqMojB(#LVZusg??sT~^sT=DrAZ~0ECKZy0?jaasfca0!%oV4^rslDGrN$B`f_vG>6 zM~eQ!Cm}upTl@-rrc#$KVG`f(DPO}^vbdH0IKTDzwd9G#)F)JTS^dTRH3-^&AFMSkc!fT%?oE0jHID2T z>-sif-T*wC&2p`s8=6pioye(l=9Ie2iL|63FLyxdMjgJg8Mp4h-~59G@3Xu5ewQqu zSrexQgHnIRZBNkk&B6mdcex?bzXaxH!>AkG=-q|e9Ea++F4IUmmR%)z{PFB8)#sR@ zsJC!3`PxLg!(c$#b(fEY6?ys$6fcmRJB_xN{GfHSHdjAM>e0|;>g0*(b_uY~>0m$2 z^X|)O{44+S4hyS-VmBz@Ft~g+_(NBPhr#XC9jx)9CFAk&RwzK%UzJlX-4X` zO{dWDNB`V5jkAxDwCRzx9%+3^QVM5AO2Ht~X3~^cg>$G^zvM`J!!JD=9~xBXF;a9t zhLhet`{cQoQUOyNnl)9t2R1)`(Y3ep6XaUFIce10G%P_^C~QRE^k@sx(k5y=DIbxN z@o_o(>?@peonr3MkvxmU?H8n&d-utmDS01Ztm!D#<0~t>9mlR`9~`nRG?H4R$uf(w zv#Y>tyO43CdSlBaVDkB^DY&c-)w{*Q1F|uC(#Sg{<=-%<+OWF$sZEhK%{x1zUATp~ zgFvjz?s`rtZswPDS5GFo+;gbFR!AhgQuu;rp_4}5fmF>8RImD|a}Ia?xzAV+X<9S+ zCH0;d0Xovb@hqg4n4wG0kO;an->zvmjyWBo|LI*lN1%5+0pqi4tGfkR#@)JmlMeoh zSO13nu+jPkEN!{2-~57fO`IxZ^bYj9cll<6_MPW0#|hm}ZDKMRo17FRxvTkAM#^f3nYEWc6*t1_Q^+Q)m!`P{8JrjHk5FJYvA6(&uT>rkzI zd?IoGVgW(re#i-PvK0^*PP;pTnx?|w{Pbh7E55UkRi%~Wu(6`-ukeN^t+e6wGDn7{1E$~n3ZnmCOa1;CPA^WINNkzC-x!C;K+oQo>L6Q0t^B`OwAYhD z8xQ`COo#iqCwYaS0`oT%%rZGb#1D;8@-TZ8Ro_xlx;$f5TS!k9JO;+}d@P^Vt>0h# zbCLG02sfg!e%m*cCyHBishfhFYZn*tAtrwdaTaX71lzDNay0)iM9Je*%}zn$3i{X$ zd>A158T{qPhrUP{W9`L9BI5x6apO{A#c?$>pFg2F6iQ@fv-7>OzFj6Xi=Ta?kCt}D z@%}6OLkB?ZbZs$+8amsezso}=<=;9>p-(%H2r)>c&@C<1Q#nK_Y@m8>oV>o)nsSu# zyzoxSy9jmbwuP&_M3>BSk{oTy^Zp^+1n}@rzju80{$#H`ha7z+B<<|7-|xQ5q4~K) zAad<@{*-}@PH$jr@>`uhgA{h{+0YZPMy z*DYS^uir7!w*A+Vi*Tmh5$Bl`V$^P?W3CUdBAae{QL3bz{iOOuY=MbdP9pj`dS5^P z4A3tr5+{D@%fyWEJKwqd&)m@91$N3?2Obul1)TW@$EfUhyI}6X|^^WA-0f`y*4@AyXIZ zf)C2PcIEPOQx)S;a_OgKc4Nx+z?1jF?V4~{P8&yx3)IBHqY7ISRck%l8~VRn<&= z-YYf|n8wTXv&ZNHg{{|9@4t5AH_fW;u7WaC%>aLZi znwpmv@x@%aW|>#&|MxyllxZwUw%u2S#7tN8Pq$Oms%@_+XBmiTDDJSgvMRL)yl4Ml zr|GY}rn4&GxvG9ZpO=1)nYka^{uj@{KP_&*hHgkKXWb0eUd2w6GC3n}#Wcy?ZIw%# ziY(1}aev}B+#krFs@EPX3F3#<9&ml!4q^qb$bzS~{py23^LCDdZOQB#$>mtR6)@MA zz-_9>xg4uM$OH3yQIqQl%NLr;uDk}0;mXcF<35<~D?Z5dEpJTzd@Lu0jgmX_G3{?| zS75LnTVx9H-~3{Mi1R#%H*$HdX1|iA`4g)y`5r^Y!prla#Dgz~qBv|=T9tLv_tV-h zck-g;b;w6}7bl{(dCNaD;$eKZ54j7kxes&*A7SNldQBz8kM9%U8o^|m^3|u2wp99k zzxNM8z5%OeLdS>S-8xEJ^>kDJa|B54!j-pY`{K*nU0=cQBC? zrQGwb7g5Q`e|i^dP`EwF%1W?)HkuwZ&~;K%=Wk;dsZ@8f*z)`Yr=9urdT8DCu-<6d zt|cWSNbJr2NPCNJgAvNZ5%vbv<8EUUeLnK2_Z8r{Ow`Q?iL1ha@mwi-dx0Pro_2%v zVBXo!HyIcl9H^P9dP-b*i#*}ZO-47b;Yr#pi$^N13p&| zw|1gT=^#&-kN~_@F)=+ru))-423$M~NMeMqiBWiRbpXPfuup&=F|uTs2kWXNL-?1| zrZ5%(FrL8vU64+o&z-iU1*(iD_=QRa+t5sFd$3yx=lIqbEcM$X`wwSCahX!+r-&K2 z<5J~%D=%NhURr`m{E+{H+7IqT-5b*T?atY^OPSb(xpYDuP4N<;??KL7Pb)4-zozzT z9ckX|$M6=R@0I^d&$q8P(Td2Vn=N}ccr#^q>AlQ}dMINRt`l+dG+I)WX47Wx4{x&! z(sWPrJIgY3b8laLL4DoMTMpjb_vZOGJVuG|Z_{Ja`mPWp0d|OUi~A|S@YDKK%F*m6 z9ZF}wB6J77ROs%^MS0ZbxN}Y=Wl*CsGh?~G$WNUsmE@1vd3ciCeBWNLb3Pv?mjoXj z$H3g0UX-1!JyX0k@MX;V^JP$Hv#-=RJ(&>s@XQ@5W%O5a-upXOx0Pt~yVA^u)E-rM zd1fa}WPiR+e198?csQ-T_gzOJ-~sKiPraROm-;?*6Tf`x(=}2kk*XOK$_g0c4ww9s zEu|@7in0uSg}d|C)quO4(N{US&{z?51e2zUd+WUe$2CR)6po4-9qVMReJI0C&S|UsYRzGzkkedbXM>t{5QiORw9)<0 zNij%26Nx%0JQeCexY{-f{R$_vIIyr)%jsS+aA9sOr(h;%VPh3ivO-3v+_5iws?hIi z;Z{+=2Aj4Ad8k&bI&2Ja5`;*jWNOq%KnrALI@S`{lPnD%Ej6pq&pyRgQNXL4bL3rL z>98`yz#XGZ5J#ao3!h+?s!|lo@=nR5mE$;U6yYmruq)QqR#bz-&sQ}*Wo>1RbA52l zVZ)Zwykw!uB4v^$9gDS$Ofs-rsw%39BAeC&aJXd@!{cmh)iA(V7kb_>?Y_g7IHw@U zbzb6|c_^sSCB$8noTHv7=L3_PSD~IQ+pVL3vqy!mmS1wnyk6DShfN7itdJm(QaX5G zql9E?K*5Q+h4Uufc#7@4;9Sxuz}8@aVA~xwCO85`nV3UWLXh2Siz9ugsGdcYU6|`q zCVP4o=i){ItzS5FWEef`2)tEwvJgI+B2{$Q8RQBWs7Z7#R#$j5sfz|^Q9k|t*8oAS5S6maTjur{^! z+42p)3502{T~UCuTl27i%IPLDqCk~XSrzLNXP*FTL<{^FwVq>D)+U5oq*1`yv8X}Y z$$?>iSzB!j^;D_$9kvEJM^6nodtN}$d|ZbpWV;&pAY;l#$|d8sig4I9r4Uw~qRzRp zu;#E;!|8A`khoEqT6XR|ZO8#lESPGl$+DtPYdr`DNuyO!oBOkA-JP{PL~ZygFE*+* zhs{AwiI8aC$nCC~h{B_lQQZv?7)%-o=4w=H%Xqcb6mZ;SD=Ic?eo!Lg4XOqbI}a7{ z+m)I(hV7@r$T%XS*XO<{7JQ9$R#p&6Weu=^Khm`|$i2t)0&Z`z>@$)!o9wpi4qHQ< zsv>3yRFWv7p+v(90VGxYWzQGU?u0?LJ-ioNMghWI4YwGSY;bFAY95B_r^DO?UaX8@ zajBjMG$4S|Fyc;dJ2E@Rd4fD)mI(1 zMmVEFc8^I!WR%Jw&biZ=7GTr#L`7TGq*~qzd#$5@G)F;H`h{DvP5+<>q}W>ooDLyV zL<&3E$1T%383jpYb+MV$5`{xcRIQI`$yQOoqM6S-X$BoO#yHYNbxl;45qYfAU@0kB zRS_+1)uuabCGBVBu4;Be9+7*aYY=R(IM=AuPrBt`m+;BYwyDLi$JYZB!I&2Jb zG>a*-L>-ekph+w(NFmWOh^r(avLutSx*c?487^*l zUtW-Itut;_jRFZI;Hjm)>98`w6fR&?(L0+c5e+Otumem-bKejeoyo9oif}MAl(wx# zeWe?2s#+uZFT{;aYu>g>r^Cn`69zsX?QzT{*r7RDD08dL`758A*4d+48rKRq8#FH{ z7I@*`v&Xcg(L?|v6*xu_$I2|D<`h9HAjITKTZ_;uR&T(ymQgE`c|9mH9kwPnphE=; zA`$S32nKpPBr1_si)3(xEiO?@z`e^;1Wen2Y*KDIiA)wAiQu$?QBEb~hUsj>$qBV)?GNwW@EC0J^WfKx7`by+npjXF(-kIf6otAnzY9@!R(M=VRq7(^8L9BR5VRx+FMws9|Q5EBIm|7^{S<4Z2 zt8E>&MmID<(vUMJP3^Zf2!H~ByfR6&a;wwP>9!)=4vlM)lxo5b(H2$dmMVi&O~;Yp zm_|Jgs7No5$TYHuWRI4!Gy)M-b@tS-#q|O%1dUKF{>xUh9zC*&)2&;W%u-%*!08Y& z42((Ps8eit8HIX>VA2+8atU)qx^W!UsUk}$w~%*G@l{m3Cw=uww8V0<$KwGl6#^32 zNQ7hpNP=O7FNK&SRF|}%iK+W~9w&t+4>nL>nKMzNTOu$Jeyy}i)8T=t|C~_K$zqh4YKAVq_ zIh+>@WThe#;)MA&*Ng-Qnmz7Jiu0bS8 z&ZQ;*AOiq2BLG$a2EO;b_uKBbch+9_-s;_#we2qMZL;pQ?Y$XmZL$fLjUpsckYYh- zF{FtO{3NhobK-zh-4IJc2ntG1fLXv}01`h41U~@)65vmnpF=-@JBD)aA{S_#Z)w{Q z_N}e$eG{MvNNJcYfy*PJhfo!u0@nf3WD3uv%4*cYU=B5E;{*0u4|e2Uof@DaomgEB z3LvQc!Jxjqc3VLM2q9VlX}k3T{C7b#+j4s0EO{yZw+Leynp^-uk6(%9=smB{BzrJ7 zYe!qgJ*~Mr*441M)iJWE-l)T=rf=2leOEDRxzX+>{Prj|_tFZ|-?b1IKT`ub@29=A zhQAR21bsrP1J%Lnl4CNFY!bmHN(ovL2$3vQ*b9q#FZ!1J#lzOKwtMc2s9N8N!MnZQ zChR}idlv>#`$y=9xvr28m?k+ge3E&kI8;m^v71`VLg6^eh#_l%4i6e1dVLsat49I|(Rt&=yS*m-i$Z|qO0iuyg3X79SI=Pe_?2)={4ZME5o(s@I z82A4Ub5ryZWEpIZGYCoUvWz3$WaA7Kr>^zxT_XA~HSSix-tUB#75B#GI*4&bQoZ;? zyg^J1LDb?Y4jpC#ibfF`IK?4$X*e-tx=V?;o;?wbTa8^5TWWs?Irft+@#Bf96V1Dm zWW+F4)Fy<2aOr6k2_q9bo?Xwi#kEdm*1Pa(FT4|LS=%sJRfdGyx-84#kTsI+Wy702 z6pusj!+N&GZVlX+IQ|(W&#{D)sSb;jNbI?WV6$hLL5}b;Q~8EroRP4vt9I=k2Z}AR zKLd<#R5%0=ri4*hbpg_QJo9N7;p+Pa_M88kcuPAczx@R-=yv0`79V=^ecg3>+uqOj zY7;ik$Hida7s}L-75HI`iiamL%wj=gqE;RwFGFUa;=KNHL|LUca`0Bw9u&TvuSjya zn!KJaWq$_Gi*A1+uM!`}T$#yF%`86QMO-INu4HTBdfK5}7WZpoFK~2Y3k{T~N36d>! zM6kjMZ*(IfaWM8Wf#E70jaSNvbpsEZ9Y|iZ%ggInIO^IH2|>>bHC|enzBo^MS?=C896B|eW$;TxGQ!ng zsJHyKiL^o%67L>7V=Q$IT7=x&?nPclzSm{Cu?d=RP=EW^YXcR&!2m1`Ljd%m0t}v6 z@Oa@qGF|_^*n--K(88^YWf{qxc1d}yEHD3+^|3 z)oi@p3U&W#S>#1TizeDtUKe2G#mm7 zG5uUH5mB=x3Ho`7(017+l14A~$^6QGHdtwRh}*?t2f9UM$GMk8;ObT#GCShDK8BIq z%g|d=*1Zap@fKNe5-h7?T2xg$)wEMTi*ehulO*$AOTUh=?HfsWtU4WW(VF<0{Mc2g z;}+A!ww>I}Dtd7{_sI7c zhr;Ye@-G|yb{|PXJ#?GyzeOF^v637}VnUr^d!(2Wn(;UudvOg|VYEfntvtjNZe9n=NDH-p59)621`7`u5loWzI3|(P z#w58BG${hI&73XHY|GP2BsE-xgZW_D$Ua$(H_Ou%Y8*#QaYEY*o>2uNPGF%!rbo2R z8%vl4jpvtQ77fb5K1wy|BMA;;z~pk_v<2%Z6wpk*8nS-EBV z5}E+fZ>z_0>d%^weZrv@H79f?_7Y1$JfG7-|Osr&wHW<#LD?B_Yos3*B zyN`OPW&wtg#Ype7(+5Oa6m4{m$#5*K0*fiV0nE1@kyv!wTiH4U(GL1|T$a7tBEq9q z>MlzFk?rwhz7@A}BK5mx2rNmIx`o9H8h9_93gjtJCAtXGJkx;14|pIq%PTZK&ckdD z6|3kak{YfH&S%DIhwQpru{t-8#^wnW*`z<%7QJ-7hNW2cduEDVLFD|bGgp>THZp#_ z?5E{*-nvh;fKE>kkHw5ckPb_l<-!Olkli$PXr$~Yk#vfcI z?+ac3f{nrADbd*d;j9M9Lo#9H6*i3QUIP56aQf$CNGg2PsRK+=;o}KIVks>=)0eq< z;A$de;R}7rGDO<6mLqx(G`Q;25sfI%k9N9t6_~jpMoo<@C{F_LAl4TCTH@B>Rwr;}hj)Jea1uJBJfmr5j-#-8D zH~xh^C41xb)Z{|~m{ywWf|||d@pEanCGjAGJU+e|B2328H87Y#Bl%@c)NP53vIZcdW#hprYt@Ai zBQ#9X%!Gy*$o+0e0=SyOS-`)|C01{eWBV)+^PFOnp*D@WDHfGZ%ie?7q11Z>_;^QZ zy!r1J(Iv01gu`di%e+(yAo{j4s#cb%-ZA0i6}yaHXi-6|i3hSnu&rq;c>}73Vi!~N zVPIN7v(qUuOyplCk`=BoyWWP-iI{O$H8?)(XLB4abig^+qb!H{3{r)^FHtNbO2W>G zhG^D=9~{3Z93)S}A^WKxT7is~N~ocZ#pFTUW;Po@vzgRCc9?E!MctN~N};|uXb^Y1OJJFkRd z0N9?*R$k&F+=DM|Z(o(V0|ySdbL(V=Vd-AtIkBiOCqbo!QC37mF+Rtb-(mwUgQv>i zr`=sogfce4l7G|yjY0mU3@RALQ`#2Sk;RzB_`2J+EgH8T@F z4Op5i3qkG;Jw?t@CPh`21k@)vmF%sUMoMdC!q&zb1OztQPnpEX?6fr1X%(XkLTBbV zZHAr4F9e%$+zo^l#wFqLT5%yNFw^-J&yL7S6?$fpCPU_Vz?t3`Hb}7uS_YJ-A$_%!&w;JTQJmTkS z`-QgHD5^@59wCc{MYKW)P&tAKr7_Mnk6L=@@UN?PtXWl>`?KihJ?i?W>MmB=Y=w6> zVmTKB!vWCbec;470*$dRaukMTn4*zzN2+o4fQMz7<3NO5-*>NLU(Hz!n^$bAt=6!# zR@tj~Ez+>xs}c*M*E8E1qwDL;=hCk9u$K_5b~&m|R@pdfOSLfo4G~hLrvBEtdgrRA z8tgmbm83#mxiuBhzQOS`(e;Ok%U>dRX1{ zvBMNi(aeHA^L>~x)pQEXmGr#)w<`c@tMi;Qyc)-Znk)-PCksikn>FyuZ&% z^Zd*$YO5!LmX~P$WrOK-l+l>awjQk<2h5Q%$Q^0ve zuj*l4Q-6jJd5fPMkWkEhZJ$-;?o}DCb@DZO%2lVUO*xHvzbw9S>+JU6)H8bdcCDK0 zaAxH4M1m-#iocZ{3GT6iuTj(I5w*U$n{MVBWmT3P&Z}!zE9O+Lk1cu2AaQkp zvUJa1InD{etz}od{gEjd5pu9jH9uE|ybY#%pdcJvJR=Re%W%!B@v^F`s+DJjRa9HH z`Pbcg^ovF!dTgA6ezBdDv?1I{MW&JL)ARq2BT%z(6FzebE^IBTIh1zyxa?oMHb-S- z3-B>)G4FT>pA0sb|5Xbp>%SbDtjZuviV)3+%z`>uIyxNVXRFFn}&4o8I?6GPF5NXDwI|3 zQm9*s!2;H8K&MtBFC3Qai0Zu5kR@7)kKQoyfXbeR`TUta>xrJyx&}ohb zb7d3b%*Tr&Hdw2=w{oi9>C3X@q}@fDt*X*-&czbspaKivSl<5k14qyu&FJ)^wOdq{ zJ9F1Sz^mB;3>9PbGNSTqezVN4?Rh4KrpX#X;~l2-a*0~M??w!p)S!lEB2FSOcI- zUS^%Vxx}oH#+OSDWOB6D#lWK;5%P?Cl;<^U+vLBQaFo46SaI{ylh+xyrN?&`n0UAZ0m0l0sv?7M<$TWfohjIaX;jCUq?1H~|xSdkD!WD#xiXqA)~R zSDavGk%$(@wo?pbv5!{Aa_L;}r)$n!fw1}3Dj|k|DhW)Q=-`>vSUC{@6s4LesBB@4 zR74D-4VGfJ#~n<$Nx6y+FhjfqMHi7M6g95YjZE_;794Tuv8X6`a$6ag+PQSFt;Nb{ zEbu&9^54&XSBnmLFu!@cN`OF*!$wqErUWMp)GvnDAW0PPG{n)^jt~#9D{8 z=PlHs=U^-rOaW(doSelLYtb~6C{s1WvCd_L!~Hm3wz3Vab&YBaG|-x`-tun`7Mqq3 zq5>pL&xIT4Pn_@~@l`!i>VJW{sqtlaErsvT z!KOgZE>dZ}nr&y5V>rY&u#z}&y**G;(26j0ryXL_uC=on{tQR`?N`oTD4?KAWxxQ! zZN#42TzkDsh>maMr;L?yt!G{PtiP<+mGWLN4nX#(m?0cSY5Fm(BMfT7>QI#cNQ0qN zbSTMIKB_TcpF~Xe*K^QO6n=PewzYBzDs-|}FqF#3k31%y$^)9$WXAf21AOycEvSFKLfuWH{f`JgW&MS6LEP=lb z1!gcr0VPHUH=3$iAc!XHOAAA?ID#7k)?1(S%z8&2PB(J190B033_W64%F~tVp7Z#P80@@Bsrc$z0*Ibbo{g4*Xw-WSx+1e%)fN`j!3- zd0`SR#EF5>1cIn;s5lmj)j>?R8iscE2Kht$%wskX&5{6~Y7HYnRbVQbhN5T6S&gN# z3MhKPa``b!Qj$6s4`-xAbI_Ae+0{%}!+nSZeIOfdD8JeNKe{x@C5!^AdbiwqJh)E=ihOUcDzStIp~A#yM6fiR)_(9+WwS-IKIP+ zA>kZ`;^mZ=t(`Iqh5vgaWbS(>9)rG*yB1-0l_9@&vhjqvZj5v9`9j4G$uie8x1`-1 zLHtgfY!w$D?2>Gdy)40~d~YxK2J-X}iQ3;G@Q^25DfUW~xSzZt+>O>xA9zmR7YqYs z_CRz$JT9s+3`P0O>9%JluAz|cD4Xxa=*y^8WjlVuW%}RIMR@{1v($dn{Q;CUjbG%0 z!vdILOW)3MLbsx46o^h0ofVqvq8s-l`WBl>{@@Ir;ZM)=?)%~90-HTx|Npt60fgnN zICv`$_d_gT%#y5n+KZygECrY1bYQK-tAy{L2fTIb?Z0*lIKRE!Om;R3GvR5eDmGj0 z7st0S?7rvrEYg#oQ3Dq7G8pneEiiYN3Wqf*2o+D)sk{bt|Z>dbw@HuURn#m3dyNMrMl z*1xV3hGD<`bT8>o9uA}(i??veepQp7Fmta&vLS4|g*%kVdZ-CUMIrXc_!m9R8jfju zhv>%`rCg4eS-dpo?_g%l=QF>1hS@(3ml(t^T8}m@(N((k)1zrm(kplP`5)qGNqyV{ zr#wZ}`*`q#xe>aS?=c;JjSmO8SxiPREVg3vE7|W*Oxzo85%^a|oo++34H|Rn>CS zN111`{8&6^8}d#cF6S7s$)3K!e||py zTb=)U{~H66JjR~<*HU~dYlx(~4^*Ssl2R*3KnP|2RK6GdHKkMn3nQ;wb+<5relF&Hl?a;BUQjp*USYd~OKe`=Dxhz=*`Z6?l7lKiTdq z4o~H2Q3vBB`CReTzYbTBQ*05rp~~Psdd41#+kp;_?mJaLuv^>fqxsYPApTW_k|b5n zp?n)*flm2ftC~As0YKrECGErY{);LH^>jjQvT@z}LybFapq=YCC=(gWBnHSYHtWX< zdp#AmMt7{@UcS!NskdH9F?fA;sS`@b?*KR&goS2FfoUp=!Q+ey>A|$B9q`%HH0qVR zWCzqNzKKsLFVP+3Z0*?zLY%8aOX2Or-n~M%&K7X1y}iJqtE#wW3qtmkt(@@+ z6Hm`SA08K42vK=-{sZ84nhNDYpwopFrcM^xt>ahZ^ZR;7_WF}~?cIph3Xgrl?$mdB z(mjuMKQP|LUFfs;4cNikxC@M0)7dS-+s*f+a$l=F)FY&kdF)WtGx z#!W6okbqmLMNvv)Nw)m>=!rm($5tUmqiD>TLI4i?GR*?ylg!G zH`@C$LC_mJQ$Ne8nRA@>X{MNHJvSz;l2a z{-hVQQVyCvYr3{Xv!M!mAbUriJ))Fr_K+H5HAKqC53)4@rl=`Ywl!n0vu&d=tkC;ISslkGrKf$4dO8ys6jW0xpo>7CTC+AmgcbJPg-gzL6dQUgr6EznjJvP4Y80 zTaa^$ecq4UG=6Zznx!aeKLF_e*1K5TxyiXx{JlXR0uSqVK0I=t`7fG^8Z#7{IH7wz z^Jc2@ST4{g^E_%-8rZh@%T)8s(?nEr`J|;nLBZaY(s3!|MD+E)#Oa*R=U*l73^02} zKa-E=Lrec8rdt2$DcAHmAqF1ezacOl|0=%2p&@GT`-4Az;3J*jZS>?5&&d@x>E}t# zgBC^NzM9Y-TC)vU(-X0szVx^d5CZ@N05e2HGynikRiG%^BULUS0fL#$lFZ!9&6FhZ z1z55-j@wObNl|9}7t0nN<7SsVa10$-X8R;phWO!&)xnMV^3 z{DekUKi4FEtjxr@k0hI;n+WXQRNvroilCrh-S;w(7K2MG$jT&zs%Oi~* zYd4Z0jb@B7(h;IqO)VKHYBFhbB02loCg^*&pKmcm4`JI2@i}ff5`~T9*w*3RPJQpo ziKbu46;n8i3V z$-2buM_t@Fs1PZ8n+*Qs4$ji~8d5i&sqjIq*5cjP6iM%mEGdR|vLBW{GFbe)F^@Zr z48Wgy*Z0C2-WNP$9|{`5$JY<9op|gY!hfb;5*v|vrRNZY%M+dg%DBj+(To`s}z1l_cAY?k&{y!W;#oLkGbZr=HY;=3!Y1i`@xG-MpCJ(K$>F-5ah> z@Vr?x^y!mjgXOYv6mL(%x0pvFqrheLm7Ue8bWIzNPmWUY^Fc^K0PE*}vPTEpPSv;+-_&iaOoI8PoN->6rjT;9SRLg?Wv<;dCanQNP6 z+~vu)o@QJ|Hh=toO3E|cjcNDLd#d~!@6WFoS1r#)a{6cxjzd6QD*m_+I!X|kF<>I5 z|6^t#WJuof=w_se4q^6xlz~XN$14xvXU0-(;&I6y<%(f`pwJRSKvs}D) zvX9(Ld46kW6vnr;KjOomCGbM+AdaNg_wR+Wf63axxTMWn`nKWviG0-F!Bq1gf3?-L z%^|DduZ|z_A)+b0z*OGZ602Sv&s2UiSGNN>QZZ|&A`@OYK?s7xT+YL$P_Z#BbTQ5#RNt^>!Y+7_18baa(A@fjQS z&eMzP-&7;(M5ubT)#{sWP_;K(Pd|C_aH@foYoqtEE5lzk7SE3TZ3ixY^t_>ovc2mDzoxqY?pCgo&!wVDE~?H z*V@JE;{wSn>Q&=gY(>U{HoNsW8ydS1eL0kp(QMwUYU-)p9NxkvgMVQA%5FL;?((Sk zfPVhrnNG4VO8=A6rEG*#+Sb>5krru=#x00uj%Cb_P#EvNBXOK6V0Bvx19SX5(&s<^0?>E z&rhshHbm{9^Jy|`{WNC1o27?`VQF9Ad*VK@>oKIU@xz^-^tHO^ZQVpyQUv|}ul08; zJ?h_$S9w@$WB-C!z0I7Ayrp`5%aY&^+pe5K@;|;=ac_1G1->0Z zk+!AKO#0Ik*CFgT-|Ew-dC1+{akHDZyyC1U{v*X#HXS$Z>71>UKY3x@e~kJbKQgrU zU_MRfBiIo~P6n}#!~2Zib*I?3yukna-_=F$_OrLr1`dod`Z~5Lew}M*rEca@Ja)kE zxohvr_|2Ykg*It6U?KBtlK-fit{l+F68d0G`E`()0}{NwY+8dn^6=cWMH}{QRir;* zy!LqJE)}q^J&>D*ZcpOs(i!}^MuF_#wu7q^cH^do%vJs71A;<&Nn zKe&;&^~)ahxIa2uRZi?Z4cKrgp8L{d6Z!&6oM$5Q@Bv)~G zm(GfSqV?_UrAmt}Q?aLbGp5r%#gF}O0UzW0!hyj*_CfqEuAy{AFG}|>EhU(txUt>G zN8yn9;Yla89S*;HKC;R1y}Lz5;7%U|ziSKvN z9>0svs|w*@;iP9dzFd74JmJ3sj^Bd(#ur)QXW42!@#}qrj?wPI4=}Td`BL0b`CE*< za6Wt04WYk)<-7w>Tjl>#yz+dXz7O$IS%26EZEg3yaPVU9rlDmqHRsZ;+J>FkfY<_@ zvC3XE8xsYXg1N#YR{}oRyRW9!;VEWlxUKDHahZ?a9K#MD=e@xQ^$^;#x}I=*#}X}0 z>f9E2WxO}<`aiS1-%ijdR904L5pC+YDLiXF8Dd;!+p`uEe+PLn$2{ztuP>Y1sU!&f z!+>_ZCh$>~WoFxYSOo-2Wy=^Xj)*eKYz2{?$)^OR4Nr|}Q&U+6UDqW7=*`Db`Z$A` z7HQLLXP7I2OZLmG6y@PFC=Pg*3kf`ROm$WERSc8XSV?v;sHYO@&Bam87)CHYmzpI; zDVkH@_<;W!hAPmAr^%EWVbZo%F6LAZU6h-cp)|f1*~M+nh>a0*c_3$H5Ot2w#JOO= zGqRN0nQODYO%iT0j#}Y{G6U1fDzgM&AWrR81)VU!+2?@|38`mpbeN%nseBBHXy!?E;;oXbx@LoPUeb01TGxT zasm&sa#Lq$S`Jj`G~j#$rJJ}JWXk@}$Hazjo`XB(GXi>fvVG{c9a+R|O ze~?(YTuR^;S*_2hfx4nMAMGh`Qeno?R!eh2uAgY=@eGFv`<%ssz0dTNILETmts^by zOy7JIB{V~_;Q8w%0Zwc_2U@nK{0%m>LKwkTE+z27IBDw--Q=6)qFHFL|RIM6hJoj3DdS!3P#bWf6gSj&S_ia<1DheNmGfaON*ziW0=mG{7dY?W zl#8wZWx_1v$CkLwIcfn9oiv;kqG=En)$Q3=CT1ZOHu6K5T`DWeWyf5Uh+WUxUsg8t z6;;+#J_b#A)>7a(A4Ul|o5oD3Bv?dWjmvV23ur_*uRsb4MB>#iC9Mmg1Ca9>lmV6S zX)M5LMQm4Xs6BO)nQ{UfLv}fcx3iQ}%BrWm`l?WNV9vKtI)sdqOD|B2jO_Jd?oM~i ze$;%K4{KRYRpPlbC1dkO2V$s)?$XVDQTs>>T#FT%;7&VDX2S-GYJRU58c@>k)(j@X zDOl@1bdqi(n(I`iH&sCxofA6Dk)p_N=0XcMB=O9buAPX(DPlefvNDu>=%wAF3%Q!8 zLJiw36as*omMKIQs-@#>lCsw!(cElw*mrm;RU@Nn%U8;79CdgWZVZdCe(NUb`f zqQ6-TOGOTC<`U->oIiHk13Ug*Jr;Eu`#yBiaD|!4hBL6$61mt`LB}k~7^qm~GF^6@algwsV)+E%Rm#X^Db6^5qYmRL9D`g#n zoo%i8&`QV-4+(5Te5p4&-e`;8VC7b5%QPm9A;(pLmt-)iYXVLk2b>S4RJDsOE2%GS zBUaKz3K|<%z9s}t2ESD6Im8uu5`t3K8=!~XjhJi06_#T7RLh8Nc7}Jc$4E^ zgH{C+@iUq)bDb%i51}*@4AU;0nkgopZqcs+c7<^#w>A%qW2`_T9!67TZacm6p}L0! z*k}1SFw`_k7z+iDJfv}g1Y%w~Z*}+*=X09MLeQbW`C>|=A=tKu%UJf4Y5p<29~j4L zE3L5);<*P-$!HBT*;b{gsy=j}aJ|@AZUMNAz!M12ewS5Jeo%}putP~&)p%tF6X8|0 zb%#z8&h1bR3oWPXBG5heP>mEx-DD&!I{JXOhRX#6-r(x$Ey~I&yZXAc?+ntyjG?oP z^Iwz>^Vj%bsA0{d_^9OOXX9t(CQtD#Dpnu@Z;pAmMt}n!I%zp7LR$x9l#RLoLNOuiu%*mMHj@=f#EWOzQC2pZbzTj9aPuLQ zekUhfShVILu+42R$ca#ZV6+mK z`VcJBf7DT48L?Kc^InEvB5kRyGocfJ^Maj#QBBTus2(P$Q(;~iUEuD~+(G^qEP1V^ z#JHYOxl3cQ#al~0bkc9uoR5_%P;hY9oD^|Pt(H<#7BRotkf?B2&f+OEB}Ui)=LV8- zsrk@Kysc>175m3kC|nzpI==vVDo!tNbzPe@cf6NGyin^u0#+rguW;)%k$_#W_~aTp zrz{w@AqI}8Op+1SOA}jWWhPwJndw&(c(z4x&sC#!*YPlMB;eZVHqJ~8@I_|Xi)9q* z9D>s)L$PVUGQ~4q_oW2umq;tLwQ_$~1b}O$*jE5I1lKhCK0$|tB>}~72Zyq90;56# z&$MX$ft~efJg%K?18tNAfiX{&SxPC!a!qL&DzZrn2-6L7As+ip) z>UnjH&AK8vA45qZkl+$CziBI`h0YZyRra!FR;q$ru;ATAN=!xW9Gz*L51_;o7CHm= zG-X>LJHcVei2#>|jcE*mX3w!x0?9YlS&cO-Li#fB>b`z!38uq&lC=0$lK1a?4EsM5WCPd*pdB z!=5JO%^d*fP~dz9rHq4>OTA_Az!`uLN-+s`HdLm~G{F@KJUUBEo$WD-I)7N1Bh<4W zx`{YeM*KpZu~HMl#uOsw{tz(%SC;>E_zq0HFGiVE-C?@cl3MkR*vL8C>IFCXE2GL8&krz@*SqyAU1*b%n^N@ov zyH4O4m7?y-uCl^iFINN50m1nhN~57#GV-!PwHjcE{}4tArJ0tFWwK(x!?Kh_zvtz3 zzP0gjoE-Bn)N*6JCwlAvZA`Yj~9x7Yo*&08@LSufC!O%qQ-3Un9J6o z42(lxYlqMk;*|hVnBjkt)>LftZm%+&`QUdDW*5{avAG^sT+17E>lqY0;5rC zRp2!aOqyxK_J@va&K(=36j(s!U`i|bn&AT*IKl!VaAYe{);vDS3CJei9HA?e^BR<~ zmMnzo$?x0C2$!AJ3}yLdJbhW&Yw#kGC#Hd?MUAVv7B`=@rRYrOd=4cKL(C8kphXP| z14LV9W@0;pm*yapFA2OmfDX-f3g<&8HDu=`)^(6{A;~eq`==o(7(-gWXQ*mCIAHRu zd%4#gs+|v`Gy@Cvmu4rh^{p%NX;7sF*e(vNGE&|>yeU}$d`s=~p_HJF{6ie@34?vk zF}SSJ##xt%k#VV(b9h;n64O;`-)kyWqt(VCsGTaD59bs^=${qsL-TZk-J$!ZGw)v? z20KH%rzVrOZ77)pjs`eSK<>|xLGQEkFu1J{x{+7l5AckkGj|m{b~Y*PKL@<%$lJf4 zNO^aXZp|lK|IQWmjWNR&i%${Ehx62;E&& z9$N&FC=fb(!|**N^k9efkxY9yqRoV(sH>2u1fjodQVJqe#C#Rgnh1>?FtpX7?ndD; z@K!Th-nPlNyPrQISF92|yxNY17dAI`vX!v+{GO_s!9+yHM-pYk;}|ZXsS<-TL{C^I z39iYmwny{@+y!#qktT|PjCPr9n7B;I9aYNm=)9;*3}jspemM$Ts_>`EEBJLt6h4TW zgkyT6B-HYVr=pWtX_!0pMGlsAj&N#(hzw~;tHX*=L9`Xet?;mTq?Q=yQU9rgu6A^1 z)**+F+X_a5ubCf2ljm22y*1g=*3sYLjC0CE2*YG{aaF`zCKhININr+?N;f_OugVf+ zt9@MvyU}v=;95T5N$oU=hIPIn$y%Bzcuh7=7M$jhWze)Jn1jN}uM&rWg}Ja`fSs?Q z${2W|nO24)7>Gx zOpX+9=G!jBz{0X@HE7|;_mF45nGZKdb(jQ}W8!^u^CoSRm7uu55*7&3A4COQQ^eG^H5ZVZa|w9saJ!&!L!z=K`LSP1;JYyJHpGwxKJI#>NFQ zWSAy)=b&|^5#H6KMIMR|5hw#3)1#^#-vyqhP7+-wssOw*98XXt39iW_x5asKDF{DJ z)6Inr!mTs@`DUtD96*jkqp*=O@Ms!YBSTk3--kT<(9ezF-V>MY-F4sZ8=)`cpjMjY zPF&`U+iAcoQ96Q*CYb!H7V?4FQ+!6PKY$}0~Au7{9rKljh9GO zv|zXA7Khq+c)V6j4E9v~@qhY>d0VJlR#6-y2g2YdEE9lrt{mq+c_s5(R|}tGxP6Ng z;A^7Kt(D#aCX8W`O=pA7{(u-1ZAsJulr^HSq=6C=}{aK6u$RvEPT zI!8o*076Te33Ka!_XebNDA@W{R8A>MS2IH(xxvC!hM~c0%NTg6n4|W!xj8)k;==qQ zx3)9Fn&&3;gzhyU{xwoc;?H9rsXZbM>$5gG!#5hG>F#8pXb3spf+ebGYy>bG%9;|k z5_PXAhY}Qtq-4JFQ z$W~NfZC8z46U67(w1br=tF=J%bUcJ!EKvrNWk=in=!uqe;dkoSKN^~!Z_Uh2Hfk&S z>eUVH8vrz`fQ1QdrHmBAs`wf!1XM8EYe^udE)NxO-x=^6G6%oTBDEZ3ROqhRKkkviIMRc8Yi6-g4blB zTkx;a-|xBvGW}KfDYnGTH~Rm)i=@U!h39lhG`wG@${;DtKm8x~_In!0$`9e1Ex&Cg zphdS=yma!X{?%(11~s@png6f$oVzAnPQurQOV_@u_u#e9t@VC|d-5WLMou0V`Tlx` zNXYfqXN&*v`T-6}ug}4^?Yv-sS6J^|Zn}3la$jARbN{iJFT%XjL>Py_= zKbeMl-9qF(v>NO_VdYi4y{b&|OD3$1 zc>UL6srUV%=|Zp63=w>l7-5Lp%sH#z0wyFu0;jJWCBv$B(dv=wXLz5n>XzVnOy4kvcwLCVVX zch>rSD)><-pFHL7w77Fa{rXdMeU$+BsUUQp7MeEytIR%rj$Uy`E;SO)2O$`ffoq!17T0000pL{uaI0A5wJs=BM?vQ(ue-OWk) z0CPZ$zu!jAB<=`6U&V$6Mh;S``xlL&g@Iw_lqBD;{P#eF1^{Me0E!9#SOL9|(oiNk zFad@k6MyhUq&oE>Ok*1B)J!ID36d1ex?~e6ZQWibOOK|o5QEeR#z&{!W|t?INYh}-05r6cX~z^bhZWeI3A1(-8MhrPxlex%@?l7Zi(jY z!=b^Vgsmn~sJh^Q2~+|YNg)V*sm=)!WWo>!F&qqwtvKm4`}NvUF1RG(-ubxbKK}5j z6^G=$6T|=OA8?*5Z+w#EF88{3k16aHSu+Z`NOlFTY%R5_wAP#U)elhjI%J5S4RIKn zB?@P_nBT2>KIy+d)NeCP;?S!=FC`% z0goaanz_5=Q)_2gTVvKw_Ow7$jl?4#?ln{J)o3NmG)W**3Lvts7$J&OfkH98K!`P! zvhrHqSl6{w??R4E%~=+4cROI1md5uYc9*ZWUu~XY=hcpUmcZ?skwP==*T<(~^;=JFjApx#DlxKQOHz zpXri5k)WV*2tJU`f{yh!9>wEfQP+|*_1prXVg&-Cx**TZsO_<*v$e0Pu(N`)SJ<+-t+4HBt6^GVU4c%r zEj9p;U4vykq2Fp|7UyPklGg=BevDvkE=k`YWXuuM97AnVIC(b0m3C#W9RMT-hH6*& z>%kSQ&jy$yBC2M2KU;Q&qBUtR>d{+ZLDjD@hWgm6d*4$L9@Rq$^NL|M>; z&|T+hkmM5piNn2<0$531lRmfr>jVy?EGU20=*?;&k(ecj0$)WLTvX`H*BD?)o25}> zwHTPLiAMIKG!U#mYbz{kEbHwn6>yt7IZJPVRZ88#!^LDZnw6Np5i6h=)n;cC(OOBX zr}034N69Rs7V7*|7FJwVfUfW+2rn>Hh^nIHoL|7YPH+QLpH~UInz{jU#W}y2EK6wJ z*+@jZLX!#!x-3YE+wmC-SSkzOq812lN0$_uj}USo2xH#m=qm(*suIpe{6|%FEY-B% z`mv-}x?~EQYQ~W*yD6iI%-qCe#J#90rYNd(^C;mXv&%S^a&83h1XdeZl^kVVY*kx) zU2Bpjt|@ON55U3Y_sffv&1z9ded6AW(W7 z#Ix}kIcVkToyLY;X5gOdV6qb#=he2VWO)*i;kc=7A~eZNJZ%XB#Jo1AZ28wr3Bk?2 zVR%}FG6bCbL=A+H!%;u`2MGvG3FSN-$d648jZ||0T~(t$#kV0LxkNJ5VaqtWdZuX3 zdPxFP@#~`i6$uj=tA+Kj5IpxZO|UeTqTc%+*9TdTGOx(yHrZ z6i6zStc#M@Sn%lyPrskfqk50d+d>2px&jMIsVZ2t+Fpsp5c|T(?Q$ab~8oD*VM7Os-%d)HC1S zcZ0cv=`~Ru*#wEq98wURnuS8tdtbaR+9AW^QCX>duc}yclja`axZH~hS9dFJs|$m0 z%Y*$A;Sdn*&LH}~8;AT5wtJc#wF{bSl?otgl19eLIOs;;R)Ch12}EHkp*CoHSL z03(>5uN4e&S+kp-V2EdNOD;E*nbmcm$u{APMUp2lwQj^S@bm?+q=4I!L4U%#v+<~C zxTtlAt6)T?4ApV5u?;k+rE)W#vO?a=)mm9u$58v47E3R~lb+&u*7t# zBzbjx6B`@Im+689t6|3q?96{zBuT{Gh*gvVtvzBWTX5-g;d*v$H^@^@AIJ=^gXX7Us zY7Ef^^gEysU``yl89zE&HOkMmJ1NhZTY{Zjlr^|!ND{uGL!q;|6N=I~JC%p~&6$=x zsk%YScA>X6aJo|Je22s^5tvM(?-xEtIA7hIomdymqT#IXs;;%Nhn)^u zu}bQ-oRdzn?eXYfbvEdFbIPKQ#CA&oqdTE4HNu2ZYnIc+mMSUb>r_mAy{!th>a=BU zIYFSrJxqa$P_#Wo3IJ2NXGVL3(#oFJ(VV%JaiBv^qNU@aSxNcGc~Qt)?3|+V>Oznb zJ^Y7oSkl%WDW8d6MtN?mOH|Ez1GuMTb^Dcpm28 zl#2u`MWwkPFFt{?V6%a$)M;fj23ir%n;C@5I$#!f{*>uwRu2warC!gVE}Zrqz-_#W zC~X?{&IX~Va%2X6T#LfGPr!y&OnwUdY|t{$3KSa+h~GTxP1C^vxCc{4vY-SruVb#X z9%t9W)?2E%_@o5LD=p{7Jj9D{T8$bO zQWjH56F>+lv?2k1C15BjM*mJ^IOAs!98E#;CIi__>@q<9fd&C6W#GzJ>m3~navFvT zwo)$a_s!;&_>C(4oq}1PjLdiqfROmN%cPEVsfwDYPig%h)?ryybWsO;JUU(=42r`l z^+CMoGLu+b$HY6-_VYw=%u*ILEbV zDO6YqbE!P$<##3xL1s2+6R%gCPh`ZZIW|_<2#@O{R#-kW^0t!mZLFOODOL(prD>Or zr04+YNP{zKDvH1fNRO#ZmYa2UWu5v|)nD}tYg*48vyFf5xJiV0vPQk-g+5`+gRna2 zQ;7_Y5xq`#`lJ*iGp$CqRh&sp(SW`=*Zumu{xI=k+w3@T&PMNrpiL1eC9l~e5Yi+GN+V;S7J_{20qn)Y_TAPd}5WT*QI<1+cm~hg(j8-G} z1mx?}5&*S&kVi)Y|J?)N@h51Wn=eY1K?Q|*-vc-g4w}Yx@oS^+{0Ff&jnPNj{9qVD-u|40XEKsBD<4gcZlpxY)IC(N*4e75n_l!AB2}5w< zh5zWi(d6miD#|ARp8oO$LK}-MJ473Jat>N$QWEqiS3E#Drz0xJBwkwE?Ro@1DSc1a zy&e+pss}~Xvw7ae?J*$%iUw!T{$VbtID#+F90sH<5e=La>zT20lu59YmeMyI;)` z3ylmJXlxYUBcx|nt!eeBUCEx4-$5ZKjM83gcTtZ=M;*kE1JBNVlTFj^XweE7wX+Zy z2%>aJ2tqWnhDg8^9-U|URgPl+s{QM((x47prGFI>Lx>M!q$bPcXRrW6a?#mFt&3Pyu}ge(>tb+k5gfhj91D>&bGI ze8QO_-Aky_lxjp2$gq|R(|1!D;~b==0bV$1UF6Y^XJn7psxHMj>{Y*??JI9lI}~|i zTDztZ$Mq+WrV%>p&u-}Nn~5Q>_J!}#OL$DjTB!HcrWT^^Yxq(1)*u*h{$T3fWgG=j zGWt_uUXP}2F&z6|dl!h^FRsuHyv%@pAN*$iI(DI$PnV#N@;{vX4n5s%a6LAEaeqBt zU@>t=T-Y3K#tC>dmkrr|vq zaMXPfv8=K9XuGw0o!l|DY@Y4z_p&w|H;pU$OERm+j=Fg9RCso z?KW}eZ%Np3FQ+8xXZ%UXF1Ilx?^PwK<-Gnx-j#l2Z9z9_LITJCMXwc|B7@&5l2$U(F>PuvlQ`#O84Xe_3v z4FpK^^8PPCe|}b=ke!{2U^&pB7oZ^-Goaias1Z+>?zVRcvoeG+v{H^y2~Aqch(TMJ zV+-TsO4(Ifv4u5Xpa22_%uyhu5Nfh%LRmQw;K|7kB(k6QM0pBN}%au zmAO9U=SmYjvdW!=@^absesrhBEMWKylT3oZsO4dM=@YUmJVE(|CV98_4VW$OvzL;) z^N5m}XS-C%+6%3@ew<$`cqq1@50B5y8<*CIu)bH|d3Tz*#z#xc?t2i2X}Z3qIfBpK zaCGFp-O6DEc)OLohP=pe8+v??{NS)ldnfa(>SWvqWIG7g0Suuoy^2HXL0JwS5TO9v zPD9~i0nOni)#OZwfJIs!%JKbMm|-VITaPC+M(i0_47TzXW=^%um>>-N zW^1T17<1}-t(dRwbmpkoiZ0am=a+KwD#|2K!X%cV1iw=RWKK5wM)YQ@USc#pb1xKg zx59|s5VFYLC1HxcUMI>PkQ4M4&6k$o%!)`LA<}G8hJ=TVnk#+K% zUw+oC{}ZB$A834jviAI^r;naRyH=Dlnga{s-O2TftVB?U!`IPH^4-HuTsKfiFo1H# znO-bnlq04J6?LmfZ^7w>xMQcxHK*au``lPhdNX`P(0`^UH+=`C^v{+EGtGYc zKZikRwDHh0z94JTgxUBv#=U-w+KwoGiwRh4s&twzQ)1jS2SJZ)b3YUal7s;zjzTbg zDedIrA}qSqxjP+Few=>0^uvdDCT|Mh*f-qV>KgUge7k%57^7~!+OOHGmK$CoWm|>e zoe8_+&6f6}^7#JVKzm!ouB6ZMW_aE1@5GOpjT1c6b{fC5o&CWp?cby8b6;3dXV$Bp zo9#ix3gcOSHPR0dzoQkNv1#&LW>e?j+^;+w+c|~|6>K)#e%O|+`;U^dE9zTK)e(RB*2@rn|$~qM+fIwwy-JmpzWu7e2mmOnu`_eBb8Ua(m+vfPoGW5_4Hz|%}mj4Cg4+F;AOXI{O|{oHg7tP zl57;G%>H!kyZ+^7egnw&ZibKHo`1`4M8R|4CEwmCjw_Qj=X`(jtkoc-S(eB|fT_TB z2be<9h6uN@S~zZVxJh{}73;NY`E1E? z@`$#i67zEYwoS7&&kFm@Y4z!invLFuXTOK#dDSxf+O`bmfjUHzQ9+YrbxBy!goo9# zFc;g>F%{!zcK)2;3Usyo7|(C&7)nsv*@tr@B*-0P-)|G865068T;$sdOme~B-X)n6 zZ7P;>Bq)dytV)VBjR1^tG6f{d2%=PthsAnnd;RClVCnf8Ok;krvSvN?ZF8Q#N+;By414$g zEhz9V=hZ{u%>I8X&a2bzzD4o=@noMG%kWohnuHy`Qit_Dj5JYoAY5Zm=};+*fGk6f z4u#Q_Z@s5NVo57O4fuq}uN-SIh3-I4NDW1zH^smyE45*we&70+G$uE)gb z;WszsAg&A8pR|K`juGt?9)wc3S zY*Quw_y8{GZLV6eC=Lm)?+5M@+UkfD=aY%#6y|(p1Dv)}O{L+(0%MW!!0pR!__WhI z8sxC4bEw$Xx5ua1HWu2N%Gw%-Xo~BOsL|BFj=5ojxz(+%Xzvw=;zvwRU_9?={~_fZ zk!%I=Wpml8YybU${hu=p+3hZ}i*F$GPv3rLi@GBY0;#?CJNPh;w~JA7<3!Wod}%)F zDy-tDrskP*@>XDa1{n9v{>+x7c;e9XqqVx4nPrGY_VLyf^pqXd93PBC7=${1$Jw{v)!jzpOAe`wTAa+zQ`E@z*kh9_dKx?)70E zj2kz(OxyMphpqDo9!7mK_py53jz{0Fr*?3iBDM$ZlNgGwv*Et1@3~7gP1<|i(k__m zdlA@(OEJ`!#c`{zE|2_Qabhx{{pk%OH<^!m!!o}PmRH~9Xy%&#?K!BQhr!<0OFYrM z9oPIT?mO>t4snXCdBVwk^Doi7xEbX3IQ8HJ8j_HY5~28)YH$NiL&6d>#N#Fp&Md91 zN8g%kdzYs^f47dUmfk^H7}wH}+U`wSoyQ;ZcPoS|4Ew#dTDWM-Z+|_J`l>GU{`@=v zsWHc(Dc(vAba5`<==b2EmpAh6yOHI9;JE#L47~?@BF0%$TRzv&^-%GVyt7ljFy=M* zn6mD{6dT0HVUD7KcSkJvZ~ ztCdKE9{YdqSj{d4xu<$JFD}l@YmwYNQGRjfFI+c~p*8xqY_oetl#~BOf+dRgSn224 zb_ypUg~8L_#`%4nK;duG!2ZB%UCzjVo_%?0r@)3&nAZ4){ZTw*33qSmzkhgF+Kf_7 z3qkUK!uY=%K~ZtROIv+Lr632U{OYkPPa6h@0!45ekIT=zQeWjsB&K?!aZuES*An{P zz+sL3%r)|bFxh+=$ldX-@0h+jd=Ek_z7KjgG<>Jy7EQQtETsb2;hdozV;Jkspy zg$vH>O|vF*qZx!*{a#CCNzQ-6QGfdB?hgDo0Qi?M3`#|Rh5M3gFWvLm+War9zJ%L+ zP3h!o+ton`C_1N*g)(p|6;RT2D^wJXq{yHu6+C8l&zf6*o0>+*+v+=k*v#aPer)qW zT=v|-(9({$%bv}4+YK@5$@QK_l~ecM-0%`~-~7Iy$KG4{$B^%&{p9&w!w^r8A=9g~ zR`|V@Pdj?gv1?CyP?NLKJMSS;K94dH`4Gkgt|Q8jAr(ukm?&TZ00Z_0Nfsn^eP)7= zys46mNz=`WK|^BvQif`e7X)Zn z2nbY_56|#WqKcL{DM_9gpyT0}uw`SjEKF)v4;m`t1sy#<2K=cy6fT)?ibBX7NSP94 zj0|zlOVBm?X-tZNL(Tj_hG3Zyp+%>FgG@CfiK>TJ;q_Bgr@)LfanDQ8a`3Ll%z|oF z4;oD46|1a6q7zdUYgGW^GaPBd5|x#J-JODtgcX z|1VvTH)UWFMqapQCFnD{?K=WiW|3G3)T$pfWyUE}hys_C`eZpa6k+fnCE`7X5$Bqb zIu3pbUHyxsKrdFsprJDUWU8V<2p|LsB#uf|l^xOub&W zK|^5t5{5*gOwq%NR+*s!2wLO11w|3iE05)wf{uk>m3G}Oi)2)U4SAW%y{ zH65@ahKqO|Yy1Q%n~}XU0B#Tv0{{R3Lqs(s003T9#HzZhyrq^(Ogr-*9_bQ087L}v zvTZbmmGVL=bCNfZn;vYYY~9~4{P$p_1^{M8z={k2MgjVmv5ioC*=WS3{Iic%TJYL7 zfO^)&hKv${NSZV&sgaU;-Zx~#*6w%O30c}bC%cZd<+&-Y((>hvC9G@ZmmwceK`UBL zoC#yJb(x&3gtfMOPnJX^B-h@AmHYr8VgN)l09F75z4!L*-0s}XmE8Yx+xNSh+m;I% z-R-;F(wA6S1KW4&xhybJ2m=U@anM;~JHiDDg(AiXW+*|`OcjjB0!XM6fRjM@pckk| zOn4Q1=Do?1*z>j)RXbnob(Z6cC=$s1dUf_#_H^xU^&LsJs-zmL?b!ltZrDa z@cP`dpN%oiwTWK97qlbk$MBIT9xQmjQHuQQv<-s6x_i3|v+yTwq^voS*Q#J^RK~3v zfGVDr(A0VwMyNuCnZPh8kz)yrDtCgT8|C%PB$2CD#vm|CXmi$E-!?m{e`zdghYIH7Av|>P;#>4Gf4X z1C~*i@G?c67GP4s#xQjUS~`R{u`nC7`OGJg*CG0vFsQeqfw)km%@R9@miVBEhpAHq zP@@c!u*^bY=5}ENtVNeVzDovJ% zKov`o2qN*8V7ewCR%x5BrV{p{j`Rh=#Yy46`Avs2cv zmPC8q*jY+RPDLgqau!)2k`fc>s|f$k3C=3Tc~dgqi3IA}cI{=hpV{u1O%#wSTb48g1SJLb z-Fh$y5T%Vx#+)1iMX=@~Kl4dsniF09$BqV9U;}C1gj7_tT{aJ`{n(!7Y^0V$jd3bt z<*>XF%@7dI0@>|Ec1+ntBXDt+s(_Y(7L;e+iOg~$ zvA z`pe}l?pPV_$3Mi&#^Eja;sM^Yw;tiP>!3F)`!%npgcpJ#bE2sAmX!%PlP@!p$1j%c zub!9*IZVDn*PU`m`Kd>wsK`pnzF!0HDR%nE60>YAq-lM(PYE5?m5I5?ouZut6SPp^Cw%$a18`UH27#&X6_~b@-2SIg6isaQpfK__= z2yzFLY|16nkG1-5=brnH3QbzEWm?%zuf*!1S5Mg$gSt^gmuR2mPFHAF= zAAdf}l*oG{ztAQbV#i@r6%Q3HP8b>yNt_q3XCZN{nEkRyvvt7>AeMn=4rU|;wm>m$ zWeGMBlKz81*+Uk~g<39V>gfy3imHN1(Nvr5K-VK5BI}JX@>)_y7mM%u?E4M|U(u{I zy$`tZo6UykTkD!Bu2g}jQepB0QS(FVEZHVsg07%vn_`ty(^O-V55{ZsMr5%O0SMO8 zY2%i++v?B5fupYN$00KIfS^goqXN5sOQ=Q~ZnQ^6B1j;TT-X;2V=jh=1rRNeEP)oo zjZNX+GFKQuvk8NH7|D#tRU^V#Df@MB;(cQt5;%aS3B`>HEByPA1cSh$Fy=G%q6Q$6 zby{H}PhiQD?))lJnJU8b*uWG?a58%3IC8VeLtEbX0hwxq-_=nMB91C{bt~lcI%2bZ zwsnOg{ToKG9AMI+@1qhKKLbCU<~Pm@+=BEd-VR6dAk`i@eWqSl24>`9~?D2@)phhkBt0cLJA zKvYYlZO&$JtIU$eks(GHbgTEcVj_i%21gze?YwdfY}2wkNZH4`LQEi%Hd_jpagGEs z50leSGw*ByZi!Ed#MSy3G053m0Jy)(S7)i| zt)i&Vh*5Hwr_(ZjjGarx;>9eE*$9S?C99$a>}vR=D1VNz-Mgl$?U#l~aT_y6qP7wq zk*h6*;P&{<_Vto&Dv_8eNYtez>_5vClH7+FF4k0F=Q4mVd7nJ1NeMQgjmsF>c&Smm zfyG`s;Tvp{)XK%xhe}2D3`bF@pUhyBzxJA|B$(?cF#&#xj9;4_Qg8RwmXGSK#PD@K z>T6$V()nhcM2Y{tsBLRZBlmz+A z!UvV7y2ydl&(@Y9Cr#LLjJ1?RxxM%!TeH0QeKKy~_sRRp4q~qLh4qSvW)@c;psS++ z@mU)7Y`moy8F$NX*1Udt)e*7`B$)BawWz76h?ps_UamIv^|!|Th5ovyOi&8<^|?ax zdi8GHUNk!uSXb`uFWue8*adYztZ^4~hIcf(A=P2MBo1Ofp37G-7$JBIwnK1WPai@z z>nm|Se6l{#ARaik1oxsju<3MzIJh!xR(&myvIv`icnacLZ zw6tI58)k2EGgz#TWv1R|410y#?S%~CY#SRrWM|GTI6GFN{~Qa>8khCygRNHqHyp|g z^4TFcYWO+71LGobmH*|%=djXt>yQijg`O988L=&s=yn8dEpYcE;7AN=8So}>n0 zBsJ=!-_BmJx{=?`bHi$!ne|Ecn>8^Q)f%flYb(Sx6K&YT1V+N_sqHxY_7z&2pML>x zSU`OEgDe*rKG|3ctq-nVz#qP#*_)hDXcsPv_k)ET4f_q@rTC{;e^iY`&dQ^E z=@p8vb`lbS#gIYoyevw1r$iAWtygNVBkyo!o}Q*X6?|UpmArPm9*wzwtS5DI*MaJ54~HU(>ZQ8s?{+)S-!JRzwepI=vM|Z#Pe3!9UW#YTJ~EiM zVYxKHRlCsi8$Ee~-u!1WY~s&~z)EM@H%Qz&&FQarP0waP%u(4vJ|=`wi4eRuxsO8A z(HY*7*-&=iipo>G0eo^W`qTj2W@@I^^LDRa9*yrVrl;GdotT$RCJpyY3btZLpyY_8 zl;``AJ&P?Tw^zEcuj=BEF4_VQCvbX{mU-7l&NWVu4sJ!yw?gR3^uLXyu} z%m)L5|NXm3lj1myPBOER6-nm_Agbv=xn?qf+I%Ty)p@y_^&^MlxcK=roaVrd0S7bX zPHULGxEe7qA0K!1xNmrTY@h+O?U>!|4dk5y_Rjw0)o@wE&*ink)44NERU5fFpkr6-bsx-(d`BNUQ?k5H&&J7n&G@L} zr6T=Tx6n1L;m0!L@3|kTALrAL!+hKQ#Cm=lIlsfTy&b@1-lD}JUX*^GH_S(*5h6W` zzCw%8AHA(;e4KNvuPQ#eLmC{AkEr}GUq;taylzBWr0Jt2d;6$gH*Vm)1sC#em_D(z z+-#zKEr-LMSo7)H{p=Krd3|?4XpD>WZzrBqJ25Ewn(G%GTI)h(FcupPj#=;C_5UC3 z4pcQXJsrR-%nQVL?*QDknrrG#)SrA;IEWWux$oxwm*C^eXuQw5V=o#lvcz9TB(dxV z+`N+sy(^mClaLzHir(0CYyhJTkiMGJ=*pi*{#$sjm|55X%-W!;Vqw@?T443@!pV zT0J=`X#W3jfd4ge-0*1}s8Di>&ydg1R=zgtmT$;o*zeU%T%K=!s`I0%Eo8%Yfc_pW z$lrBV_9)$HE^g@HXKqDB-_*CD-(cdhO`x#u?!n1b+{{BeEFB zr-we?!1wRbv-`j~L2kngU4hs$S>2Y*1zCVH#(Q0uKfaz|aV~TR-H~P%<^KyvGSkMO zl;HY+xv~;XlfFkG*uiLjSN6Z8$}no08>(^tvF3Gq*dfz&dK>v(a6RW?n0l7!(btco z9fcHVDwt5PDN=R6R4VB#`SR;4AEU3G zl>Kc4N1AbOpI^N~NP+FDTdjuAEaCo6u&=%pZ~j^G^*#G0F0BTmmac`4p*eTHiGvmE zHes2Zkmw!fO)_)SA35s2o0|7(N4_PTYp=THmY)`q_j(tjyOy|%Z?=-We}A7zgaih_ z3NI}8pg1@qs=u*i_&i{=h1m-b& zD!dw<_j3ae%Ns|?1-pVC;@>lU&G740tg2o=RJ-X$T@n4TT}@GKN*c~+OzgX=*wv9$ z_ShO0Ud@vBup7hZv+sxozId(LA8X}g3~j~nudv(GCw<=)_5aVbhW8oC2fqi6JBhPv z5XrE_l9KSb-oaCFY6tM+M<%AkXActt1y*rYQ?qhdp14bIc;i1?-~QF>O|#+hwuKcI z$dpCN$r2j-g>5uVS~lWZsJkKVTh;ffc!}*TUdN5gH1_G;uc`-(=ZQDKFY+_TKl3N* zpc?h+a#i41L*fFP95m!1CF2dCD+aJ~C8>x6Pnoshc?i0d~Qum!h=gFRA zKFxCAI|&Xc^#wn@>}AKvo4BmV?-tWPcsYWM@;Nx^-VOeEv%DrFg&mW}GKZ2xm%E zz9XAaz7SA_>GKP51%GUEHgYqcDc+3shzq&lJm^*oh)DKFmuP-NtXMW47*Sdsw{V>R znS2%SEapX`odsw|$J*mjNG|xf5rt}?Ubn*|NZaI}S{Y1E4s=O1dxm2@^IrX89L@C% zzToY*h1133R3wom3911Y#+`p7x8!8a`Lt8I-VWvT$BBQWxr>ddA`XQp5TEw8(w|^8 z0+*Tw=qLDT()JGUj7nU?R5RTI}!x^VuN{h#z?rR*HF z?B9N*8?~bPo;Dl|3OaCygv-u;-%mb=v(dsN+2eQh8)AJBGvIs(c_sf%owQ}VmiyX? znDBe>55g#fAB1Rxx@YZ!FuTzU?{#h>n^2FvL2%N_LdZVNj%Zc&hX0Qj5l`H$8qnS z(9{7y8fNLSQKFJ5rm{_{r!2-9_^gObKEjt}2KAczl)OKPfThyl9hkn6)@3glNqxSQ z5JyVscy3{Va8fx@&@?4MP?n6+ltfWb5hawolPrw@q%0e9tPH}4t4982_q9o9F_MM4 z5Em8201M&JOd4I4rY9VarAQDd1>#ACmL#i*Q7nYVknXxCasT($#US1NyRt`dsO@Xc=zdaHPrvkh|*&j6J5035FZ_SJu^xo2} zE{pH`1aJfJd72|%7o5L`z#`8#J2cIv;xbFqw8pYeF$aU~Y$H-C62Dsf#vC=Z5s|o_ z0ll8ln_&-}B0cG$*+H~Vye2QW9F*Iq^}6rnj*Ddgt$ix26sQ^6JvYWAgk)<-MGW%f zaEGz?{JhCr{^`8I95kLc=u$Mism0Y~OM80aWwYW{R?aT}9dJFWmmj6^+<)_Ei~B2M z0@vh}mIJ@{DhVe4l?VP4Us1o(>lf1{{px!EE3XrUJcjZ_#m!_oY20#%(YK)a=+}Mp zL$???yBF*Nhfp$VVe zj)W|sVD|w{|DE`vz5UwD(}aP8h+9n@9Mq1S`Y=upXEC>v4>59D>0?-kTE$>H33hF; z*rik?yr^5UQ17%l=3c#~5x>wvC7meK|A@8{HkGbP&-&$p`P${wFsu zAOHA`ym#d?*PHm!o+e{aZ&X6fOh>0e(>M9lW<%`a6DFJKb3Z@2S>E`q!MjU8@6o14 z_uPX^Y;LEXW=l%2m0yfT*SkDq5IPG>_(Gn#@ z211PzHNn;O?6EYJMijo()^3}j|eA1${3#fq^g zUiYM!S{{}JL7@r9R5d%3>gOUeQHM1^Ku=0OM0OIfc8QMiEUys7z)`=66;+hO7)t#; znie?fMu|jJ7Kj5CuyN4TAX)xgCVx_-nr3K{s;DSPR%lcL*L*w9%nYK4SS1c!h*kx+ z1xLr|V=~paMjS1pMCA($zzjzbwfXE!10lYRoG5-&)iK`DIAReH`)iz0&%!U)uN6?q zM(N0hnBs_}7dC4F6DmfqrGlhfsA^#p7RV8BO8`DG>#UD{eBwo9HW4)y1NME{2#R0P z*KYR~e%ftiZCFRYG!^Y-CH8g8uW+JzfG11yU|?fS!H!tUst%UK#*&#BS^e+a>`{ke zzwjb5o`{~~-??o~^;qIvMTmZb6_LxJGZ#nd6)B@A%8?Sey5(KCEsh*Oh-z7!AV>w0 z1BFY9qrL#lJq&dKi)L>ENGO)bej-tV&vj)SeFEMsg2QVu`iez`xQ9<;V*lZaqnJsN zEfA*F;qoc2qd#jC@aX)oqfjX$h8KQx3n{qIMc6z?NNd#Pmma+%Rv&GFDd2qdJN(E>R2 z{)bKN-O_qPo%vBXg*v(u+J!Kwsw%k7*V^>8flYFGgodPLcJv{iux_ z7CC-6>9W!AmbrSHp#67S8a;FQF5t@xQXh`CXgLfv{`f~rKL>R(z@u_yO&ha#GV|i=$ zjX+&Hu3y)%YgKl&*EVh>sN)I~Xk5sh6XFWZ<*=Ag(zwd4Wd1r1r3IYwOp8>NoERnY zmp{R-?yI=!$a*YWOe}JhHk!Urfq%_4ekh%V07wuJ0{{d7Gh|aH003`gYL)g`e6N?9 zy@?~oyu6mT&EG(z1qXzq5yE)QLXvF%;fBBfJ@z|~opoUU0Eo;C)zAQ(zyYW^?j_DZ z!u?!e0fh0N{xzy6#Z9U1dPJPqH=4Q|n#fM>?=*6%`%TkI?%vm(dOzvcfZF93gl29fFo_Jz$G?-UXH{Z^#h9^Ci# z*|nE%Rlau8w_WYL`!3yXD|bA+-nBi-tISUuTk7ptUFGiYS1+zBV8Cr_Jq(1k?W(WL zzw8gRv|rklH+Fh_llK}=Z!@2Wa_EzRy1m)Fw@T|&aCj@R;u{hv@%55~_&gEFs;FOB z^E^sKKLZ+!J5(=kvH|n(yNjvIUw6tR2mdT-xab;Rxp4h6Eu zm%STBY0OnOmX_DHR6n)_;p4dQhwXbwyKeZx^?G}D8Oq+5{9S(jy>9?|es(V?&nO?6 zT|_Q^{EIY-=JEbH>TC1}_JseDpX^V(+06LtS2rCrp?}s1@Cu!$7$0)!b(w;7D6fli zt9-n@(DMEKW0z+@uTnl2bodp#zI(+z9ml)8)NycKeYf2EydMyhkAA$q$X@IO9-b&8 z(sHuGQp$fY)Ye;h8rlhEW$(MoyX?mvU*R!raKX(x3FRl*1=17B-xS=`ikZpIten_GF?_;yg+4#wNB@Vl?P z|AZFO)7Idzv44Kg{MTn!#V;|Rma{Ui9uEwB3kGoQOY6SI!1uMJ+AszwmH6bm!8w5%>)uSZ~rv=a_?R>ey&>GY}k&=4~zam&jwj_ptJSO=-G?L zH;l%rwZDrub2IZn9tPTvaWkr?MDNuP^YepF4Fzj8ToBwqWl|@o>+ds_TH~T?~rEj(tOb=`(-RoZgnr$IprUo{;mhp*6J6MG5=1f5`DTm{3y03s$Tdj zy=xD4ziIur^nq`ECXLT@ixIGcL(Vxe&L^TalQ}lw@K63 z^6xNNN9gwXSRadUgC)z%0}ec{yZw9ffB%IS2cC(uy&=V;m|o*G{$M>G z<)^ok^_f@CmAl)joBW>CV@o_o*Y4WH9z^R>6Lw6?O>-0SUXxtRRM{~YiF04+c_64a9! zaqviV+>R(0T?rt<&aSM_i$fsHcH_s8WLtryT|$0XYn9@H>2M?f)qZG>2Uw@9uL_37 zQUV}GLOnrDfTehfgcaR(nT8{YV6_`#RL&_RPGQ~yffV6*2_xB_fLkygt05`2BF3%B z0)DZHV6_{nSQ`_uFgV9lm~JhU(=dZdL2@u2)gdXjBDGbItzU7vXRW99;K_B7C{^Q02V}Wq9TW4>p9V??pB3$hf*oLn@Gj1-*F%&>E%fHBT zAm(hsOl$>?JG0_lZ)0t@UBIiwI?fYdlFVqht7BnAo~t?Ye8#s=k~bk~1}GjP;jOl% z#jTz0oO8=%Ut3>SYl(n-iW1*Ax-7-0q%2vn`3NEQ`r)t8dRqa;MtR%2yXK3L}*o)W=oIm5pCnX8g9@G4FLL$D_r{UkBh zImw(r=&P;NN(46&!D=_G&{Y{iKt^Bih@3ze)PcoP4mrky&d^$Fk$C8S*^djY}BV01;jI)Y`A@Bl7qSNw=b=MLTMaw@WERFdB|5 z4yiv}A1yPFAg~l$k=9mq zvuRyxS;tOfbrQj9JA%LkJn)r4DU&ir0u{2D;ee;c~M^Jg_nvHE~w4R}L6ydytdZOFRaQEgkRd;MA#P83%|TZEP3Lcj@4DQmX(p10Y_p`Z8?YwSTq|j z@V81-w3(hz9M~w&_{%#8Wwl6oXj}IhdxhLKc6)oSZ6WJ@b+)C)=C%5Yb#NU<==4_y z<&3Y1Hnk*5Usj*%h;T^gC=EkpGEF`rdOY(oQCmDkLeFm4z`9#L>)PM;>+F#Wa@6g- zWhqG|s)3b@&C&@dc{4!H+|fLG4s(3h`N*Uj{^7;u|6AR`X(~I&>{YdK7_>%_q@o3& zJzjszbL+%*o0zFXfw*?OzJ=`MF&nCGMIDQs%}hYm&xcWJ6e>B8(|jQzFfsIY1yw|d zhmBs5gowoeYF8SXYV~1r>5TMm+D!yxMCca%VQnoJa`m zv9(thnHc8tkxo^=7NJ>G@SvUOB(WK1G(dyY8YvL*_z73K6}<4)!9=hs<42-bt#8t$?MNS{Dh9!cUsO?R>p&eeOeQl+6 zov(gvS`_tStZ9nZOK+t0mB=9EL+MmA8gdRELj;M5CengmZx#uXI$Bt3R;Pljzpk$6 zvHB1z%e@_>hIwTzV@YCA4L)Jucw%!nFt|CW%<5Q>Yq^st=B=*8 z(Gp%Oj+ov^B3eyZAF(p`NR9_00cmq)>KnQZ0=HB3VE-FMLMx7o`J%nH>yEVpEJQHc zkP)hOwP!Vn)jokuv+@e_jGvWf>2Uk=RUw*Kw8P@R?!^)Fdr1VU39AeeJq6A+%~nXI z0!YV7=hL2#nUfy4E3rtw)Z7DGaY#F&;(`fwBpcNz%gv9z$zA~991w)$kVqvAWNFk1 zfvy&hu~~O(V`BI?_9UTSgU`|wh_mQFMaQCGgfk4DVl8=T5Dv5}5Zr9o*%e2a zMMol4t%a@?YJ2N=qQp!|4Sc*=dOXqZXIiy{2HFby?8TXth3=lHIKebK660#u(nFDz zJ+5tRyV}!j+T5950t!UlK*`u)UDE>o)Z4M~_C3YHWI7V)YWT3x9_v*9$TQQaHgx3H z!u?LlQ?kraE6j>mBv0zwUs&hbmJ!TmBT}lmfYpX(S6Lv@?a+&wA_U?Bc1bFy*u}Gyn)?Yw&Y)k#n?r&N$|2p02T0-9>587(|;1NvEqZAjcl7rU?&BBRBW(Bqul$(yR(X z^=$<%cthK>Z4HDY5v!I3mXmP*Q$jc>k$YhsPuEVUqW?8HdvKIvkua_GYWN7zKsgej zYU+bDEgW2<_IzFBw8+y@KV#V05bxcEM5WskUmRS6e57LV2G;YDpo7!uKHJwW1vspVZpHO6UZe(;(8V1;wf zsN;lW&LG_AR!f6Jr~_e0M5^V4Hx&{B%H@uW&Ud4T)RQ_Ht!hR+xsi~qV=Xalt+gJr zv$L|X(qZXDI{`^Tt2H!n<7@MN*PW@U1eMqN8Af@nsjWR~kwrqaj{c?R-e+x>X~|*; zW($(0SD_dLB>64+Id3?nmUB+<^efT+MrTzU%HhS#^lbx>S=+`PYkG)Gw!kjcDw#NRQA(@K}}ayKB}gT_cgMcK>UzUXqy$B7E(4e1TQEUg_|SuFrLn zjc`LgyaJ{O`f-o9XV=`Y^h|4aV!FD0>>ZmactR4dYC>0UeBtTlVY*f_FyAw=y%#jO zJ;P?aMGH6T!-3QLcmiF{RFb$>Ym*8N4LXodrqR>aq*|peiEyf&1ko^L>@Y+x-1!ej zuw7;Pm&Z11>_`&7YN(=(3i2Dj1Tn<&HA-2%26TF1@!->=J_th>ZvTfxyYpqFWz+&c zlt}7Yg^cJx>A&Vy!WN0S)#w2s``^uF)D0={!W{py)Uf#g(n;zuPf7$X^M8v*8Eh`F z`%`UuTO+4YO(m-yR=8mwUYg@y_6jx{KpwJ1$Y6VN_BM+USEgD+%Gl1EKC`~v_*w?M z;U8W{^Dh$}nmu<1!`wOCb#aCqU0f6;HS_B(!@6><3HBcQ1AFcLw; zhYPn!q+2PiJ@N!#(2=OF;wuvBX7_uVJCWwT0SYI?-rN`l$cRV5kaNcfh#)!ieK`j( zms*j<`3q+cpQ`GyVsk~Rq?SXBP|Rtmt-sZsJFH97EtX0H{L9or*^dQZIz;}utLm&_ zszvc7^@!^N<|G1n0D)qP_oV;{CN?nuI%t}|GU5%H$J#u{)~~s#>amRUMXDsEf;6bX z9XxJn#Kh zUt+%X`>*sq&F}h^v!M7pb$jiKUn)kL#G@K1SCHPBVQentOrpIk?C!Ej?rc2CJ(I6pc5H!;+4|erAl4|*?@ssWSTXhd&eT*rex!P^o1+; z2ctMiOsc&N2cgJ0;^vo9JP2Tv`Gtj=)ZDN4^B_$2Sf|{?@Hd7GR^rPoO|es-_YnsS z)UM>5LBP@@g6sRhps-jN zDp8<5g)?b}4`cCX*_Ugr@cOHfrj{lkt`%Ja!BES1ry-Kkhm=rYU`;Sq8;XQo-R4XF z$D~zkB`}7?6^MZpOO@#^#^xe;!+proh2+RZ!iv^y^F3`*&-zy5=Y+WpNtmiq6!1X9 zZxA{;GjU4bU8FwHxk)c{fn6kPO7Bj%b+oUuv5u~p)uu~ynF&d?ssoT5KsgeyE;*n; zM&qd@2g9I~W>^EUNWRjty4$ynMQ)Rstx0sM3DFnsLdl!_O8{3SvzHiUCQI0}cDtDt z2}?cG_O32;nF&d7s^d_noQTHs1}x>-9GcJ3Dd1v5f7T!?5b0g&D#~l7ILok3y>cW` z)#%7Fhy2P|1(d4=S&E@g#(J^&l?oe8^kPVYN5()PRLGr!*Y+;9kZCKAg~ z-f>jVAcvqvL9=nd3*#G}bzQzBE6e23*RF%Z+ObH#xiIifuj}?z&=V&yV*BNuan^ylV+aIDYDniK!HArZs?_EQvk)N> zja0Q00Wr(~g24y0LlLJrVmrl4<-wdt2;j8*WIB!ldcVfk-l=QmsoJSJGa@ z8%$}NdoNdbe?FM~(itO+Sd7JDT`w7+uZu=Ehg8!R#ozbAXc6b$hr!ucerdBF5d zlVf1*6TYDg`RS3glqA3D5Gg`b3Uz5ht1*I?mQ@h46(tpjVfxNMwSa^SyRdg-kqvV? z#)}J`MS@;-SxfYpp>c8wGbct9m@hgG0gT%QNWaDL1z~XM_{<|f=}wA5CK@$rE$6(< zGioqWX8}*YTVjX@5adJYIf}6?5)G4bUM|*w(~?f--J}Bgt*me2xrvt5?tR77b?Vgr zSL1BCI{WJcIf&HNTHg@Fa9;#*=%4)M>GAYJ8%X%O)m!k~i2?B-!lynZ%8waxwArXy6HF7n8jyrG;1t$Q-3qT>+yiw-4Pkoaj zVdBO&HgvzWXnNq4Mt~=T(z_C42BZc!4UPu`n1a!=GPi6a$*IP5OiU^fkc~#GS^|Ly zEC_TYlVYQ@f=9ehMW8EcmA|nT3CKxqtgUQo=aacyz?Cn1&>W<8%#ykSH^3f(n*s<9 z^C+pM7ji)801MI3RqCmsFgWS~O43w^T_r1s=>kej9rUhPBtzU>w=BC7cEr!XlZ7}; z68h@F!Yrt*Q{mAZbIE_})Dj?}l_!=L335s=w{{@aT;xCEB)2vYz+G?v;V^~89i)r-=aX6` zfavV$4kBqWKr0%+YL&2%@W#IJP6C9V@K_k|D`SR1Gnj|1K*gFKSIUT|<4K{rtX~P+ z6m@XTP{(*GT_sM$J|@x(5K0+N^1*T>K3hqH=&37_VKn8|6c?p3g~8NJ=A6 zZ9s`)2It}EWXu|;_}{mHc}7T^po&Q(e6L!(AD#7!0CRO$dqM1?uRvfFBM>tI%2ZAO zrxRBHo7qA)(9+U?L^NX6R>*-#Q3gYAfzhoAnw`o3I%-k{A7$8tBO8nRihKKxi#cJ! zJ})C&Q9wbL1TRf9ECJ*>Ar_42!B0{^dDzJs6>1$Quw+(nAxbcj_Que9P%oX?A9P)0 zm<2-CY#SHd`)=*t_L%S5Uv>6re5C?;(J@3Jz+$Wv030C-=tqJBs_dOOEx#68^?m!6 z)sJh8^tV*%TU3TxhABjU>t?*ad-V?EsQ)Vw-@A3qI5U3(-Gj!rqRL_LrAR`&1!jR# zY%-{o_x%4HalyYNL?J1=M9bBc=GF4+N;J9gbrMB-m!6s0?^=Ey3C1gb&i+rx+x9}e zRORY@eJUC!@^6(q`DNBG50$U1E3ohN;nvyhqh91ab}wRxjMV@M)~!()6E>o7^kq4#30Cf1SFzMtC zmwx2US(7c9^SDUQ-vlhpk!+;yZfNvKOAyx86Dz`WGSKps@A1yg@ohLJFstY~n$;l27jvZ0(hH zH?|nCv_$+%c=}*`m=1i^15Pmuj7Riz8n=$Ua=iY(AxMaGwYE}9i~H^LwL?;lI0 zhvwtDU!@eN;F@>nVj=@R6(xWB)aOi?`Crn3|)E9sW0uPLhxxHTG)WxPMwrb{bF1R?FLU zW}K!XeSjJC713X;kEAY@@_Z1~7pv==iMTs^#qg!JHoutOaCUw+SoMnGO;RrKEzln( z)FfS}_}vXHhA*+$cf_>a`iRDP=`B-vkLc<3Pktkg$#U3bKtSiw_|Nc#zhop-GHr{_ z;d|ru2DO!uj=K@c$ORBP_CXE2g;KYdW0646f-}I z^y7^)=4d1lDZ5)8y&zd&F9r+@wkHn;mB7a;kQeB6WZ{=DF;e@9t#8cSuhx3-o>yjn`Vo?0Un!1`oYMhBMm>uCovv%ODi}_ ze$;wX0k19vDgj+I6II^ao>Y?}g{JnW3fm5nH!5C384+hU1c=CW9kxE>7=&Lhzrt{I z(u9ysk?9svKA|}N&DrUSAae1NZ??mi-{6q#elXJ29Xsi&!9-`M8#6`(`Z_c)Hn~qp zI!HpAr|d!6mDQe#YbQfnI7HqsIqh`PhPNvicEq-+l{|1oH+dfnYG&{d=kz!prgUs= z!23@^+H44y>$_q5wS7>&4Z=@+rxaIHTjRg3UYv*|inF0d6n|60o^KY3B2D)QLFSOb ztr8fWfWT*i!LA4iu9I10qlP4NzvXbyRycr-XUg z5Rtt{a!EqVsY%~+>=nh~)VbeDGs~&t6b@5|-PZU?VL_6M55xxccPEp&UrMC00seU|%C zTmBF}J)`(<*?gHs_)ocw^zKEVL=X@I00aOtRAeOp z0AFNY)pdbNJ2}zgPBZt+?AvyLNC^%Rk`Z>g%Hra=7YdRE$f0+Y996g2zW^dM14SeN zU|;~Y4SQH9WRmjuwwcF!(iPh~ZoI(e=WhB8M4FCW$ z13)tc0PaxR-ELd0?OT$y-3o%;lDDg5Z711wA4I8FoF*nKg-VE^#4{BHCISSAKmz>0 zv(;Xjh{1_pMT3L`Qh)&h{73m0;5OJ@$6dSCfgg6`BjAOue5|`?z}I1-Veu~GG5z)V zR@t4q#c$rUd%L%QkJ#H5mXTK1H&xt;vaShx`PIV{OxRNbfJKVng_e<5+ z_vMkicP~=A`c{wpJ>XrqVKUF4M8%D=avqc-R2CYpZ~|h7av2~!PTNP*cfI7Bh{(yz z_~ea=sr|o@BmYgidh7c@wx3H#vOT|5cL(-cdo%9)Q`;1)&dI-ZIp8b^t=FF5SUim9fpZlyOUMSlrECuA;zdvLT z1I&nqOn9Q?%ep6vu?=(CRxGy+epzE<{M63CIZPu!ktxM+V1Q+3qLs$eVYUvY+3^XH z#g4f@WEQ=XLs)JY{IkZ%_{EEv@i~?0M#F(@XD&QKHiSY`E!JT)+djcaJFnOdjm!oe zbMO7kqHiX0o8ad)#>Srr!UFB$97VAhUNbW&+fjrj-OOkz{K&riDO}UF3+~*lE!?A= z#y@`Tk0_S!?~ofITj{#%j*cJq(r=0roy7xBez?V8cdy>=0!91dzYY1>S3I{2epzFT z`H>*M*qj0Axlo3A4R-ZBA+Au+;;V}04>bFB!Lhs{{JDNZ;06r0JxS>8Tu|-&uyM%< zJveQ%#R&6GXDE>^W@*APo6(>qln!UI2X+Y1L4oUFnl7le{QkWjAaq(M)4`I#>St3 z**FjKXVoZD2Gc}C)JROIEI*W6f88VBQtu}jO_xpVtsNK^(^B9 zcT7_B)rnFuL@mV4GD>#lkYb$q*}J(MsW@P#r;@Y1lP@ktAWw~mN<-0>t%7MXq?cV7 zl(B8IEAgEDHcwo!^|Cg*18&`w8}vPqCUnW;*OQ+$O91lYiN5)+OlG4co1f7bgMZdr z8ylo@=_)LS4D$fB2#?_e$2F5Wia#;Wurwu7!De8U`nKY0a@gy=^sn*69g$JB^ufHn zk6!hv{~P6c#hezq<`|WXUfE)p`6whV4b7W2(kP~wqB)i_dWXrNGbjzXvU6u(nhvVa z3xea@WRyA=m{xCyrIJx42{SB429-S2Ad%5nipq5`O^#1+ zgmY~8B{$@53th9c(zPX9j50sqS4*kQbIMVcw=0Qq&Zo(E1kj3J^pqyYCnn1T`_^{1 zVi%NOiC)b4KSCw-hhkUA#w2|vznhvNB2_@ZQlA0e5&rr-oik4H&1`jdVi?PNUcybr+xP%T3kWT%C!i#p~(__1LEdZiTrAn3}T z{=_!vU^Ha~qE6_V*0f+f1;C5oxn5HIn+{An2k8`FQ{$#vNIocD>i0H*d4#=Lu@x>r zN{{36->X#KZMFUWfR6AbFR8#9LKb7k&&4F`M8{Ix`G#f2rtZp*S!C5+jeqX$)_-^y z3g5%MUF#RtxBn>Og^-halUSTa@ge_{A2ak<;P1$G>NCU zBN9#LQ%bCH7?N{%D3!bX5c4d^#|8tBeOayBoppc0cW2;Ub<^U3f$qqBg)h5G_ruc$ z)RG-o#ETv#WP@f`2JYWLKaD*umzVQhqUWgAtR<;-gOC;P!01wZ|k>6Lfmd&sjCTbtYYndhR_ z4m*B(Ye<_>w9%_EM6iFEc z73CT{7UDj(x$W$&ARL4f00ecSx7J#tS=hQfmNfYfY^qsP$h@w#r5w4Rvr22&K`ACw zAVQ=a1b;RIua7p1BOvkEdciYQ#iv~Ct5uH?1zWnIL_~T-C@JougVx^O#scO9SPqXe z)epp-RiQmr8`oB&_S6J6==DPp;n-OZeW8urrd8#$wxG8S(69ggoASOA{#FQ#0ksX z1m`J(Zq25v25}I4S~l!3b+P~qJ2b*kWIBt+qqCj2J}XY5L6+Um+Hnorw(VHg<=QJJSm zqwFhoZeG3_svI!EW1GQx6g6vJ18R^Ahf~#6N*kt(SfKHj*ycf`&F;fZ zPpQY-DT(te?N)bmapy>5$rT|#*9py3_meV^gRbXx3mWdDRc3WE+h*GXk$Tsc!C1+#rVq>T3oTfZC?J1G|_*Lr|d?23QKER;;_F)&( zDY`0nB0c?HbJJH@5N2xVAWGlr%hP9N+BrR_7w}4%_4}&DFZbn_Y4(~9KGyrx zo&}H0Fc#vem|P_a0d7v%bX7n|o}%FojR35-)zP8?6U8+4Z*_28gO9_TmM;UJAD_6! zCT}Qkutqb_2fNn#?sqev{((*`$}}T3U&Cn)5ZqMKIG_T|cU%D&CW)%|8C1h0o?@0nxrt0c0daI~3=kaKY> zaoo3C+F9CeQq7fZL8Q<54{cG^eg=c>_Evu2&-5Gd)A8@`F1{k+V2y3Zroj8jk29Zs z7hIBl^_)74%~gK!;<_d%+|k6ymKD%I$2^x2&N%I%H$|(?7N;$mV+TWykk@jnou78! zn1hoMfB(C(M16KG=NqdFNQL*CobAi@(x?j&L0_fhjh%A%Od_lJ4fHK*3Zo;ItALdm#8UiI%dH^z-AU=X?b+|Qh$F5*wIxD zYDwEF=JNdy+`u}Nq0_BlwIl;lWot{+!?0Abv-Yx&Ls@N}lbx{EYK+QVs=A-pcdnB@ zaNOg1u(={<+qrFQ+MK9&2`n+`L2<|Nys$M4+FkpBb;30F>1bkMDXHt%%0x6LaHb_{ zTF}UjfzPS;)h&59dA;Yiq|zs|uqv=~os`tt3X4T?Wjbd=##`5mn+#fBg`1#(AC1UG z_{@mk7!}Ma?Uksx|Fx-~j;?_RX2)5mUa|zDFnTD)Dlh=*icrcZ`5L4- z@b}x|)I#%c0$iJFk%sf?w)6c?EYk8@hZK{$R1CoLBQEf^wvOEb)}&_MmEsTu<)usASaAVAJhP=YoVC9xwp96xG?sO6O?2aA31TN zrZ~8nfhpJQ%iHhf-QHfu>A3s(PYk|)7{N|XJE#&>gnHhZ_4Xsc)5yz6&otN3=08_I z0Q(=EyTq+pSk&%cOwG9it}5EzTp$3C27`9tgE^Ks^0iy=r1Q*jZIUgb7XQ8noVE{H z?ls>RND}ngXYb`O?bOY*Uxs>zdP*d?QAKgm|fWizqd|r21VZHr4}@ zO%6UMu3u;MJMEEqd#u5Ry>cEvA*XzoW|x}2wM=7Gp-S~Mf5P3fz+c}qLy{>s=sqGs z(hfY-dL@q3OKXv}Py?ANT(4YTd}Wn7&6z}pRDZcY`yL6 zcW-02ZMe0bfF&y0{Yuqd9yS73oxH&6;I9THSo@4`|Ia)a>U{sKGR?Yl!vFTL2J5Cp zWd-;mr}%yJ3FsayL3pAQq6Y`@?s&(l=lF4@bjCTe(Md0~TULUO*p_mz|G=y%MFMODwoMU@tJF?*TzZ5yVU zTc`wsm=^=&{XxwScK}{?{09*lGL@1WL?lsIIGTXvQiX}KdRC(_26+1c7gR7iOn>e4 zm2jZKs7&%LLOCy3gG5@Q03DObG6>qcKaE-wg-+7u%i~p06Y$_^McbQi7p5`?VxZoRFQ`1Y4f00nM!U{$({AL|!Z3pnsLrrkxlvy}k- z0pX!;0jI(o^HKc5+Ykvj*I|t*wX-(uy*2H5IPERm_c9aT-^dz44?C@QNGmkXGpKYz z2JlKV8^Dq}oP{(USE8Iz*8;G~db_f1nmV6@|GZ7(a0t>a`{e&4&1>M|Rh?Oizg}y> zU8Et*Qi|~`;vqyAjAo7^3=Ef{=K`k^uWs6}Iqs}m+&ckC+NMXOKRy8nbMy4=L@QAa z$eQ=sIa*v=()9++aW54F>TGOJM*i#TqvrL2IFLcL1uHBSREL2Ccp50wQ~lM@J?RoSkaf@sgFgwE!9?Hk&sp{Sd7?dJ_dtZb|h|`VwR;yYWZ`Fcki70OMm*T z@nY*e7M%m7#-<_G3gtdE!vhN;!N-!)h?1KAwx@<_`>zW}unzldhuP?~YPd?YV0fqR$78ZGMtA#W8ySJ9+4Pqn^ljz2+ zfGGue-cbPcfH*469P>H@&b=}-WSFraqY3TKMtPN@oRTC?*}I{r<|BXK9lB**+`Di) z8=FfgOUYY_6r6meJ??ouukW#Au)Qt1Zh74PqQv?Em{1YC(TR34jqcWThD3}UIZApU zk(v&3mS%p)aIN9`2j(r$99RU*LW;_sh7(!<&bQd+9Z=Ebm7QYJhP5r7#aRnT6yldd zn~L}W5_5o1OcZgU1;fnYwT+d9NrEUljT?FbdEdwina2n*1Znd!u6< zC;u4m;j&3*$F(_e{O3O?dm*2eGpPJ4ol1oM`t!eB&3#eXa^{14r=6AtW~_U}4P}oy zQ`G@`6+-x^VbK|d|NiR$Q&cQ~!_byFe!P-`BVltij7>2G5N;(3tqlptbWpa@`XGgo z0^&6JFcJ`i;W1#5Ku$^^#XD#+%E-T@)s!`m7C_cm+_uH}x%zO!&;cNikrUc{Sj>pa zQ_@il6#s)lHAw?uhx1555Yp1ZEjGf3(g?AB0WD1#^zHOSBs^C0f!evI7DCDa!|~wf z4kQ(8O8O}hp?CpN{gD3$uxawVZ;FrZ@>xXTl_`l40N@hul*&n+rXobijO20Q6%{pg zD6lX?m51DlkRhUO{oyuw=eN-~Tw3n~lmut*^D&IDj| zjE+IffDkZ+<>t)wG67U6z0~<7+o`~>zoI>;I%1NHV=x12o>o*y$%!)2X9NlaVD!2N zNuJ)lJ*1wK;8dcNZs70HMhUrGX@E96p|B8#l#!KV&V>&}9lt;3ZLIDotr!Ms*IL^9 z>Eo7&>)Cx!+QSIS3_E)U#)c&zxLfgB2d5F9a0n9xbWmzw6y=4Uv73M05<7J?hdBTg z9tZazPar$HWZ47dA>9lexpesQ0$zlrE~RyaWfJ zl>0a-XJBr1TI(dFDhkErInz;YqU$<}_-X4?7xH724kov|(#07AP8M}M{MDr)^=x431)irRkhme&{%V zs(tB{s(1DR*|%XMkP12zlhcH!6ch=)&~YT**iJAy^A>b7SB))}m0cSP^5rRV3ZQ53gMztnZTt|y(k@Z>`!F~pLZ~kY8B*C05a4vH4Q>*tAvAE7zw0Q zl=LFcYCEByCa4lfn3QH)4MGk`0L~hOVmJxKRBatKdA$`}C%U%Xv$kz6-~V-UmyxUN z>v!<)+m>;5q6D42^Iyu+dp(Q5_Tp5c({mhqpp;v*Wh<#7MzXo7VM{0y8p9c8c%Y!d z|7b4FXGQp;^}-e>or@{XI0zG*83Yhupn^D+;x*@UwEwX$)bSUcd_x;T>c0H-g}g0a z3N9rFXLChf65IiS1S>>Y7PwNO!V!sqm?7Q+meudljXGCa_CkB*A+%lL~u$8euA+y zlcj55oge>;;LDD8dzOD`H@wWO!(#g@1U4+I`+j>Tu9)U6_;u4gY!x@+y$TbvZMJOY ziDcLOMDZCV-${8i^4yf^54kV2INeoT<>^z!z8A@9s>yBtxl8>CPIZr9S27X$s&f=3Qp!=&%qYgW;Ad9!hXwm zW2`u5$)7x179Cm9e;sRN`)rpZlk~~6$t&0W=Y!a5O%Dq*K|E#7@4vRNk?;$BD4pTq zjt$>LdCNPdcotf@a%zHOLV+C)V${&c&Xc}C{iB5tLWj<8jEvM*I^**1g;KnZvBfim zc(6D~!gGQR91Z{;-aEg$mZM&|Bhh7%1F2ZSRn;Ttt*~4nuoYjS-v6wDD5BKp42Rxavi>Tp(Jp; zL&}46$CzY@CIO3J%tIwpGC@<^NpD8?)3~Xj9J{{5?Jg}Iu>v=Ao9)TnzPY!YKEJCn zGu(d>`X58CCs6&b$?iEiZ9g~JrL~4(r_q$58J%o1d{8=;G(uQ83V>F$gJc$vec1Q& zGjDKPNWb%cMCkMPK=R!0$NyM@VNSEA9%ES#l3z9EAw@I{Dh&fv!9b-#S%@-1%B#c9 z&T<~!7WuR9{m@1-HhNohSXJ&-@R{%H%IC0tm}O6t+(rL~LQr7}NJbihg-Buzq)J9! zj)W5nP=|;M#U59KnO`|CmJiI=`979fjwC;7i`<+;fX{QpbqfGc!p6>|sYyzBE3HXa zpRFc3hfroM%pX=i7=!9#DoqFh?r0ywi{0*38@I!Xq9^80hyu7pF8zH zU|zCgS5xmZye0kE$|D5Gm6G@CBYhlEilmWNDu4!cA1UTF&v}F~;uV!YPqzGM)!N7T z8xy{As?Jkuq}cC)ej5*m7i&mtu4rs)8G3_^;N7QA7JklyzvwUA;Vmw2XNg(4PAgwiW;N!JG6<_g_C$ij6G#ks`!^}NuKzaZ7U9ZKr` z;Ut`_w*uI3Ay+P&;vnprZDS%<{l{I1;Sd61G;40V4C3-RzZGoc3ZiZPq3A4)|FrwJ z(m)Mc1(5^1A9)yHNbFccPdlrJLV4Nr{qJ5Qe)ns0!N2!-PxQfse70j4#BM4lcV?O( zv>E*kk73WJhO(SSAeLwmBm!&3WxVn}&)e`_c_8P?z30AW8YX?8>Rz}2>bytan>_M# zznL0MgCh=Rv_PiUb)^>I569gAKJf^NoBd&6R5Ei&dG%o`#;Zx=js4rv35l`^K$A?K zE6FWe+5?bTllG4vn&PX%hw7<97NbMl(f(A)R++?hxgv@ z{>03=!Ac`G3LN2!c@l!|r)2l+xbn7J+j@~b=Rp4eFe3v+GXMYv1{_zNdN#w0E%V+;QqkB_q}^}-Pd-_R^HvV z?#u?e(z>>G?e*Hrk{a77|163qLM0Fw83>`Y*ktdrLpto)wjd=C5(%LQFc1QM1c4!M zL_flS04N{={lX6)APBp&577Pz=9CaR;`bBd{#rTLYO`W9=O*<9fx7dBH_tHz;L9hb zB{Ye86fP$jptAL?XW^F51ikgRx0jiHqKI$r75jF_BlC8`v=sWY&Q^yqKFjd>LM#c1 z%ZJmhRWvq-y9KUUL$w05)eY=zJ!>B{>MIv~7H{vm?Iyrqf8BIs{0lpC*7W_m_B*fM z#@Fj)MenafTNNBC-`x-)8kL2n&i>YuJu5nA!iqaT_gGc=<-yB(cbjvanxL;Wzwp`| z9`p6R%CO?d+T+!=O$=TW>aMg`-7TD=NB|$l7SwE~bl+B?d{eo--RcU|DV%@u+L(6Mg$`o z-^q5>T(-gfUG|T|eZ+P0ZR}m)+EdgRuL=L|Tc<<+iUKBFDXFD#snVn)>7p6IjJ)X_ z6$$=s2sYAN$L@~yIsZ>2V`8mNj^(j7?uP}5Rhe;Z9UxNWdgvmGrl`t>n(G1-!YEvo z-O+#~?K2}5nCEQ3blT!ajH8DO$O zv(o7iricAA?x^j3HDbLSvJhQlnpbiBcCHtXH7}ekh9m1m-Xe0xl0JqIK%gtwU?fC| zD$r_tEW{;nC~@htv`(aU1*L``kzuoC^let{ksk)XZ+5=3hsuPZaxWJ}tZeJgM z7MRp7k7yhHxpqu$@bOBvf7(#Z0sRtcb&ed{Ju)EIQ3+81^Md*B%xw?x@56tbYG_j_ z>U8P4bgD{CT0yLAcU}*Dya9J8Ssbs(1SSs&Oz{|bs5O8XfRR`UJaLtTLe@l=EsGYa zvj3TPogm?~7+-?{RobtFFTiD83h(1Pk#seF2qybcyqf>C@!j&&+>T3aH8|N0APLnA z%&TV4I@emh(4cDoXh4_08iJsx%4DJt1~5eIJkkh*D~xtJLXob479`b~BFZjU?eQ(o zMOEHE{{T21%irFDZJ9BAZGD?I1xi>?bUh%cO1OwP!QCCLxy|mxW5MD17Zrd3M40NC zc&-|c6e&ZAXorSWtfCywMb$$zTU%C*?^2bBYhdzHIWPrOZ{_0z0&`7?$iH8o|LZ>K z@8P;z=o{^Ux1zh2bF?vs>3YShR8>0KE?ujhnr?rokmpHY+<`6&)K$Vn#xnIpNz#gS zLAOn*PgN^K_}IE$RFX=a8i8TCpl#2 zlrawha#6CR6pKXlyRFLXp{}avZ=6(efXO5S-peuOVhYyV6Q|v{lbR|g?%I{1g`EFw z2$P}L?c3Sx;^>m7HFT!tC=zmKp7En45bwoQq+UoOGmrXwWDG%%n_s(Hq_ZT8)h`~36J?vt+yY9^8B){L^Q z;Oml?VwH?eI9sl9&t5JswG%OL5iqyctT@oMg|S2h1puE;2@5imJOr~~tF16Sae<*W zH~t-u7BuuX{LVAgEmk};8JUbm(1CTF7*_r(ROM+qVxYL9u8FJy|xs}GvK}%KSfzM9_d_g zNH#LJ`JT)*>3Ir>q2RD4B2x{C7d0YZOH<0z$_Gm08PgWq%UYB|u)%`OZ*c9vX89@e zBtBm_Kq<5Y*XSD4aid80x3Z8GpD$?k%JvfHA#yljrN%~y{fCA z7Jnr`1XM~93+E?NjbG?x`D+{P(R^mwYkC+}yj%BGo0`7t@lP2y=dCZ*+a>Vq7hQy$ zGLl<|TaZM5FO%c;R~zEor+LD}W*l<;OD2&Kc0}AxVP)>3fs6~p=lb@liPvc9PLH=2 zZgL-UM)#=8u7bYhoS^Ugb#+RZrEB?{C(qjIEXsaDJ?%FkX1ZaE&N!1CcJjBUxng+~ zoOLVvA_5`uWLn}U^e1sQ7UcGj-YP)Wr$dwc$pW#Y43J$cC#de_L?noq=2Xo6@(n;^ ztfTgF#>P-S_96WKfpfGXaPR1P_w~`bNgX?;&iI5zAT+u;9VJIn0?@FiMUNyS=ZP4V zvilSYKVp#w#oTvC^A16z<_? z{IOo1JRrUnOxfI?$eeR#cew^j`KLuD`#X0;|AzHF_eUMk%!w(zzzV*|0D~x@8j%gi zp-f1RE=i9Ru@0kVwwpB5v*^ta70oeN2--YNRr{*p;>f5+h}6ov^Pk9<0J1vO_fBs7 zoqILjr|OVpjuH!!kspgLFVzQWLTgubc5nHgM9kajKzg5-kac}fQRV6Arw-DkY+*{R zb<$|ALrvwkdVe*2L#}~61Ntuy+zHihbK*Y#vZ+L(TBy^zS>V&%(Gp?$Bv(GwUeqBe zyLs%k&-|J%p9VA5EctHh@?A(zDI--7l3SsuK~(Ta$j`;Q64NqZuPUpO=c@B^ToBvl zcV^>B>~F2e@xk5(g**TjoX5n*csq7 zM0~W!JSBI70Y$7Kc^ls=yD81{5?)tYzHvcrEY3gy&}8#wI1evI?XY01mJaL--&B6i zmu8n!Prko7;3^v=($;T&+qJyv6BnNLkU1wjFpG#?qMjbBW{M+4P}7x zN1|n@0F{#R0&!{>Bog7f%v^K3eM5Jav0ni_rJOjNnW|@ zD~w0R17uowi~k?sYJf|VRYx`arxeI`jp~Lznx5>81?9cl6q0&&s!F{>y64fV?bCng zZu*nI+814F&n1E4q*_rts%q79^WEKn_x}(y#x~r`7vKheb?Rr z=DLJld`Apg+Ni3JsM`7XDyXlyBk!T5G{7JK$?GRr&Ch8QwCV~}MaIc+<^7)qK|yrt z4%Xdx0$i!7!hK@fD7OdGk5BIQq;I5Zf#Z4M4)2iN_b=bLrRd($BmqL!vZ9Z^rS>{M zY2$3U7umTIaynL zcDcEnemZc~Ti?c&9LR-Y2Y%NsG(G-@``mc4P-B-vsN59#bgwVnzgg%)ha2R(>@E?* z3-w#vWtsSdG{jDeFC+n?A2=);=O>7^;M$T_jeTC5_ZZY-qumCom~-5tX`-kR2{Z7h zi#?d;q*dBMAaeuFu)mh#pLdGU#3t00uL@fP_~pJx+BzEUM>auXWWQOsGaqMM#0T$# z)*%HKort*K0dO8%jDP*zd}f%d=`xSkyS~Mn(cPOw9c^xmhdv9pmxA}V4h{FhXLu)) zF(X)LWH>Hp-ee^ zP&M^D@hxmMo01f3^77?8Hk##qxes`tQ-hn^+41zU3O-8az`ZM6`&j{k0uTVY&Qc|^ zQ6XqhDTwtHW5j?UBvD|4NbFx-Z2?t>!(}BeQ&F>cd1LJY+3NneiT!CKaIdsaa*qzL zQf8j0dlOm4)tSHfwoFU2o4(w=kmTVYkbyDYs{~^zvMfa6D5~U<2qI)GfT(@T zjrm+0;jtDqEyqej4en}eZWlvfC*|)9gd^;HsA>J^nJP2Yb|pS)Jg93Ui2oh)&j;m> zaC9+@sDODWR75>c0z_0*F%q#RrZQ4AF1Mc^9-ec%zYO>h^#*75rA^-N7k3GjAI;*8 z<4New{#wOmt(o3%&yA80g`82zwUt7-Wyhk3#GlZ6 zs3V*XGCo*H2(*-ygFBG0?UubPM+MF zgSf?JFntRwzyd6=m|HdleZ}~&RS}d`u?Hnltqlrm6*3?)6a}SNbraHv`>kc2b zK1WnE&y)1^8Z~xr#M_izv6msOVvt2Vq(D+AQ$ckBp+sX$slt@#SV3is>5BX}y^NIo zv6FecNtP-Alwv`H!b5;HBw%1@B_dJOFI!YZT#`zq@Q_jl#!`mx9sm%L1W{9yRa>JX zrVLf3oK?9PdWqJ zD@!$W{fE_Ai&TrhKVXV23+Q3Oy}iD@E7CcmJGQ}Hisz7wG(ggLhM@EFEia3!#Q$E~ zm?O%?!;9g@@C;?(NgCVQVv#U(zqsSPvI~xNTOnb=og1t@%=__n;~i}Ojl0&-E82bV zWwtu~kJ#XzSVjKcqF58Jjg8}Z#O=A}(l0h`=I&?2=oo!}5(E7AH9_$9Bl%_#YU6Ge zm2`8rL;cN(kdu;I{YHOfSQ((9hT3C~js5YU=d9NzugN8~)}0W7P-prM;NQEwA9>@$ z@__uWmV(9q7eyK>5ha3e$C0^mUpBxe_pFK1_07N^Kb36E0MUw!m`E&0k`y>yA&6l` zh^{g~nJjUv0?E$?;!Ibh!zFuw`PWPme(|cct|_dt0zwRHB&ZlMMPQ*N8nFy&DkPxE z=aHGUrX*9{_pZ6kZ)N@+w8mSB3X}pOEC}j?Rivsyu`pyNNz+es@JPlEKBN1@6qyLa zSQ5(xX!t4xH=(wpq?{LGSjLk3#y2qV#EoEbp0sZTkXcbom$Cx zw81;GhI9ejQWS93?)HN{9khfc`rOm#Jd-2dOn|`ZI;07|V~@Uf+{wAT85H9*HOml3 z@|ol>$2yA?>mnTwV_R6t1s3#+UXR*R>f^`uu!{>Uzyd6={c2m%E`3)DhY&WLm9$nG zE&f~R8^7-K3bX;5x*8C*Rxt!s5|2D1CaJZ!5w*rfPo`s6v@bR|9|1&3NX3^gQhDuX zqr6BN8tcl;GxSsq852kr7W7sqs1g#ZA&n2iOBunFjNsIxfmb8J?q#~{CoiLGL{cF0 z_!X7eF9eX`JVcO*tZ9RSpGkNx1gWv6lzp*_-mG@*;+GOT!Q1|2qrK>NE$iyMKd4NO zIc{9p48o0=Qo^q)-{;cKyZ>GCJG}!=`!AlV0b%Q2J!h=_w|$2_b4n-w!5&&NJrTE$ zOK+JSu#J_y_GVwopqm+6*YGl3>ugD?^a55|X#OAw5D)_Z001*X zG&BGJZ)Fga?K8#{3j$FH*1NXelGsULWg{a%WJ-i_2y>&sb@x$|Zj$ciaq0)0_up{* z0AOSWY-j-B4+@(`4qr?*I5xbyk4xza3p}eXZ%B|^94R005L8eF92rHz8N0Js zHOM9qgh27Ekq8g<0{`e%@W-vGlcPudk0lpf3s=2>l|KBkThB0exNu={z9c`1d-yaoX^D z;1-jw@=F$(*&~fZsw=I-Z%-wITyPxiW(N>AE-L-!UVwP#P0sU@nmo>xv z@qzlk_*$16R&on)!Qjr`4gdLw@c0ZQ_h9Wn(zxq{(;JyUwGZ>^)PA}d6}RJOuV&i0 zH9h%v#PrKBANctrUDDLe(frDGU+27DQN%ytueey*#@Fj*NYQtU%#mHIK6mEENOK$d zT^HYYO=P>m_)&ks1qfy~ymKm7>y>~CK!w7lChViK1w85#RV?<-t{m>Jy94Oy2o zPsf*w?~d2KPLPJ(R{yR^WLtStf3;}?kBSZEbdUpApLte=``o|U=ObWmhISyGf15mJ zzJpeh$8D==H|dlI*)7e@F5CLja%A0ij=v=Em%WJAcauC+?|N^mm&m@%*+K4h?eUX$ z8jn1)fxLaX&g;>Xc8mJ$cN;P124aI3@<1g+u;7>fuYIYLtKC?Auut^6ifla*$inev z-TaY+*2N3NKZLi#F{Hk4`9sKe0DY+P?u_p0*#uRg|Lxj4W53?Z>y@47K_&&s zJl$NxPa94qY}-e?iqW;}R|g+&zk6%1$gCKHEFU!gIG^}xIFm=p8CHB+p~aB&t@*^? zc-Ot_aHxIW-D%-lzc0#n`s;BWoFiOFZN+=*eDY7|KKE-Qt;6<~Y{y;FK|U-Uc7(2A zsNog({@r$4d4uEq^Sn~IPj4a+2fX8}w=Kv5Viy+7Qa`b59$L67D^gOkHdKoxF+LknYuyWhd9|tUF_I ztMZAv8uu^Gjr_^JmkcA;^?zgJ4!xrW-lr;7JRW=JeP1QpDaKFul5vCl8M)3w0AU=P zY2qJmN&N#ht4#?Rb+>W5w~4THQw7q-v`QtVvZs|^cB`j&iV7)ADvp7Pg``KT->(G% zuL2xQeILoJllg(~!`XS05kQ@xFY)eExM%Ug-Qi1i{>$HW3)#$mDfiR875I9--P!cs zqt{?IQoPRpA7V34_pAQm5i&28JKRPgUD^Jn#0~jRd|#jU&%&>I8Q&-ET)iKRV>vhY zHs3s-joH?469P>t$smi2x)D~vgoj81nqqZ%PLt7M>vvslUW?kAEMc0IJlIz&?(H?s zAzM8ohAB~#q>id9ikYnlM3fB@EfTQ1FF(ai6T3D-4B$LQ%7z*+STHq8yT)8&!wVw= zRP~}92No}gcFZ$;1RN+?2+$_X2v*w|3Nti~GFKB*u`IJNtO)@X#wIbJ4qu+x=41C- zx%F&sE_hwX+o$)~)jVVhz@%9@YD{HT!ZJnWQc2s&0R&9VHpc5oof~2zFYn9bSt|+_(nD-G)UZU`O-yK zLXM@RQs{T@IkyC>$=Jw|T60k2W2IPZ7?7XSh=q5f-vr95bqoGAkeo0Ou*f#Ka^tg5 znD~r~W(r%%EHXKj5f_*xh%#*EvV}%2JKZE9%=^aVCA&f{gO&pbBfM_jKj#5+QQUi0P?sQY z9&T&*@0UrrwoYp~j&DMR36wl2iz*qEncgf+V%eKvC2h)EWaH2X=aDx~TbcDcIVna+ zXv)l{6mLu>N~uC=Y2sw~Pq~EA#gA^%Gor9a46`;5j!cwi0*(HpDk-Xnfh^^UvUDb> zBOH`((EtrIXzt<+|Kv-eLZj9|(&%T7nCCSshGv{z^3_wNgcHqWN^Vx>VJs0rDUhW-2hPNI~eb_jVBm;kDZ7DiA?e3dpaC##Z!Rqa!ebIPWiro$;Tu}Xpqi=;Qp z67b-W!_-U>34TKrW!8+$^QYLhVM@SMiM(9%j+qV9(L?^@ZqZhR5Ut`FpX#o40d(e_ zz|V(@dSjQ8OCgX!FPxMNI_uQtDs%@I%-@_8(X$r6+&c>={p|~1O1as!?X*ju!#W86 z?h!egy_-qUI8zPjJt@iJ8mfI|@P+tO~o>aT7gUx#03{bTy4Bny%> ztnVbRQ`X7STzZfMc5ffDIg|Q4&21YL!5>lde#^UFqO`v>&pg~T)UfoZ?%mja#6NYP z_TTmrJM^$26;bMHK{cb-E=rH<0Q65r;i!2a$vMUq$jf-j4t3t^jAJ_XP@S!#hbbRaD-|;7oVwrCDhX$YK zY#vAXhcv9<4hs=g9ezIMjp8rz9Tmn`-+@$B8x5~P(qpp0&Vq!$L+8lXpt{;=1`R=j z*Pwh3Y6bAW)YZP*By5NjL%^J*iAuPrM%2uzKn=<*rlm>%&vmTtpK=#xzG?n~rS+Ll z(g^v49_1XmT^1{Ps>kJp{HQ+ULUX(cKYW*SpT4@P6%m3>M5*%3Ym21{W*J7D${z&i zZP}^nHs)oWgrF;X*V(xemrfse~89cUB7c<014t(shyx>GY53 zD(AH@f_)mjC98&o%l>LESlaK5O>wwom$n6u$DvRCuN0ZFh3M%y_IvrvCjBudw>jSV zHfxzO^!I6^J$xY58q)?1T&mpP+h1?zVXuP*ph0NR$u;4g7h1NqdLqc2WGlrdqACSa zr~6mf;k`sF_)nVeXt%MTVeEU^{c}QY(uixGPwNgHMUAxzjWQN2$+7ZChigQknDeIK zV7E6e5q~NBFdpO~N_-^nTKjnXglIdZEyFAxF(+b&O- z=_s&;mPp$$RP214LPeO5Cl7( zM@j~mO2k6scF*f<^DB$INoQHCI(y{ueR9TDf?)}}RTVsfQYvMu-Fz(<@k}PhRV2A_ zJ{95$XK5Gs((=EKxuaomq2r~HEnJwlKB<*n47Mm~DL~Q^MK^!?HP~e~I>*aHB}(ve z^phq*HL47YZR*+Cxo7K7u*`t|@eh!^D zvMZ`c5HsrLe;6LR-BGjcbhg5%X&rf2Gwuhzdbee1LHPIse!&YMhm)v=k;1S3(h|xYmoS$cuTfRr2bgX7-9K95 zxkH4pprbZrHvd^w6=ju4A=qG;SfC~hfTBa&K_(L_@$Dfk+1yb=GtjI}0Lf$&VZ)Jx zlu0ttTVSaKMG!j>N)&OP@o7tZeMp-ArVoN6((Z}B}fJ#;v@(xuRDw3cv?ACQk2_tU@N>W{gcY3;C5DZ zd^a(_?P}pB0C${MG<#$iYW33Uq^Zu#m3ic*NL{`M`vVUPF!bunrIlZc4s_+;J3+4? z;VG18o(`K+xgb4JC(5spY^uyRjZi-t7Jn6qZbK}hcE#pA%Nnz$`8^JlX^0Z+RqvFi zHlnt%)@^SHeG)d0dkU8D8iD;M*4)O+REU}^K*1`BtDkkIZB}e|!P3Oz%sbadJiCXz((j6983OZ<326xvK3ZhMwh$JDy zmmrJ^nxrCYB0wpiC;EnSEdjGcI51d;0#Oxf9Gf=&tre=ldTT{R_)I=l-A|s+PQG_a1mndOWku#kGfc8nt1TcPeNjhpm zAtAa(AjP7H6-n)Z7DL30Fd{49ih;zp$9MrBB`uEKjuM)J3XMhaFjh&}nGslJomEMy zHj!8ph>MttJv9UXesiLhdU{pYbfTyg3&8iME8^ad!Ti%?5AS&jwf074EZ&q#_720s zaz2!5BcA@)mQUE~a{~K7U0r!}V}SpeO(2duyK3*GLK#rr?pKfZ5N#8)q2i(Gtvduf zp^rRo{L{{HG<#<@pXQ3uK2igs6L)4mc`l(LeR=n>T_Y5ZD3yvj2b783OrmWKp<}DSn8#)3*v51Ezi#(riSpYAiorQcw3+7*&z}G`B#(f- z(663G(-$EOaM=<&aOOa&VO2}+lnRiQ(~o{**25ag~9^){ko4MmVmF4BblSJELD7Sm+9I>@{bc8q6{g!iLL|)D47`)M3k%yFanr0tHd95 zU;3BL+QB2*bywtzik|^&q4Q8`nJh#`h%}0!O1i-hb1_vJv7)IXEoMtyEzhDbB8lQ{ zC2U}YFv+G-L9qrBiyj8y!R|6OF*&l9FG1W!Rj^4C1wkruSOiQ8j0jUIHUZjI*xOj# z0qj&c(Xv);Mn4M#E0mBCVkC71VSq##5z{DE&c+fV9?K?cXjsUi-j z(iIU^ws>h$(Nd#sx@&3*fQ&>kFj=3lW$0`qzbO(M8b$Gk%GoJN5|&H(q|9O!L@AyE z21jKks!alJC8p8Y^yf0XjBnwuHxE{Z^Ij`PVQ-(I@$_QsOe z<{tDxIs|~ zRj#EP8&E-`)}Vu%A695;8Z-tCPK9)1HMsX-x(3RKFp33LMTZk*m1Q=H%1rB{B4sCQ z(oeHE`YUOP_bNR!?r6=T=-~P>HdCF^?53Gn%Aa_Bng1>O={bdo|gP@^3*=UFGuB}L%&2} z{wKnZdU;6Y<|ILbhOefuLF3S%G-zlkbxJseeyLyytcc7cy1;;RmZ3=`6PC(Y!Xg$% zmKv?m%UG86Ii%GCB_*J|56PfZ!9c#djA%C}$x)I4BtdPOT1n^v7w&DClK{d?+B7c&fFJ>WB79SQMg14N6qjDw1D@RAYw3cn z_3_}q^>n#ChlpIW(mO@;ReuWcuH)GL^-foxQvQpbrl=0N`?0j%V%oTs2n3le?SNG|4EhAdl=8+b#*Z!3DCFZkxN-{fyjTKv-IP z+BZvRy;t&nN;Wp$%_`uFT%_e9D@wsj@>^j-Z9}ioQPmxWECcy7cHM4B(k49bF#8a+@4$ zo!oAP+K!DYqg`Lbn{CSJ@DJcUDBf?uQ}kUL_LF6pm$(FRMG+B3`&DV!8}*_Hg1Vm6)Tp;2lY~}!D?46B6HV^0@}M2!$g|>x+wrEL9PbMmTe+;}OlzxfyS)yypZLG_BH8TktM^n`vBy;v##sKC_L2uy zm4EN`zBkoI)22}h5605PYFO$kgKW618G0!l?TRt#6Zn?(x$Zv6A0Ei_OmJ=K-M3|F z`Q>R-Rv|U;b&!@$-qvAEv17v#6)N2Z0^H-cx-#F;*ftmepZO4P2iINvZ*0AnT5f#L zG-SG}%zQJyX?;HSMzP0YTY#kUtm%`z-&Z&Dthrw9ZOq-tdHX#A;LnQT4)*8arQO!u z<01O{swvTM`jgr?@&4_nZvp-odJ?F#HC1jsqQa|8ZmnhM2S!xe_PbAttM#`HEpHpKKn~Tf{Jwn@P9_^*{0Ep8 zbbf1Gl~Jewv`4IbUSd6RDCk=B%icf5YMxWKYgzXD#Z}05EvuSV!p5qwcxv_TR!8ez zYi;UEcK=V;wqu5*WbJcn-T{pFUu^B_A47s=)4I{TY;A7pC%pF=`+s=aTRS5eA~?a^ zPUH3J`hMPpiAg-{cRPczrQm5^0q*= zT^zNlr>~R*zDurtRi95+j~QYgDdxLyunOv~@Dq_+^uPJ_o*%3FAbrm}ct5rMgO62f zV3T=QJJxwrtgb(`czBl~KR-!)*US~FmVreS88=ppE4L&c@=&PTj63N(XZB0u2H3_s zSSMf|v3m#qWE~icwvMc$jpW?D;@WGmWNzlfOJWSNaz-KbF>zLEulm^W%HP3z*q=DE zkyuu$E^s~D-Q)OR4Tkia5xDaPoxmM?aFM?kzte>?_@lcOE3M}JHJc82IXha72)P0* zn=Vhchq0c%vd(@!98y_OXkOHQnKR0mf|RZ=ZMyNBK7(>G9X{x0MCK^#z{%mUSV))b z`k-Agpm!UV*ebBseyKM|e%oKWT6sEKHoN3;VH9&2LFBVO#<31dF`UzP3$k$>r6!5w zV&N{bKGI&seDD>>|AhY;bFfqDJBzZ!eC@Saqs4FK2d+SB+)I0cZk=_>#+x)iTDQ5( zDgb{yPV5f2Ya8d~X(h>OubM?BXWZ46BQ|V!0;_T2dpIqZfe(6k!ICjt7@~b!>~DB5 zVQI#}LMbzDlSz1jty%k0~f(J~zVgz{$|I$I9qnQO()lHZ~2gvD~m``)DRe?6Pu z*PUE5dcJh|-7}f^=vx&JQh&L_ao&h|=w9kJX3IMxVsZEw6G*<7J=GP1h2(T0cPm7&jb|1jPnvA1}1l}?qxyGy&i`Rsni~L zCmFYe2gkj7q4Ab&Yl3<8YAY|eyROKkc5*Yn8`yI;7aL$iv6Iw^_p!OUbT1K&O=XyDudK=DIhZ%FM z&I}`BfKi|zEen-X6IqyZr@HgEnM(_R$))^j+ux)C@&j!tmo8yD=E%>FYRcux@IjqD z&Hb8WKPW4XW*TS?H!!&pDknVfBL z%jlKJ1ntONXF$nuYDO-@n2E0htm;S{T)~IzPa{}*1i4D!qNP!Yi2*8?7Sl-z!FEd% zR#Eh%t#SKug=B5)l}O0Wq;soeqQ+FjB27ir`9llNH=I?KUjZtxBPUIM{(V5%YNCrD zT$8j@db<)dAyuHtnlQ|v=_FEKQs;UmK+>ZrVk>*iGQMqr`YQ;VTxo~x(l|3cd?Snp zAPlte3%x&Q){RfTl?js$mX@2pSY5+c_(32Z9M-94P^?J*nogez`(2n(MVs z$j%~k&xk}#s6^8;4E9Q*X~=~%F&wIx&4-)!plm)}#+p|eLndRPHjH#tfx(ukLIYO` zI*)@Tt4U=XT25_zo6j!s-7oqrh`p8d20d&tbY z-^)0(`)12)0&2Fk5SF>peKK|p%wU(s@Jw6d)@z_I*%U^26M9TS!t1xwbwT#ONLyPg z4`<{G)XC}sZilL;zBfW5u%_cLqmAl~xzcUWml5i*@!?uKfu(Y*&82rs!E*G9$qerPhn-TPtig13 zwh#WHRo&;_UMOYs6SV`)xap>PXxi;2^D{GZ8!okg!TbBp(ACtR0N;!(tp&);=-VlW#jf_TA}6!wYg2Km0dKG8!o*5CDG219r)@Wc69Zd;N*;cpBzwh<|66%LnMZB*Rd-`rztl{JRTS))m{cb8^n z)gH6MFgyWaDFI6NLQnO*L6(!s49`F=KtMo1W=U8!)}K_wwlbzQP|+LuaV-twaL^@* zikBT)(5B(8ZRoMK8GJ2v1*xVDbK!2l)rI?|62Gj2Ge=tg19nsTLQZA$w*!YF$f1)b zK=hQJp-EOEa02gbI$=gQw>9)@@g6Q)g7z1DU5V_{2Wb6c@gqWHvWlxrhw)_)m7bdG z&D6e&=`iwNWam0;lQE5|n50DVlD-3XOo%sd3(uf-{x%Nl$%(ab=+04c-h4sB)FV2= z8)p2Rcfgf7EYI^PXo4as&0(kHf%?7yjDek!dya-+&XsfMETnM!>0en3uw9UZ%wh@k zNE~Rrpy2p~3))JX=$pzA!4NyU%c@;|kjrh&*CWp=rJ=)w4%j|@H%c{5Y!K6AS1!gP z)f27+Bv`TJYLu2)lVko0BV#fP zjbwA*s$H7*80}HRnpQ$|nI6@J6E&15;_=`=kA%|6i#*s6q% zzty*v#&3<*jsHEzBXB2+wN5~a8PI?Tlz=|jW`Xp@IR+pgCm;yI#3b(o$?+Zf!+6?5 z!W8F`vZ&ajk}4c2Dj)!ecbb}zMre_9iqB&le0)~={o6hCJk|ruS=m}zmN>aG$~8J? zo(bWN4m9MA5{oc^Kw(G&u0PF;EmVJhA(_SX29ipU=^Vv?7LCQ*@Rx|OERHB^5qPv% z8ZWjT*Ov2_J|x1p(Lm4=y~@#?cZ8K4Sd=-0{xR@R>}aF851XR`C*H&tNCo3I444XO zxuOaqph7(7z8JY#)SR(xvv7J`s_GcHO3s>Usb5-k*1JN%guhO&R+GARr(h+$I77qIC8HDhR=XBrZ@h)l5(hsArHPLJ$aLl3J8Lh-;D~Fg;L#h$a?{0g%|gQi5orEaa-qHI(UP31?TQS4z9+zN%TY zu8*aLN{z^-EL{S$=+MzuT|zoNtJC8e@pF=;1~j7)vpc{rUU$mZfXs11uQ0Pbe}UJk z${?=E^>k6&p{nUx>pSbiPO$cGQA<8Hk%al5Axi3)R=|`@sHUioni^|-jWMy!&DTc@ z5i+*4C_203e?ZZ7Z_WRo+(GG5a00?p0(EP3XC6d=IDmkFfP9;kQg{$Qf;qdwM<{qs zH{G96Q9R$#Fz%e(%dwm9cOBKW)9z92kCi^S4pQ)48NiaU#tOrTXH~UFFma?h;1Mac zw*-~sP||VshUiXzeU}x7Vv?BwH8fZsVMl}F!X=`~+aM@yC}H84n6f~g$Yz=#hRvu_ zLA%o~RXG-Pn~|lffB@VGj+o!S9tFVoa#4SQdG;AVzHvYVm@q;hXdnRCBvVF3 zT@!#>f}AHx3QbYKza$eLRW8akS5RpYqfo<~1%{Cv0c7ucBvJ%-SUa>7n4V4Npu(kV z`U`FYWmEYlABJr<~iA6kEK6)43jHS>A?~WnTGW#nu4rj%9b~U zVBCemub{`Yc};Hg+Cb}FGvvbr@;A`gY06^ z>WiCky8Zi>O5?;lGn5DSpl!hR_yvTAx9nuSMpKUUu)6{2aP55P$1vxbYDXyO}^tj{FRFjWeSqAg;t+igo7jvGwBaU}UkiRpDPX%okkd?wS=e6uLK>zgc^ zw)I^yx3OCCXc^9SCmTEVE92MgKfVpXI+-vq+@+cPStC8uZK9*?l&SbkBzErnIi%jQ zH|=gx8}e=~Z`Z;PdVD5!)2oy&Z0cojHkq1}nq>-nbm(~u^l3!}RfbNy^0UdKzGL^~ z6U4hBz910#Og0896qhp112((v5kAm{tOvaASJ)AkSot{aF zac#0!O}F%pe%C&2W8l>PdFIaV$}|+tc2$vWFR0QAYLXeL3w|F z7f1gYj(=JcjdSlJ?7&zM5CQ-I07FDpBme+kWw6xrudwCJFs}&nykwjg#MM|bC5l_! z<%Q9Cn%wuvU9GOHyP}c)yQu#F49o!0i~v}G0Rb^-pJyD~R@6>OqVZ$DS#`m*O-i`B zI1b7pa9TqcC_9n>dwNfHgA4ZU4o*p6?O@M*DGIGgWtL&5HA*e6OqsSvYt%CGP`0TO z`MD;WR)Jh{i6lfVe=zzQD)VDn9qPeC~;=ySxQboU9Lt8yZO`NQ9*Yc~m4Cl9GgxFp*}G z!NQ8XHKka85Vul=gZiT3!Mz5zFWH-(e=?SjMA9#w{_GE`d{m}2{C-!Cuz0JZOIJNe(q`D9_vw)$s7lbz#-hW+Rc9o$K347L0|up*8#@ad|iGY9TO%61K^0F zL!_-9e|TG-8^v1YQSP?(E`73p)ry`3myLnlp33*BxTF2tJNBm_YqUw zW!GKS)H>FRGCPhrzgGvVx}zF@K3MK+>M3x$B{~^?zR~j=(RLA3qvg-77#?c2VDvTb zy2lOgedhiKjx6DvlkWlYV6G=cy=#4>47TMcKHWB1mYZx^PG(l9+w{pXZeKO7(`p7A zJ7Sw~QFD6VQu@JJVQS(?ey^LWu~wrdT0a`2&+6;lD5zstIC^1~!KhhQ>$ltRy7TbZ zy})m-sviK)J2>Bg%=vZcYZ#_dD~Dxb7>m3`#);T3h~K~cQ?K?xh_b~B!+TatH4W?`-_l2+EfR*6jmzE#rjtl%=nTzMmInUOU$HBfr+a z%Z)GioB35@E4+I+(L9UJj*?$2g&ecqGqxF4A-r8A{xJz-H zWzodKY}CM>ec{BP@dvp#2kel&nu+yYwW-ua8QJdSkKCrey;rnVsMk^tLY$SdaeSu0yhx)|I( zo=fKQ&oHUU{>fX)XZv57M!YGdh5L#|tCq%7WPz|1_m*zyN9yv z-EWT7SP@OSph2M56-iK znjxB$sx>)C4G_@Ec~uovE)u2P9MjLKt-~&4RL(i-A1`~mSb7&aCuPRg;pwjoXP|(# zpvvkB3JMAf3an_7B%-$#4}IfFEr8;pbLTIB%4`#G^Z|5;W|F=2m&jHK7!6~B6yF#Xqa z7ZvguAE20cj+uLr+;hVFR!0CtrW~HZ{=xrx%ZUG;B11M7$j!|x-~{S4K#8=)yW08 zhY}IPAd0|l(o1hOCW;zmd|PP1^U-PdFSd;~{0I*jHxv&*En^K&*2Z$ERFOup*KjOx zqK8jg%MGP->pda#Zk-l`4|Ibt;Ga*1C#ovXga?w&uI&>V=d;bh55fCN1NhZ+8 z_;Z8Gfvs%az<92e<#3a|NnL zQj_s)Ur{y9u2sJ#S=r5EaBv20-H>>aBM0A334V_c_AN;>PE76)9?ffj;tE~;KUUGV zdY<#u8CKVII)Bz|z)1h?_3Ay)1wdR=m~wZ$Ab@vmV}GP@0ory|DIXR6b51md!MaEO zm3rh`kaXGurl>u9FlJlPuVMTeo$FNjyLs`0N%yd@{hO9HsYvk-hFuRj^QUk5O=f|& z#z{YaHoxIi{qi+U**=0h%npL#54dTeyj8Y~@fmjS&sKjAVd70gS5ZiOr;cKQveafX z5IhShp>t^(tvAiy_u3cLkHrKAcjF9D&W?ToPf_jn`KzsUe;!uqcOHwOx}T~e+&*FN zRDE+cw?5kUiZpK59K}Pr-+kvEIuF9|a46@SO*l(&6MI21EY|~BvzovP3JMAf3gzHu z6UlyVidHY_xw$Hi!6>pc8WloN)DVByB7{tz*MkmkT4RtHKVS2sZ|T`QC2l6_&~DJQ zKRa00;Lw~VldF*hnhIHTiOFa)$_SL?ft@|U^lW+&J-@3EWyPN@T^AfD*o)0ge+l9h zmS0sV`>rnaq2TzE$?%V?qp0VRc8vLd$wr>uyC7MKaVgR|vY4!xh8)YGfltSg{uQSP z&O11zTr>jQ)CX(m*?9nf$Vn^$zngcL@y#<(+A{X>a+?~B~#n+s#EXXFR@$xA1`5a;Crv9JtJ^} zg6M+mk$2z)g#`r#1~LV5UW_rD@8a1Oc&VQjHUx1*4pL*NAqKO#c&6zP8iA9)s}NULCK_zl$OQRTv-sAB#BZ6oFsY( zR*3+^Ck^h~-O(wh9GFW_Cid<7pH7RiyZ$8u3et^X)l?9=P&v(fdQH7ou(q7G!OZG} zm~nYsbix~ZxLnYjFF3+Ly=BiD4t}3Om${#UT+E>CA#eGqnOwS5{DKi1{3d}YVR^H* zL3X|&8ga~@5bQ0?w8Du>h#U(4{}5bIP*70jQ0spSf^x!%%Pw-pm};skBG04rPz#9$ z2o0daCfnh3O5jJh)e8IUVD^I-`Q&&G_EdR>TA<7>&J?=JFBB6qjrqCv7g1d69Ctid zzo-nY7OwRgwkkVc0R;ux8LWD9o2-UVP*6}?Pc=8ivXAms=s3?NkQSrK&*Uqeg2%m%tCA1Pe7{4s2WnT5l_xRQRFgBO)gN!sx*yZ zs0(BXYUq^Fwgai|pOCkL5eTginW*Pbbjdbs++KBp5RRITY)g}IuD#{ujE}wqxXw@a z^;E~y#aV71A8oS+Ly_s~y+gZHr)*1$V#=XE)5kt?v{Gw#oraA$jg`O(3Tz7lI>q({ z1qB7u1%C;Zu#zeS?GV2FmvaeSLY7cVkbNLB@(CS5|I!bH4x!J6B4m}Pw6><>l+e!i zhZ54?0ct@#!pPg!t`kMRtMl$n&7c`9DP6HcMqjJ5#qqJEct%nBqL4j>JUq-W^g&KE zWeh+`g%t#&B6Kwsks;-=9R!DoMprd8U31nRqCp2jz9LHTpL7W9DS{P|qYXqMgGWrc zsRS_Ufs=`|tW+?-)5@yh#AZPV>zQc?vrplgjE8pQ{gtqernD`#WM=#r4+%0;6zC;} zSBIAi@kmMU}xSqOfg$-!ybhY(rC%`BR38oMF7C_2}KwT1cyVzBKzcu^rl00#E{8 zP>9UoB;{BI1qB6c6Ay&MhH!3TV4h>a53`uUbe_Ys zpFx4DZ`OcEJaVY6=$9lRh#ZLIl>#L7x^yUcO@qafKEqRm3T!G&FeIT(*QgPFFM%kJ zR6851*yZC503e<=lhL6j7x|abDM`MXuaI1;_HHgk9~fCZj+}8wh_Q*ty08RVRO&5z zBu_=2|83fd3^c5ie~%(<10rZ}3qp|;sRCTqRGM@IN~8zpnQ#dq^%Yg+rGm^)ak(+w zjERG}ibu;0F8Jcx&rq;P>Y%Ar7Y8H(c*hZ-4$aAH8UY9uQAUs6^c>xDrulTtU3{b3 z77ld@^aJ_9Hn>G>uADfqLTFD^WsmH%n^1;%n_~@2IhE9z87iK0h7AQFH{`OXeur`e zuL$o{)rvpnJk1e~Lv3dCBrR0qQG(cd&)FsewZeR84dQWN-!+%afW5k5w*(ffj5!|S zQ3>zTZ++%I!Q_pO`w3Dzk_f>G=g%1f`Wp3 z=xK$xzc*c55otWrh&)u&)pb=sPm%Nu8BjzRnoW`-m;|tZ&-am`kkg}^VZnGhWvY8l_h#yr<(9*N0%Jq~Ndgfe z4D(pqMP=45sCGl{2{%i$AV9WtbS*64B8oT(3>2belnP@q$q~`e1M@T)NJ;(NLbB~W zp2hV)rhZ~?IZ!YXpE*(J>l*)RIGk>WUbxj?#}WB@%o@7W%;T7RR_~!1{eq3u^!wN& zx6MN(m^oXi#Nu)#Paep}6uJhLJT$@F#2k$K&35y78vj;-@;Yj6m6%ZZM1uU(_#Wki+jv&ve?l!e;8 zH@p9iq!grerIuv{m@&r}MeY|v_kfWca&6%oul@HO{{R?~0V^5+IDi9zCB;6!EG%H( zoI2wf|NhOI6JHh-GGL$h5=SbmvuGanralD1{r1(pT%zpmeP7O#z0)T{wnV0II`rU_ z?dZF|1= z-q&hcoV~#5r3cotaPJyKdlC)M57Z@EQAsJ`LS<8^0il4gMj@Svh}A9xMEsEe5?KDj z{T1M#M-{NTp{N=Kv7~WDzb)12p~N}=J=Q}AJ++WCy2d`DMS^SA&fPC!ZP~X6_rg z*Vi`MbX)Ot#A)y+zOZ&O_Kx>le|lJf-?aOdA+AfU1-ZHg*XpAzbH(v?aDtJ0{Yz%X zqA&SLy$S!?z8aW2=pEz1MG$5les?!B80HSz;hEaj{C>s%7w83beM(KP5g}r1)Xwf} zcuXxyDR|xbDh&6#TXB1AT|z!0!zj9Amz1fr9L(WS>zdu~UG_rtYZM zU9MXdnW?r8lG4Z>?a7Q2HcD`ppwl+96^3&9@e2~gCL!K%o$gRDE$%j5B%lA%$Ec4k z?+yQ{3xv2L%)(TKA5|lzU#+RJ?(gow{ucOoZuNbcvW>W9!LPz0Ilx2~x9I~?G{B3y z8xH;2c0o5F-%EQ6N9|mOqxMXGtJ}nXuWu^bqFZz0jGU2SGOq7&pXr{{-HH()h7+%V z6q+3mwYT{GfM4bX1f;+~#;AblJIJ|~3U)|MeyhLSb-hz)Hs#xsSRo^n5M4zWzg7^8 z5mm8CP&XMB0CpGIw$!+dd(BLy@t1Zxyv0pd7p~=N%+q?|inoH#a=eV*D8DrubRMv5 z#}4f_=GzWZ!Cus(0Mq0y3g-L<7ZkFtPYQP_9@Xcf<0^UBR&nXS7UWdSpLG8?DSx_p zwC2l1?T-Z$yoCX)9VLNWk}4+ikunjAqVDpVBSBC@3f6<i8j5;sui}Hjt|KhJj_F{23B|2;!W2M8Qe|L zzzcyTK3{@lX#Cq|zH=RscpzCTlYF~~p4zbMGvZIA#5uK`{*N>7|N8r5Wo13rQ5@1t z08_GbIemc*-h8OKvS0Wm$6xHR?}mJ(U9K*OuafFo2l{@)-?FD&Wh3rCY(*!;W%%Vc31F7>wTPltL>Q|s)d@?eb5$%vUlySbc>5Fw?oSzT_)0% zK5Y-zjMAT(d&~hB7k;HPE|-t5Z$rz2zq5C^6tyzn*IC&m5;C+Pc|3&E%yLV@UO;+B z?d`LYUyHIl@!%f8iN=)o>!9J7Ao3>D5tvsB>ow}KPK75=Z@+i5{~d?M`cu3$HqH7+ z>Tv72wbH8ES+U?bXW_??hHHlAFA2vN99+miq{ZnEFame9VmO>3K-Yl(M`o9ZsFS6_Q1!?tE-^uD#Xang+c?J$Zr^ovjTBJSRq02YHM0(DvI)HCk& z{96?YgFsn&>|gf;9G_cBxV+Q%MSEPWK85H&u>UBkPkMn?v;wQLo02#+YYr&#N8kF+ zi(HnKXjOIYT1TfYGGV`;h=E6y_V`eT;?e1DxO-_gOqPSuE&n4THHl1C#eZWadCuc@ zb0<^2C|-@+v47(TcmVd2J3Z{u_v}7))l0AZXBsv>WI`fY!>L+_7;6_r)^2g+2YneEG`+-MvrQo_gpJ}mz^zEjpx4~IRE9;+}p;IH;nW8UfK@( zdWYm)>>z=uV?pigwdbGPRv??$LrHaey1Kb*u)a%E%{2EP^`~28ntyf@&~DbIsB3fU zELZA@m}lDR=#_Ocm2X)W6LuW9&PJ`+oAa^HTQ^@``fYpa@_f5_2j=Jc9}O0XC$IkS z?cLVSveql(vPBTe23D%AMps8hm^4+RKxR?GTc&=}HdME-fV1 z;}Q9tn(&%QpDrvRz@%iaJ+9gGs!s%dpq=2=;N~%uaE*?7N$Ym%L);;VH{dsIEAc}f zzueZpKzzU_0vo!p`-(}g&xkkB`;;&1{&`OrTRXa;Mgs;#1G%BRfJK*k2b@6e+<=Pa z`k{r~Y6IHXNcozQek{5S=z5WnkeJxDug$na6an*cqm*G|*j_%^EDJ|qJ?$NV9+TO@0 zbD{J1{D9=#yOnkd=h-eYR5tc*hZ~Mrj1PnX z+Wvf6j{r+Zz$gbHlU@;7CX7$8%(1CWYd%?GVGjHFcD6wuB=E7%laefPzEV? z@wwc)kEH9X$GhDJwTB)`&~_h-_W-i#oZ94I)g&vI2@3#){a90_sfpCr6vRA=n#N=M z#;1@?Zj*8j&!gV&j`)So_=04VbS<<(3$@TZ!78N!I7|(~A}BP-n|^*r6wuT8tuSlH1}Fdw)LG&0uGnu| zt<{PsATJC%=0ubZYY}Yd=iB%-&h@S&tJS=NImdic?A9 zI*yVBwi!9vJl&JpU!&-YF{#^d4?Tyf;$F^g^f@j`$Dg-9-8WvA-iAL0PFh^GMoNBW z8F~IAdN&JE3bF-IRv%zAW#U92BSrU|=5H%9K5Vw(n)kBsPo7iVX9O+huc-F=Xg!n< zy0+B-`87XCr{c$&i3M~|zcLEY{8W4V()U2^A@E7>L8SYLAhpm;{$LgLFFqb8n;@jn zhAp&03$@Vl+%(Ey_)9wk@;@qs#lr$%Q8kr{3SmSRc8OH{%%&9qT26hZCqo;&U`3xS zXpzUJQ9=aoxf>ppG8N{)J*Tt>CFz{T7RzwvDCoB_vDWgmQHm;?2(9_g%u%JrJ1Xcp z@um^6#>>eJDXWUQGU@ko^cM5y0uMae+h185ba~nLeSyCo0WwK9nwwD*F>aI=r0yP$tb}+R2+offqg+kmI^HB28%jUEv|PbV_p(vPeej} zin7N64WJ}363qGl9OU5f0{)3RYF$r^(83t1CX+xprCm$`H+(~kUC1pkE;|d;U9{>H zbUeaqk`=Y9&!GEk${4iE^H#A>5OP&2Y>Y=AV5dAPt1M2k`)B`<_o_~~u{&K~8`~*a z6iy|$Ne~l98S{d1u>lYwR|y8u-{bjPlNL&JXgL>yhd`;&%_g~JN@kw#^B4Oi zqTnI@3!a}fK`pk>sU}@X4%rrzGDh0#0>Q_Qf|(&f&hHF>P5w^rPMsj|OLYD@7hs2Z z926z$X1+{gwPaN7xj+%Qq)pkhY!x1jWsQ%f7F|JCYV8;wT zN-K{>TUHitT7AmZTAzN7=kibOWv)%BgSGmcm-Utu7QWhFwEV^ zH41ewv?r}+2Nb93xW0}D=fe8XOs@acVMF%G>6lMc^6cT83L1M_UA4XSkXlu59E;uh ze{0y@LcUlM)4W|sPscZZ@Ij>LA2J_Wvs_zh?U#kDvg*_^e{C9HJM^PY*GyZncxchAN3Vg!A)YG-zp#5(gJQE~QN-kITB?7XO#e`jOptI6|u z9*f;2+WzYfPD$I1d=AtxfnVj0$ma_M$u%zTJ)g&LAtE^2XJQVQx=$rO$^OMLe=H3lZ7@9NZKnG$TK_(dTV_;L@*0SRn3sDHZ@Vrt~%%{ zuT**rPV;f?k8*H7t0jaK2h_wBYhM%kX0afDE+oOR`uskgb6np`swpp==-A}}X*Xt| z<}pgu1X-xCp-yo&S0^&#o1a%oETI+IYtPDGo$agzr`E;{W`>#+T?LH4l^7XRm@%Z03KCJDWx}in^0BQSeuJ*CCyKzR zYPmJUFj2}A<+$Li5bGbr2rFFH&v{_j13y^T50gRenSfLY;|6^TflD5v8tOtXq_qYQ7QJLc;A zkM4u~;bP3X;3NeKi0|m9L7Cv=q-ZON{xeJD!zAT1I;kWfD@>$6%vzJ9n6I8w>1^Kc zR6nO$65mG7RQY5a!We$3m*DXF?0e)eEiA zLM^mH3wqZ=D>~PKEDd2KOjZ~~ zL=0G$xc*RXGMQ2c7H9u}=TUm=85*}g{u;v?Bw9yUL#C)nvOuU~fvaDVGBfPZM?Bfj zKlA{Zv>EQBzGowWyb&`MfKoM47p#hx!7D^2gab&(RIr%dY7g*iOI^=%1}<4HAqX`? zWsr)FMN_4YPh6!t0uEGchga!-`GLG_>o%iqr$RH4Qy{OA87T@*3RGE1_oxy81K%lN zDOIuMWK?!ZR0A85!dalzg+^J53Sc26`UyV2T72^Zd05y(W9aUiGGryNBUq;f z$}m%5Qxp)Z%29mdBq*u9EQ@v@$Xib?8>RYaM%@Swh%N*EOmvpAw|YY+b`WtbHMoSV zNz9X{C`QzjedAxx=C%%(*Xd~c&@hzDekO~hr!IvUC^trmt} z67tgQ^9arVU3)8O&bH=IW3t27j8ZedwaGx2dX|yiV)=h3rt~SYOcMxLSk7hA5-pY4 z-)X4MX&;6w#pD_^!mY5529lwdCGn$Pie-u#5vdtQAi+YasV}g$q*oM z<_0)x&VSnFRNQK1FRZ3mj@!}CHS_?ZS^Q2EBJqn`qD$f=CaC%$O(ZxeHZV{>aOCX| zm7oW)k1_y5u6!?RO=0m;1i)fgkr)AD(vw9B-B4AZ_;u^OI`1#MQQqn40YFHZHQfM|4g!$$6TJmrrZ){-oVPVYgc;mtg2+>>Tzb%{t{X}pr1tT zAxyt`4iCyM0^ii%t`RzEG?&5Cqac4DZhGJPPxsx}k^p$dA-%h0Iy&cr9-;U9egcY` z_k|Yws#%O_hZ)Og!4_Jfg<5FJpK>U0#>|>jWiY;~nu?~fOw2U_QI2q{_J*94TJL(g zn*k*mkzWxFtJx(BD_QJ}P-85m1cgnCF=Jy6F^GqP_tJQnn@S(9OIq=800v!?(x6#v zr+_C26B5`REYha|#l2Z{D7vs{=lCG=?Rk5H|Gwua_`J}Yii@WTYbl~L4201F0&~n z*(M@TFK{6*RQJu-t-HU}#B#YW zEr0h7x*?&ZF!7!C^RLjdwk)ITU1YmbKfcp?6b5;k*&}GDLw|2w_H{MTV_BcrN`8Ax zY|w!=h4F?CUDfLv4mD8##Tpqs9X^VP)HAIc*7C!lM*MSRV?c}$5CZ@J05d~0Gynh( zRUjy|XV_912Sy=v@AbAav2DN+$3%e0+zH}9U>%Lt-Kfbo$?i_$#y=zf{|4v)0A^&M zXaE2dppOQPQBa+=G^A;Nm4_8t@SCWp<3Erz-j>+AkYHnA*1YBT5RY*Mwy@i5m4c~2Dyj;5|ERS1YD6O2{b9V zi1qB2YiOyMdX`pfoh>8MrA!ZScWP4`ytNo5HIUplrKN%gQjY^QgEdPVq|mI=b@;D} z8EA;mMb>tk9&J-s60vsGj_TDbYwnJ1^8{;+qoxIE9XozQ<$T%yvoTu-Pg`nHC;=PY z1gM-^R_j`07$HpotS+GoROKxM#6;7PM|Z0F$QI0l4doi{pb#3D+Z7t{;qOm%&YOUC zfD79)Xh7&AFiJV9f${Z{oS9_a+11f6ad+T3UNvkJD~s+OMBhb8QO6 zu>o+|;|(knsZ;`1#G&I0A?9K@h{}zLuPL`mi4!|DYBmz1%(p?!jSC7j5kMqDjS;T8 z8Oy581+f!GDwEiqvDW0Oh$$GjCRLFPG>3wS%KDO6D%Y;Ck+q2WDm}dnY`uA^RtLJM zS=NF3=@kgAx`bcZ4wC~wJp)L6A2qE0!FMY&%%3qQtL{*^5}Xw&d8vGdD3c9TY_Ju@$T5 zRso*pB%5@~(`w%NYUWDDobS6MQ=&cT)wS|VTih;;gs`cT;`vxR1>~}~Q9KEhI#n0= z&J6OWu;gnbRFAb$K}9SyLL|sl08P>45=Z#0BYZh3kE+c-LTLl!`0zb8agRUZl~R** zr65bt+-xQbMa#;$<`W?)3hz$)#oWp6m^>AZj1KigLANQZBr6btDvf940?6v5i6)>z z0NA?W-JuXVty}(lSv`L*s?o||(RRyt_F9$dwhbKT8FyGHoI%Br4M5xR_9?$k~$Jh2g8_S=?1#qR(t@ zkLvEj?IJ*}NIsU5phZ)Npuxp07fQ!mS&kYeJ;tdcOFIfs5ado`GI}O5kB5tKeQbXSFe6I#kTg6e0xD0`AqQ&dv z@K;%lL@a(&EgO7k8nhbZvJJA;3udJs>m_9iB^{r>X9-rXf`~ra| zm86Pr337{VXS~w8{scnm&+FuRz_D4>p)~3JnvJGbxeWW7CEq*Jv2HfDUGM(`R!no? zSRAlovF{cs`MC`wGNYs>N1g}jvjeiGMm~-2&AM>}ypVe%Bt2~O_7VQAs-|Z5!?kOC zM|C7!cde+@te~Bgs9x`;hE?ZOVPSWy?Akg*-d@77w)kLVyz}CVWHQH-(O+3 zq}=ZG5AFXWYSnIe$v4IZeRldQ_w2_qS*}2M)5i@gEwfFwXas6{?r))OYgRY4>PtUG zHNuA48mABN(iYvm@%2BlWvZLfDqS~MVWUu)Xnv%l!qcv1)HuO;c(a76WYw${dun$U zH%2lw@=fJdxxT6&kk1b}#*HlHu@$GvIDOmK;^VE?61=QzmX3?~`}M;uL#KU7kavql z7p+-LnrDNHv0)pLdKFu_C^rrd2YmwXxfG=!v5mtP+(UK`*B-aWK`&*a2GOul_8D@# zec9b?vs*3ai7^vcSt}UiW#Gcp*(L6cU&v#{l;gYG;t#vVL6gM$R*gdYd|F7k$0&njY(3s!}jXO?{CtNi|px+ur5j;$Hm{{u~tZn_CDxXlE@v&K>IYS zZ!cwyyvv8pKh7tj8eZ;4{nF^Cm#q2)@0t&;-{KH=Eyy8uc=Bd{-1P4OzDpT=fJ3O- zYK7Nw@^6rS@LyA{!?uXwu2ry6KIt0kNT9(efVS9`aZe-D@R_)tDG@r zSB96@TGmZQj!NW*D~e?< zsCqXV&)epg9`l01>t(^Y+IJIMXE!%CLLBZg z?>=iwp*WUW+tl&5tpBEFtsQ4Qe-~jJ9fdd%Zy0BU_qeZ%DQRsuXbziT*T^`}oTL6yZ>J zleoEl%Kc&+!7-I&Y1b=g(<5azdRf=H-Z#!YS0iUw*L%prEzaeYtX;q10O~sAa*_et z7jI^ETx~Zkr*UaDlL$!ODdW{i(`|i`2ND3_h1P|WR&AQ{qj2u4#gM0HDT;8I6jbP{swDoC0SlARDp#1p= zlf{axXfHKi5Dopkr&`jA596Qm@SY;9Pvn`+_w2I~XT)KFgX{hE%V&Rl&s_d^?3DFPv}}t?=!ZQ%R*Q5B^b2 zd^7du7)L5unio(|LWPt_FypP6{#f5jp{BdG(f7(T3*aZ6-uwl@VRb-G$ERvPEa!-8z>qv^h;9ctpPv2fPp0 zr81q54cs5G9E6E(ZU=Xnbe|9LgYmNds&3VqAKgQAe&f^L$5H;ic9?u2)7lq4Uoap1 zv6q3F7ab!-_B{*AHN-Z_lhmWq_PIb&e3vf~X>jH~@HmMyc~ALJz#X;mxj>5&9@gIN zSG(#uarZK4(ZKIoZf9AT$g504rh`AAk9^eV94a?~@r>e*?>Kh->~1G^r=w1oeAI8; zaSC1I4`2*;MGy4jI?81`pP`~Re`X|6$6GF~-l0<)xr-49}5ItQ9qD^#!n$sha@eEzxf`TxH#-d}Iby|tAP??*f zLtx!9#;WVXG;Ewu1%yZ+@ZHT^6Dz{)Sf8`WzY~eDukUf8Eah<~ATZ~;IZ5hVbV-d< zxzYj#?Qn7g$T3lOxdF(oxCKaZNuvrQ8WcbUcJ2&hlZnJ%Tjm|QPJ%cy zd75dPOI$fT$YWMH`gX^CnSz<$oS?Vg<#{cC&Jt5zEjzZM%;jAWIX2r|^1M|M zo&x2qFVfBjulr*5~F%03b7zsk=>iT(UV1CG!7C+^Smk_&XWmBUm1YU+pS7Hv%M0d^f9vR9~9{!Y8i6Dh*?VtlQjva_D z6&3mJJP?gPvzCEGV(Hbf+>q&HW;J8_9E(PlODv8&6;<`mFTCGXAv`rEu91w>A9WBZ z^Q;s=G&Iszs-q_!8T4`CDC40Vw1nFO`AF%xv)g@Gx8>Oc`sYxD^^_Zm3&B(Ve04!2 z-Srhoj3a)dw{XX!bEEexwh{+u#Z%v<$tUzgCf5Z|&_6u>AFgMzGvhSH5DlE*s+4X@oTOB;UKoq&&()zcZoFu2TY4bAY|S>o0_+c#B_{B)WjLB7CN;zMdQp3oq=PYUhysz^RTrePGLMR+ z!6`0m?tiu4*8IR>Dg^DHUg^j3p#04#KP2g!1CXQ+82`cwUXlgjVNlrA2H%_)8vy$D zSU@)>ZuBkJWQGK?p8Ush>?y9TcC2 zA>j4ym<)dk)&ohsI+UP1s3&DAcKRAm=7&D>dY{^M-JuU(=<@dz7-4DMyF?osc?!^v(K^-;ekEOr+bs5qm>uyY1td=84?=AFAW)mZtj zvio@#4T%1N8mXZtk1@~u-g*Ufb2+jj{}5Axov)CZ1AmFoBV?>UO`Bri@Ps*oDdz|t(Ru41$`PU4MdU}hFrfdyD#D3e;HH9n7ye}Mly zeOAkvwU;ldLQ$r^3@wS0K_)?}lt}5LUpfdZHN#`J7B6FlWT$eW)>-%5pXqhw_JGMM z;bD}MrwE>_^M#<1C{)${34!GM_PfKYepmSa#EO z{dzAz{1@s9yRNd;whPSuu|Ux7xb1v8J|Y5vPWt`)qinJCLwnMO6u4+;m?asd7L}SR zI-E@WRMi38;pnGI=Q+np=X0KA$sRoF9L z0?L@w1s3~_dJB5{7D5Zmzyd6=4-Kh_N^*p>*eu3b(hm#ca1M=@0&l$~!YGotlonW_ zm&w5x54C}>Nw8`>swC7qvv~_5rZaBOR{um0+jPWy1*lMYSTMPrwG)szQZoM%_PI@w8qM`9v>G}(g+AddMK6X6=e}Y zxg^?J5@t}qkA;5@5-`(UFcZ%Z9JWx_^2lS2Kp<3+Jy4LLE~*!RDheVbK`s{w1fzPl zOoIkW&~`y~X0&Pgf-DdULB!ory1|m}CSIA_UMQP6t;(Y6_WbO!d6{kSJj5yeL*KdIBUW4dteWfA#ywfD z`~vn79w$og(RUj2IqoE59=l{sC2N18!sD*KG#!3Yc-@j$)aeDe=Sit_?a(t`kDX%i z^(D+E-2G5P12}(vP{%gn!M-Hjy41#F4{IM!zAk{KDgs7ykY#XC>DI?^skt9BLk=)g zNIIzbDr4#n%^SEHJ(w_T9D|8RCtRm~EPxFEMS@hv^L=Z*DJUk;R;X0}ko{atQGMK) z@#RTaU}>t@0w+-q9*oNQr}Ro-#W*@%e{Es3D$bB{lkC6(!LdvF@ZJm3zyd6=0t@fP zRG8a)4@f$ARlx8_A{9#s1Er}Rl8k^T1j>dhTS9|B6dc>-jz&L4i5o>ZIAvDi=a)r2 zLgh!uY(Cz8L85CCdLFK6Ox8BAD6&hjrhh}C6+h_wq57sHF%Nzw|GK46y%xVy196?Y zKpp0XqQH&IC{f=5%4j7dMT9j*XwV?&0@cyGn5ssaS+Ue70TfpU|W!y$Oa!v%Ym zj~nzDKpIgHt|Hj36+te-1RqU6Pm$3Z%lbjNNla#)HloK?oWjnX)W-pJn_kiHQUSE| zAQ(0cdzMs0nX06sgfRm2aSEcCUhV)hc+ai%WaLgC7Gq1$Dr%_64MFT+6oMl}KoxTd zxWIZ>kUA)LiCOhC?`(GGyzY=8(UXhRZ{7bDyZU`siS`wNVKwN5I};ni&&wPe2k^xW zofmadr2VGhDSDhwx+gLcv)jxh(eE0xZ)#l~ANlr|3~zJ~PMxss)DF4X_J=^)CC+u! z;-F9a(a*d8ZkyNrC-(dwJA{t+{ro$W>|6D;y|v*Y_1llWCDEW;tIgm%|D8K60kNMw zHFO=7UMR`emc7?*v)sg6^=ciKE+gxb!&@WryS+2SR=MuBt7A3nhMggA`^|oFdqTK3 z)?(**|Lg?I8lL*^q^H!09=`G(VtD=kYyB&-JaiveGl}i1zvy$LOaXCvaLYv~)nmk9 zD-_D0{CS~%^Ul+q)MHh`^A0K={SQ(decVQFZ_}xr_I1JYdf||PaK z1C?-R^5jlv4oR7NkGrAIFXjLvr!-7Lq z6W16~3U8!}drA%>|4!~2tR(hGKAdNP3?Stb)?MHA-XS1DoJ3MjQf2(!eG8t8U$dPry`D~2-*AquUMLYu0Jlc3}W1VS>Hbf3yT6E5}{lJSR?Ql zx>oMihr;lbks?H?4#xuZlbG!cY@lg_x=fX89%7g<4F=Wyh)|WZkx@m)n7}{5xcUL+ z+mEZ8$sk+vD#}v?hffQLqAVcl;ejL&qA=r<{S0N5YwmUMGmtGEGy8CHXXQiQI&SOf z8nU6q`m9L<2%5D)_Z1^`1tMI-8b4 zBgx2?M3RXyRg6T%krjqxn<6S=fZ#4TDh4Dba8NJ=cpu03Kf-*7;^TNeYyAZsus;pb zv*lQ!^8v%|V?jLzv$SV_m)pl*ex%p{w=HaJb%6)lE6|HiiT~XmweDnj)cn7lo$D@I z4GZE@S*dmHZM|T%RbmYlw*uYGuV^ncDZ3=Sx6f)5{3n)VEm9o2 zj*Du;wY#a}AY&On;x5;UHI1s}llddO5fb(nSo@k{PUwBIZG!36$m{DQQ-uwzx|Zli zC$==OSgMho)08?oRhTPGqN1*54KNb}^FV@-;Vu#fpV7j-6kM}$mhn&JtqYQO4-9ti zB|~n|>LR}Q)Xg)=(L^4_KE4|6wwCoUyNj0h{CdYy|4{pQNtXq!tlPrZ=$$*PkkH4z zt>C@`o;~EX_*yl4Z3?a>R`??iA71{0Br&g4nEz#-B|jMyzQ73S2z*X=zwa7*O-h_~ zus4Pw-A%rDz&u2D_~+RI!N2$PcW0;-UUyCYj|Lb4Vc@Wh*ZJZd@sQuI*(W2$Kb)_9 zb;P(Jadgq2bNHrIIx$gW7UQa|>e8t&_Bs<OX*iQ#0!=6%#v=~yVv!z&gxWM;_8z9^< zEqwU#s-~rVcSTn3-Dz}uv+C2aDrz0-DQLSPPwl#>*VMXH-}YU%)Tya%zZLZYBj+rP zZC3Dqb4-EuVG=)isJ#L`RX%wf$o!q@~M* zxEFzWWU#BM_S5bPZd#^jTs$q+s!6s3BGJ_!!Xf_fL@IO=40Z$WnWB=Mfw@BL_n>+zib*;&{Ez#SUp zQTA$W6gQK-ZSJ_2otqsyl*J-=JmkxKh=5$?D7bo>@VY)KUfqsV$1O`h(;f%i1~X$7 zv-7*>Y%G@T7w4~by`uYvgD+<_kqs;O?SEt6&djUcT8+&|4VT0NLl41bjP#Piy?-<# zAs@v(_ZwQ<49JjhjOTqF@Q@bgta*R0IwCzO>$k7=AJEHhgOIT7Lrz&QiI8jXk-3e! z@1jCqzAGK%K;(n}ufpGpgFKJ*6WtB$cS(FNDWi)~*Qc*rhqT^wtAba+L+fBpmsHX_Wd5GBeq+bh-UEFw#bf#-LyFe_dCC9+3KSwL;mM| zWRY+A`T2g{j!|7XOgh#g`cy%X>D*u~m^I&`{v-PijW-r!Tzf_~M_tUx58nWHZLME1 z`}O$PR;7m<6BJG^SEj-VnP+QrktS~u@~+nA0r3FXU_BTg5G z<`PEx^!L`i-$B(R!u|1v2>@gDq@m*Te8AU3$P z)a@%md!ZU?VMtBIDdq$RT-P`vl>pbZgYRC$xyyWcldUr;Q*u+1@{#M+km(;DJ2TnX zT|2wj+SaP71CAEFwExCzQaE(C;m>ee@i<*>6(sa}82Vmgtc}+Vu(H>G4f*rEmi-8_ zErofq9v5BpdjrX2Dp3F|?QD5G^@gm$#g)ohd4rAaVK^UbrL1QhAQTWud~=Rg%l_E9EagcN^*!q-U50IJsiVPK_3u2 zUnq@-cK1@Yw_|kf92sxBX$~L~yYwbt-{y5_wOr^Dw-TccMD&dyK8o486f;zA!NOA8AVh0T41y0wPR&i7!uic6Gp!eYMNT(S`-!TH^y>Vn>q>*a+>^1%qK z&_C8R8+Kl|wgbG1p6`m^^1{vHohK$|x;I3AF_-4ed=LT0JN^Al+$OY4K)|kNM~W3B z01zd!r;;cQRqK_5D+&NDt>A9C`zl6FxLf1mta+lq0n)Tz-xm2GK9i?rp#=fffJ2FK zsxGc*h+z{bfK)W5hRx}59Vf1F02KH1d6$|edpKgo+8K`68=&wvvD1_$aL5y$sBQkk z4HAXJ((-vC&76F_zk0fc$|euhIYMfBdAcX&{ElS@c2hDcPjz#{#f@$voxh4OhR?u_Q?v>OfNn7?<0lF7vt z4yZPQas^YN97$uWW?7t6(fB(W#9iV1Op3LyvbDadRVQY5fV$8r*cNDs0% zO!5(1rA-xMC5^#wsK#OOXz?}Hc6Aw);|w1+P_dY`f0w6SO_m>9k%?s`Ocla_$A%M# z!&aR1n1BQ`=$d*Se0V1Oqspbt6_cRLmRBw0W$qjIiKytiYm3Op8Z!yN7ZCZ9NI_>B zqR2v)Jc$*gP*pR-L%LixH{5qooK&5=eREOICz4muZG#zqF$a*o(u5NgQb5%fTs75G zL01~7%_BoSZ;i$;zx|F;zZdwNugxgy6^k;23Ss~sj06C?njA<-4EMqd18Ttua|4-u zdy=OVDA`40c&VCT8@|jiR6-54Q#sgejR|#6)vJ2tJbOJ;{pp;vh_vO>rd>u+T`OK_ zsSDg5hFrVA@tQ3mVr+uO6tbta`ax|F+r4=Xsf*_RPAq$;$>EqLd+9XR8 zvazDZj}0P@^lqa$%xm4r&@(-fR!Eg_DBfeR!p-$`#*f?l*<>tfz#M;x$$?8_!ihLNXya*Nh5Wo+Vy$s%)-DiY_>Ud;x0j9-X@ut zfu+Zexm4mF3E`cOgdL3;1DCOnHr1R^sKP*1^8T5qI-HkKX40s6uCvi>{vk2lkY&Nh zSDo8ZxEmCN*2m`EGt_(4xo#Ex+el3y4Fmg+=3T1!rqnJI3$Fjc(*2lbR(Pac$y}XG zTl`+8Ga*jBZF?M$aR|dNxvA`B(k(Y`i(Q%My?I!vXll$vc1!Ul^4A;PPxY-XSDy)8 z(lzb#!4p_{C;U9`ZgGE{v3MlAs2j0;epJbzcu~|bF+>t%UU}R;wjI6W?s5=O5XmG+ z2@;zGC0Wra%n(twc=SO7y7lhu)bECIn4w2OY%q=)2TzW-j*6YC9}Mun3-_lNEBA|Km!Oyt6v;8zq?igO%P@o`nwyZl0QoB(O_GY6;|VWrK;sshFCUHGAqPI zKn@xs3F^;$mB<#~Li0lur37brc+dG|Mg(t3P~{aCy*5!9EmpLUE&Ex~jvNl{s7nU4 z4el7MIhr7fTp$d={`to)g(*=+TSGxH_;A815wd5Wpl8A$#^NUcb9IXP$>$1!L=Pv_ zUJEj8vjoWnZ^n5+%-3wL#brmF+%>+6thyixJcR4Q$i;UGj`N=!2wL-{$2QYy=(X zkI0@L%Wf2bIET_U)lfLoRI}*EMwGt-`a$HM-uzejp`(T z56^jN7`LE+lg9oB`Zp`@oTR^_)zqHUQSa5KA$db_M3lvR0BDXGa2R{9p352ts4C`p zr(#vb7-2kSQu%W9l8m0cy_mvnzRh6-Kw?t$XFMjU6u8$sxCT;YYL3#SvmuCBurf(``AR!sIW~2G4A~c<9>@uTlA#l#PVwdCsa3)oWf)ZyXgs^r}YT|!tHXp;ID1|DrhpV>2&PO%oGtnPZPID*aU zUUzq}Z9d$Qiw4j}Tqf+x&h$xi3imF^!Xf%G z&(NAFT90f`d7Bd`wn- z)WqU%%Kw16Tx#aZ7hZ9_l_lner-pB#_~4tH06_00{zunKCSU#2%h;5>7r1>_kESjA zSy{jtc)7v$-~3n>`C?JrbI+HDsw>%y2;kWT9`d+qIz01+qulqtAZ}v?=n00adNe4N zSy&}c(b2Z3+f~`7=2?TiD9?O^MYB1BcC0?xQ+Sr+Ypc(bwJoSj-hrgAfkC#`TsrFy z;W^r$EBvk?2L;PE1@3Z7veEA5EbrVWcrGU`*0*Y;J^m@o!)3w>P&1e)=h2p+Y{D7R zDklw2P`S%{MYpoadblh-xx=GJaU9C(6mPfbhb|pm-a^GRo0>$FBvc6!uQT(e_uer- zyr)F8L{te9f&?Z(OV&z-!aJm+p#1gAidwXfg(}_XO%>x0hTjcv;agmf>nbpmDkheY z{TdiCX{w!2DA2}scS(|ddN7;tdUty3AB!d#O<`Ql8#?&{^xF~?uBt|2G@=1fHG@z? zWJ?1WQLeyb<&g*3lS7MVNDw#4R_WHNtHjC>#v=aAhVvu(!-x5IVT(1_B;)J4Xsk=w z{wZmg+^w|^an;`SWJxNGrc~q4W^)_EIU2Kg9w4cyX}MCaI}dyE1*UGzjvILb!M22t zccaPQQHH;(QCg-8x)+*_`0Hw&z~hR&osD<*rM=*wKDX-9kGo};65K)VmnR|7;u$fm zf&?HzT#zo?65W2Dwfc9it;>oXcMfx%(!TT z%TZ_!Tz_(RezVs*WnEomM@9HshR3gedpaDjPE5vco+A5Pb5eC1T&@iQFE`53Mq5v> zs;cLowrWB+{U^6HYLM=1XQ+N{@A-i1gWCus5+rkOF364ykRT*T2@?4>7vIg*?ugX*P-S*@y)am!SfLTCd!n6HAOWHlxB)~ zn4UsS;(Xuq$Tu^LRUq1sS_lUshmXxkkCmQ{ubDGNPv?vKJh6bX6+eBczb186UUwy)%?;Tv6qMObKs8CFAlyaGg)2CqG1XE#1g<^KnPE$BvmyAMNA_` znlw0t?<_k?nC6MH(=-c%Af~`@!ZfRr;{@4^LsxM+7v*%DjbyE z;$J`ppn?)l&O=6>=Y<#t<_6BYe{BX?Sm(8zFjlYAz+{ReWW0%&fXEzZVtQ0T2A1t~ zZ*J_Hjbu|>u9G!rFRAb9Bl-hqQ&J_SP?L%%sPOVLIX)UNW*S3IKr}k*%ZAX+o4n^B z{9kmeNFwsa>=W5Cu7S;zv-`)1MKX`U6tl-keYZ9>pH!7Lmm4hTj$`K^%;rYAer`Ui zAvvRJp=`d}S?cx#faw^?9NUCsk9~|E=K^+ATDEL!Fm(wt`1Z^Ve^Wfo##;rZKe-3M zEf>6WI~Sm!P`Ti49#$=xof!r6=eDy6zB?Pkkw(vwXV%vAb7AI$uJepI7nXH~bf>?$ zQ(K@jE;vwAocPZfo1&ZN;`v0B2@XXH8V%2@EUoO_=Gms4Vtt;V7fiK3IL`<~}}lzyaE z1et`5qASjT#WaY7MNJb`SJ@*?NlsQ(Q(P>6h~qzI_9~=|@4sG&ln&3;LiTJEP0vI% z8jhff%+b+M?}(q%hUFSyBC00YKhM_OOOmi4z)@=*9_+>y?V03c3K0nhmsBKAzM?NY z+SyPf%BL0D!R`djc@+qmqRrfWN0{H>I58IhCN&g>MUkX7r~!`^Kv1(l#%A340wecc z%fn-p2wBShdIB-Iq3AJm2tXRuQAq9>P4rzbv^@f-`J7tY$Bwh-8=*IL%*;^QD}B_) zWM@7l#D?CV3I;-t_S1|$=5}7h1qzVG&+Gi(|Cr|_b&&jt)L`t{v`u5k2XWGM<>a|3 znQ}F$9w9C!H`?DqVv;jPY<&)OQy>>V{bg87?oiDbTD`%hlO_vUkMck|ze z{704sWc35fZGNh&tzY9$FUr5?_VM4v#%x1#nq8zT)}H{75D)_Z001*XMKb^ZUR5Bd zbZ3ZZi3Ow}&wF?GMPd|zqm7LKktv$Sj39p$NzRf|ZCbXqVb1;c9RC0qnE|310I&c9 z)L7X^1;vTgswFf@{Is7|=!$9F5b^5T7C<8*l{I3vJ4guF>;1z9p}0^t?qs&qT6^+9 z(bk;hWvUX|!E(!FXf44{_004{t7R>@6JsR%+ zciY=;TWg-*d-p4ClIhJdy~$%S`fQiT*EZ{_i2r988K@_wl z;vY{aPl8W^evAX@K&6ZoEmpelB%P2uT-;-6A;A_QJsi$10gC@M-Zsr8%g1*3Z5^+D zv&JFQq}%l@Rb7ph6?%GACQUl6?fb2T9fHMw)U{z*6j5O<>IV25;EzgO{>63ESe1hp zAQ8ycyS2?Kb!s7yXe{@QNs{rr@po5(E(7GoZmw@?7QS9b=;lx35E*eY5lXd z*JMxKJ&YLiI&C)$CbCBCogMK|()RXw8NFg+L6A220*~Ad4&SY=RSDvsOa$B~*1%B~ z-)%#~{b&R}-AOyD5{M$%*8=^a_`P6gO|#9b7_7ZlF8T>23La+59^$bs;>!Trs*#HN zK@QFbQrZ_7q2oOKJS6e3N~qfYCf>y?gI4OU!#;fXA)dlu*nu2kU6u*7znZqW7eg?1 zmYr|SN+0hl53t~^`9hF2PFsHUf%oA)Qv4%%$W^zEHB!xr`_qROL*H)2BL0A@wYeq@ ze$aTu{&p7z{LEf`QD5l)Hox`6>uvCLW3$58h>7dpufDy5t-J1y)xO)XSw6`c>u6O$ z482+$8XU&$Mq6=lS>A6V^x?1qnm~4TlL3ajS9oYKa8vox z;h%TVCQqzw^X$5KwICWm-)>a3b9ef8ooCpoq!vJEA?@Ng%bk9*9#t+-);Gd0HCvZ%xAXs)!=U}CHykZpP34{N z+;sY&ELvx(Pm6B6J1A)7TvAmaEJ6djR%KuU`+Q&~V5Q$owoGl|akKJcsO+kC!?Nwq zQKl>lv!Tx#f2hVg(=0IJC;GPr4aX0twrPcWCIZ|J6}x<>M**b`xAnaQ|MedEUUqbf zT7ANtGi6ZLgmY_$3G4gwsTWoTm9=)H?W;x8jK+3>2|i2-bjr|qv&EHDFU%5 zT9fZU9IO%Ks9$lgnm;2uYA_X|O_a-&WGn3=ZIG#?7feSNB5Hr-^3zaVA<$X-z$6Q+ z1u6mpc^NRN(WIoL!HOCcr0MqvT#aCU#((GG@7FhJYGq3Sp-jqDBdV24!gY2j9Vb#z z(@7@Hx13hBb3Q&)9X)^BY(=9H`zp;X334a)pT%@}reiEB{mqh+u72V(hWf5ww|(my zH!b&B`%m#WxQ)??O0v@M%Vfkd?esCZQD5Y2k^vKKlTk^R$=}k_3)#dIpHJk9j#4O* z0xQItw3jmS0a%QQV3~VY_inB@4_Z|m93q#h0wPsDzmv8~$8^pr@3>u2zSs8ZtPia# zoohtw7I9=FUdb2je`${@McqEb!ISRm1RztCXi1Im*mtvGLX4cs|CrP?=*^eE5#!a_ z3yiA`v*-VDeP2zX72?j>elNhjAltDIe8MvxhmI*8i9;F{*Im0ITFvgbjXzvkLsSJm zTO5O(@qLe)ub>6ipND^I*>FOKaQPQ`zT;K((cbqN`dowjSrK8>)Ger8Nq_A^=)S&_ z-=Vsr2K2TrF1Py4-*f!myDgAwf&Tr5oPPAn$D8@oBd9gMG=(;hQN! zwqZSew*BIU=bpzDoJX4}=S(koZ&%bEok<;v&~(w_tf|hIOOtPF$$#UR)A#$G13F8L zts&hFF1O&9*#vg(U4q=CKwRk7lnF06xB9R_xpH+TgEMW4jd3luqW$yoWn`7+B_F&` zmg<(}jTfeFp)qE1ivj1&t8VdY^>Zh$|2_1cfTtjW5Y@kU7q!8+&z~>kvHblkj@h|yp=irAN`?(y3QhrNY&wC|-mdPtfJl^IK zIONRVy*EE>Ab9y> zfw{Tt$_S>YQzbnZ`n;+jEEz6qf}-zHLg4;m+ToaTpuaR>{M=wSOwh6Ol>^N}8lHp9j8%z0%v=WtcTZQ}pWEkJZ;%2T#^(eA?M(-egx@ z#jyUe?o>+8W#T6^=XR=mP((oKM=mc2UmV^!IHO3t-a0=YVw-UBMVo-nTRVa|@@ zh57*TcNzosaf@*38?KIRg`Z*(x=op1Vfm>z$GPKqo_hKFlrv`F1{d`=s!uquwr8x# zIexI@NV#JSgM+f-4}TlK#1DA64h{|u4-U-Ur*PUEm(d^UHyr#L?AU=KjSFBnZ8$fi zRXoV0)=DPsjB1xWuqw;nTW6&=%EZ0gk4bo3eO<@KPaiN@Y4R|P|UaVl(v6VUCR0F z9mfyaYAUeBIVdC%PEtqOX%Ju6Cgg2 z>I(~QOFaUqLeaCI_dBI-mK*v>x=y~vrN4*v+_O18xHoS6B}vytzDLgRPl)ubFOS$v zI;mcD|5PN&bNb%*sZx&0D8I?g@tvsd3w}`fL;E}+`6R)?HTV)SAXXBd;Dq4d;NZCU zS7lkg4M!ICk1xE1sfUuadTq3)SV~!b`n0U|*<2}-&rtvk{xp^b3lO2Q5|u1LEP0Pm z5?uV02~pO&BJ^}-4u<^Qb{eg3zN6DMpTV!1(Vn29yQ456Q$&~{ZdvMWbMsmagOVo8 zH^CD6vsm@}s_)YP3TaKHOf=`Xkb{0LxeX=nF3b2}nZ3LC^Ij#AJJs7}%0vo`C{wzw zQAFY~7gc39WCCo}1+wXIr#iv3F&>oM+P}7*{)b>Gq+boU6(bl&2Ul= z7Jq7I&ZoK#sU)wB%(C5!TU(Kpf~Wjm{wRUI(6)R8kCTO8C8XtTtTU2wd3xG?S~*KT zwQtogj-wOAQ|{1|R9q#Rsb62D^*LW`rmuSMR-6QC7rqZmU5Bw_B2&w~Lgp>@hB$+?XA%T)c7HkS-1rvwkFhCoGs#4s!KUK|0_fLzSC= z=fc#nR08kEe&|Af{#bq}OlHHRNGnT~L_kljkoOcimWm6+R)$#AUiv82=zXr15JMeG ztR-*>o_q_Hsxc9y2lOHB~ z_(+3qw}pJrXdZg2)V4=^dVkv*@`KfPMj!I_hd=cJgZ#kZOi}*(fXo ztoqXkU+pbMUF?HX$hJ%NV3i;zhFDNZ@B3nsEC9M9PQ^8+u>>yY^!n+nuL!QPHF=_u zMP<^?7V^Lq(S&MLAuzF|lZqm85vDK#i{G8wryq+PvmO~!6D{jGDgqVq1J_I=&`nVS zQ8nKNTc*Hfgx?0RN9V`s%pxZaN9$}{wu#`ox&o1imvOmb%1DN3$V_f#bA#-o5#*F+ zv}BA+TgC%6v*v9=P^PL3La~uFZ3-FfN;H>KoQOXBLC!-KmW*_X!q<@Z6Cyz~u)a`M zB1=r$fK3=!xPoMn(k!Tz&C{;)>P3Qd|J_i;ZVIb6IlDU3FRre%}a0^UK zJ7vlMC4}EEyWSZPBFN5O>nFNI_)I7SNRa{psEA6)g9MQzlY}BwtY&4y%Uk-GXMR~A z-g&fcjZdkNwGC^GMKZlN!h{ciUlnmPQe9Fn|5LX)kQA=Q@;DI^5=9;2!}pOcO{*K_nt?hf;}X#lNFbzR2%D0DEdHN`F6 z0yQ~~#%`3^ay_1;Bo?@Awo`72qT)w9onJf3A7`M2+^zS%zPCf$dzxs981aAJaOUMj z1brIY8*50fLbc~&*J-678BJ#AYV&T$=S-fJ`^{X4_GCG!2nC%{>&-3>-$eY0Gr38* zYX9l^1_QYdw!>R%A8#5%xg3$5AYZDTtbaIkqWmNutoGmOkE4YN;Pwm$2XN&Sz~Z^_Khi3FlYF7VsOWHT{(s=m;Nalk z_}~t$06*EdAE5}lgzUm}KR7I`MU5iY;*miWz%r3QO%XmeQL3pme#*0S+L#$$2m@U7 z+>lBtt-xs%6R}tpY^jh!0t|^NCX6u1hKw6sHuPuoR&GxBKUHW60K3RZ5GI86Yo#;+ zJ9rw9rf8U<+WOvKP9a|i8s(v^Y$+fuZ`Xx&EPQw zZSdmhd)z~9HhFsUc8q&!g#@t54D+c@H-DVAT^NN=JVeMEV70sbAmt8m4-UG)G6oX2 zHSP_Uav%;44h|0vGJ8^i0hj;G4fKmLwDhYSPVF4LH7+BJQ7RlMibR>B(JoL`MTL<} z`+2MD^gS$d6muWGs{~cqq;n=R>$$~T-KfgXqPO;*wkoJmC@^!AA!Jn`5&-HN6e8!+ z&Pu%Zsc@j$5$BtIt=2_zb{)eAkU@5% zCmQ8w<)B?BIJzBS%4xjh>1np87BoJvz3dML52me{`Gz}w5sueT6xK;B8u5fF)a?3WPH$f2ER@aI;I9J4Y0GOJ)?Z0EGi9e7CM%?%?fqWVzHx7d)0fj zZ(H4c97~-GoYlb!2n~MM&|eB^20cblI8u46I>p&Wl*Mf$!O}QknilJT+@HC{EB^oo zN63R5Hg=~#hk{ud%F%gFh|@4PaFyHcep}U=Wujr0l5`|8rUEKV?=!-)5$w`bn^0o@dfl36S1Cl!vhimIwu^<4k`%=P z%1B^`SD69`#HxMhe6&uvl(2P0A;hHRRG5NzK!B74S2kibFV{uQgo_t6e7L)9rAF2ClQoGDo-ka>f+b*j8ENAGgH+{JmZcj2AUeMRoTrjpy^@%nZfP}A+R7UZ82j-5E z2c>!oHcyzBv@6(a0Duq>0{{R3Gebo)003`f9+c7m1kkY(KS%BFn%GBRL?e;G@*=}H z5hO>M-nYs2l2UEj$F&Cu-@k(62LLlOP&5Dld0>MC4%N}Q!!%%EzAQBp9PmieL0!5e z|H#cXl(t6J%ADmi{q4PPimcM^H^(Gr_MX|cvF<#{U2E2O<59A$oN;lDvb3#nQ<7zC zWbvn;G#WHMF+!V|0^$J{K>-;8000>PnwbDFzbkfk+uHTFin`tRuG`Lyr?J%_>S<=; zE7i?dLe(WWP+AWNFv2#HRsyyFgKPw1;?N{RC;9NIrtJQ4CD0f>lxC%s3|T4x|N z22nbOi ziH3(Wjr`vw7D&<5d|7-_pbp8S|LWqTUT=5Vh7}u_4x#SKUu*=(!BhHyvVdrH#15fu zbUv>acNC>zgRy020Mrka6g@2qDM=<307FV@P$EW9CQc<6Z{r);C%`6UPFYR^iNy_I zAMgu;?AK3g@2g37${PeyajMX?r6)>2nZxXCbRXno0Jk(()Gz|IJwxNQc;U=Jw6NF|eslchwa zBN#$^$wBs33OaQL>!zZo!aRp1m>KgTsOwq*il2MjEs3I}JT=ebGTK8-&mI$^X-*Ri zNF2cW$wPwS4ze&q0K_P%aIu1nzD~@ zb7tj%qy1nL?sq$s?lRy6uvw!iMG|#@#gYn0LqWnX8CNo-;AoCwaBttj)~U1Bshy2+ zH*mf+-dn|Ju5yB0%ufSO0s}>b8@Xl@lD(ArVsw2n|F8JI+SqmNK8Z|&z4msKeT`iTxZBBU!Dp<9UYD$% z!=zjok~r606efw#1%y&*pcv7p*muoWwf$UJSmd;@Ec9PxnJAxzbVw3TcG0DqUX?J3m6#3awG&J<4^u=>w zJZ@#EH0KZ@pATEW?@?Cf>!FuqU?J%Y(Uy~gC%_BcT?o{pakRUqyZ&PO((HXD_T=~K z;*3~C7e4;(t8$!)K?}fd_q(7qWL??ieecBHT{o-nHDg!y(2qB#ee14kYSu_L;nt=j zK6|c2Asuj&!q~n@B<~ad0lwd!x7@XNv5~e?NO^fnBw*v|9pFkVHt~g;4L6lPhqi;y zBTjm#;v{`UDRKdn1jPhAh5X$@r~``lpFqoW;VD zKl}gaEjvC3922asD8kIG>C*gg zbo;o$Pub>6O|K4uY*{7H26jxDL^4j6OcLYqcE2 zj2?sqC7(>Ctd1>d>6gk=#H;O=!=UPRcM-R~-@NX9H@y@TXIhpfIZy7pmP0Dw%k=nE zv_%pDfu_zieT+n|x#`Ukv#i^0vZE4Ef%Y2JRn1ttnC^&JgP>?ff}o-Z)XP9j^qD0L zi1xzNbjLE@d?P~tx4kAQ>@f2<`7yY%cB=vzQXY3)-bag8c8y%dRg49+%>dU1zrqKp zn;4)8(&EM;{!E3Uqeb~zHb*)H9$0j!_CLKmOX?A#1SrocJz)?BnJD4&V}XQ9W-@&Q zPiOL`n!95udUN*Qr_EpMn@a2a)z2l|$g5wKXAhx+R0}N@b%8so+{lv6+1u2cxDG9j zj%k7O{4dBZwe6}h)09aN;til&ongx?h*6Ra14ZtfctRM>1ea?ml@; zOYYN98*Qezk9wjhLDvJ6Ff=J}M##;lhqBYMO@Vw~X$%W0vdu67nJmnDbuZFJCY;{n zoe;dNBEy~L(?1!?i|EbPx%*4>7kc`N`E+q+Nf_PJez+H3pe2h7dDJP=K|eOf!`b@7xVfO~u42Hx@IE)1{xF+j z{o&0;k*=da-Yb(g_?Enl%d&~hzWZGL$|Vi?JrV-g{SSBQIGP2$`@>I(VCmrctz2(X z3nvTz_)QL|Vn>3u6Zz{9TknwV{k^5<@csbTWfOI3GH zUTSJr`_jXGc@R4MSF8Vw4O6`x1+r$XzeT1?lz%`?jj61VrZ>xY*x6}yENWK7de*zl z=pLR%cm08@x$KVWJ_`NZPj(*H0PF)Pj$!XyE6;)0OY3vl z=UAIo?A5Y`au!e+aAgjn_#?26)Q2T^l{1Ie=8Ij_q4{_!GPlofux1}z_fbA*b65TF zBaQF(L`589#!mnK`?fw?>A?E)!jXn&=d)z4nJ|NN{pmz>I~4n4kEyhOnGOZKMhuTS zJbO-;5#u~--@W?sqrG==@m;_Xh9${k*f@gR)>?U*-M-*gkjP!fI($Jgub~j znpWU*#JXz(Y$P9ajct_D3Y2n&zw*20x<7Po!0!B?RQ+979*UbZV;%3XH?$LQ{cR(t zju;l*qVvD&yf#;gIvW`lDOf4Z{#e&>UbOREMe}bOf6s? z^;F=kcQ%ddvE?Rw=s>l5YPD|qp-vE+D6MZ-Ep7|u@*A_M@2hpb1FWHx;d`|wjFp_$ zD(tn!oqwXiJz^?pHr@))Iu>pD__qBDwam`#51(}5!PF~+J~W->knbjg6lr=19snMh zE%kMBV6#`8-u-_@r`lBAp60wztk*f{SFaB1HqCDon6jM?s@)8HU0rz{_Sj#1KkpsT z|NAr$&#IR6J)GGx7tLs8H2QR-enr~?-w(>cR$m|jzcIo-k$veF8eM%gfgi^=M)r^w z=MRrh2VZ79P@$>%Z6wfar#;LWP&Kn)_IO551^-EU7zrk_6wb!sZLMZWWWnh;OEFP%9WS$d zdl=wCQFhC$jlc>m+A*Bz=4tV<=5r{}OqijAdI&)ar(WWD#(Wc4;8#)kY> ztFXLcgUH$)tRkaPEnd#6nlkt)s(=ApM(2_Kl}-h{fg6F%!W%6?Ni=qgc54343(Igj zJaxVkUai|gm#yS|Gwa8ASNTuN=Tf%4GA1Er8N{XG`dI$RYOQ1chCyGM#(LsdVZMC& zmAcfZ$u`7PPgOx|8|69d?N<}r3%EbQl}hfvis}RAZWO+HfE+0Y2dcS>DyX6=swelV zAcN?_yAn3Dw zXC~6%g20g19*{t$sn?+Jk}O4>*}XX!0y_h2PTB@6#iSG|)RGd|0h$+M|LV?AbpOr` zbUH7io8z@coal|h6zz;-8FsBBCu`&lCpymDUU=EF5U6iB#eiHRde}&Fo+2ALVrggt zy#tsjHtNon2vP|yYZ1>T(s^=H(E&X~fgx@=L*ks?QMPtFr$nF`zzF5)wh}DahL}vG zGBaUGgwI(LN#C|(A=D`c-+W3 z(vLg%+hGcJ2D+0a!iJHN{_xU92p}$ru4h%uE1X~>K{7*$LIKM9CNKvdlY&_7z6UIL zjbu?A%J1?$Y_8R4Fk@uj*#ss-Kl5sfw`xM1BtYK~hRtWfq^wi663uAG$WqBF;}=z0 z$$X>d_oA@&MAxpz zEe<;$uS!k1@(Wg)^9dfxKFMCrek&-`piB%(KP*;CQT+gm1M;`V3WWPn81bLAJn*h< ze%4~r3gVG-bZ4_)0ztSL_q)p7JqKCR)numTgeAI6!P(YEVr z=(h|$0{+9gxg2#9Pl3{;e+$6e5(A62S_K?8k1N?-O+uX;zp{yYP1~beY&mP>b*0kQ z5~kNu{kpc?@`gk2hupTWYrtVt2|1~-y-9Q=uJF_8b{`;SsWNSVE9n8UQfOObsW+R= z{EY+Gx_@pSIqGEDPQiq8t8g6BT+Eu>!Z$zdSEzCZ9gjfm%)7Z?`9bd8@hgP_=qKlg zoFGOxR25-0iArAbF1Qzb$M8C!d(Kq-Efn~S^k}U!4Fm<)iN}8dNXt#UNerC-YXB5A zE5ZDlo4$)nm#EAO*E14MBF1~sOBsx1;)*IYy~fg(LfGCFRZ&G%RB{ysdSY0Nsv|o< z;1B&4pI&M%_33@afc4(bE>X%cbVuuznEu}z4I&o2GaId{&mCB#MGmIClO@opp?%!c z;=ub~%$v(T-bjDko!~Nh%P<0L231o7Yg@D-$w7%PBQhZ&^TT?sff!r;LzTmQ2!@_BA2lWm(`$T{K^) zspz^H(q!A>=%qzM0=vd$QDLHlM~eKbJ*Sa75MV+;i*KuabIBejW{IK6qy8i(JviFy zlW2I37*NR+6RN1}Dv4-S2t^fCQ5sd46MmT01A4S0zUkHbI4FTL6p9Vzf=-A4EfE2C znlx(96ZGRy%87mzg~Fn&5YG_l%bo;@QXmbmjA*0^m#8I*X{0X_&Yxw*Z2{Z@7<#lJ zq#0P>o;g4Ob*`a?lLDf7HDRV8hyi+)M0w~^s4C-d^I!nc3J76CRy1iQsv-$p&kP2h z36ppQUPTQ^4~mjIC8+YYao}da0;3g#gd-~?w6<>5+0F7^y5i4EHJWW5y$-TK%|G*j ziM#E3^<+Dm8C!r?oZH@8!?>eaL4xS`bMAxx+gd%U$BHW2ziLNS_1cy0{rq|k@kO;% zQ598PMb3Ggly>hLDM8t0d-~ze?#N@@Tnp=WqdKS3dZKBGae*M7B|%g`6l9{Mk}Aw4 z4Jr%>mQqFJ`jI3p+?|i?JMOjkgZ)pkb%?@ef2yvku4{2*EvG;El@Ao*NFt4iiIRjS zk#Qh4;%MYyR~=y-9N&BkkRb}n+nU+AA+386EZ(9-KSJVRYK025fMEcnhEu%vHX30$ zL;W_`;2lw0e_2&2s)4m1SfLFxJwX?U=t9#=h%z#XUs0qe>0(h00t4XE)1W*OXGvm0 zjVX|re1S@2C~5YIB#ChsW3A8@m48ThzU|(WR_}A#I4tXi5De8KB>+)G%Sd2dW4gFB zV{@yHOFB>jc7Lui+QLnR8Fm?nx>ib!&9Jm{Mrcb*glD*Il!(?1^Yv1I>4K{~fKc;% z4mXkqYnxCg6ukS5k ziSZ^I#8p_?fs*YPBxzAfoIUZ3L)Q>cc;GmS9~<+alIGY zSQ>(2Emk2J;~GapV2X}0Q6(U1)YVI*F+;!fM^+6A;funUm|yii@_#|twpa*8C(J-y zqlC65iCP#GMpC;%O<&X$dHovW5b0B(es04I5q<#*u~zG^_87^aQXtTjrD+*#988ZO zb_5lGQbI8aq5${_Y)S~w#=<1_i#BgL5=*NQERe>ILfUhW%X`FFFS;XEw?G1}TANA-UG!=dm6$I5JOd_X&jpUD~IAyAuaIO`a~Zx|(nRm7FIGbqKYyAL`FPt52H*W9GfaD}PQ9CFsr;(D08cZaBYKac#HeP+Es#2>_>p%F**7Nrc z+oWwdJa8y3Y)V&jOt6bGf>NA^JjooQxuG$#wXU&#pv1q~K|#+dDkmoJCfuc$?GhY` zeg1kj2gp-K!rdm!+lDPkMFts((EmTqQRhckbgkp*;5`d&a(c!8YD6I?%uQb59%{YG6zgiR+0SqEc^60 zxW$+U9|5+re^vX`PxAX7E4FLr4mOf8ArORrpnc$ESOZTv%bL0=e+5_a71#;aj`Ts; z!Yyuk_pSJ3`ObW2t$>+e7ZW^^48*@;B+S@6u^X{)_Z&fy5Ae+79J7@UP=Hv(L*s zejF&SV-^Sb&v}ftYSj9JMoVwuXKNgS$c3aZ>w9juH*0E$u-+Ig zJ||qt!3WzKOOHTkTJo9{omgK9e zc4JkcB3nH9uoey&1BBlL;P1M^XfjTpmb=$Nyibq3+g6lvLMNF>l4z-*t!(;TOpZ%H zPF_BwQhHyN{{wb~;ceWfy;%l|52*NT^b`*~PPu+!pn!EFZ!f|NWe;*r^&|73%n(MS zE`8-bYx@d^#*y`S)82z$0vifX=dCN>ql`S$DIDxR3n4$yT9gOc0{6o||E$UlZz?R4 z&hUX%9&V@RInLjcCY-gdWfzyD-Hk0BQ>>S7XzRL2xDYe`pRKK387o8_3m9X2zVjpL zi`-o8RRFV&5HSa0ffk_hqr)Q*cTJTJ)?N<4&jTr0$rf`N^lf_|StcASPgm zSS`^%;JH?n_gat$0u>d%c``b?)i8dQ|Iylb8&y=R|Kl9{ICYGmoM{=cVs73cv^|mEP6Hyl--=q9P&JV(W;R~H+NW_ z1DL2ap0Q?Ym7z966GS3JD$z(O$UagmXryxFXVD`n2JtAzlntp~4ct!yw)6)H3n43i zsh<4l!#ti#5!sj3Ca-1StWx3?@zF6#xKma!smPQQKk#dd6y2)ZI#6UTg`w+B+5QL{ zb+$;=!?j#02&Ltk59N{?DrtrDU^|h$G!3tYP}xi`0tuWzQ&|=5*c)??wpctV$8yF! z>MPirostML1zDG2r!Ph}TT1;Jm-TiZEU;w!B}Rvz_fS*uMo<@5YGDznRNhxNGFcxw z?OMXHmMCgwAcaCKW)bwi*$2K}UB8|@a%pZI z9if}At)BT?SphV*pbu0R>C}0I>)KarW(vdsBCE*ruV;^^hRw>e{2o9~apnivG?)wC zFT9#-kAx@28hCnlT`35;O4-7eU%f_v6)(KUHmMMA;5e2LC2qROYOw{FsLlEOchu6n z{pF)@mN zyBRFe@W)uXqOs~$@0eF2d-5@1@|-5TR=OlInx?x51h!IBK)eISb}>12fk+B1jS2Vt z9U0vtE@B)^xru&&jFmu}i!mLzT^Vi!TSQIu9HTZUop~GSgNnoCt%ZUmDC7vZ8)bxz z>w&@s+Ww#$cJ@%shwPXQjRvcv#OboL{6^0h?dTGf%e$%7hnABKbrPpq4WWj7K95LI zX;>Reiz!LP;DdYa_R5t{f|D^LJuD^i5>}RN~wC3-~zU{uxXx zCXC;s>qf&aRQ4e=sh3dZ#Z%NMrlDRi2eqMO)UYqc>k(@5X>2Hkk_|PG`~FB^Ee9^SihKJpfwE0uQZzfJ31kM`n0(%GR(Gt;a7kxK7y}_$`C^T zXTTqipdZJ_jz=I9wSCKIN}+e}{+#H=e>#3PHot!xj4Pgv@os)0HeZN;UCV=fNImS5 z`u_f>^Nk1j9d6h8#8>0m`Dm<~tq?l>QV*SLg|ts2HB6KGqogIyf~BBxril^-(>#pa z=@hEG*@=p~5XnBQL_MW89_FF%uX66gwk1t*lBW0iRN)#+RM5s8YDt6U|0fWSdjws5HSM-cDxAKZse#gBn7YPKqzore@zu8<;m zt!Gq0JA@|<(2wErXxx!AV!*U|&rJ#Y9b&<)OFez}KY>rImDW}mw&N;CFgohkW-owz zxy0~1pYNCRg=84kl*Z+10&JY@Ow{!k$S4U`T z=D3JUQ~`z89?wxu3I7{>%7rFrg^U>BUTHFFNTHB97RUe*gz*?E!Wcvah$0b61+tMT z(ER({7KKlBdx_V*!X}mU#H^Qj$cZsYzMWW6PcJ8WQ|?Zw=^T^5phsen42HE^%}^C% zsG-p)Mz&&O=tiIWsEuz65z5sB0FWVqKx-oy*fDA4p`Van!gw^`Yw%pe(KG4_T4s-O z$P&_L84Ji5o=xL|j-}3uU=eM98Uy^(@KNsGpqaUd`pX~q21k885vrpb5Y)#MpWA0V zQsn9Av7}=U8Nb9nu=8$lw)6LPC-q37dwA|&n0ww-N1h-QgbLmA;rbQ%vL1Iwdq6}e zItW*=8SP&hbcLbPb%`f2x`U8^G(_+oC`qqn1QxdE#zk5zGx?+3Vf zg?&gwjVQ-PTw^Az!fZTS6Z~C1Bz@YgZ@dC_goxKmKH5a&Ue0w9I6r9pIc|J^h!vtlz|BR## z`Y^i!&I+9~WLCU=>GBfbAQ)yoshwe|7>;wM12-1joUNXzk$!|rLB8Tj)e6fXM&~qs zt*`fAo;$f?U_YC@$xWKca?7%3Hi2fX5cRn|_i7dI&WFOKq7JX#bo)e21fFCkqY?5y-Rs*Ez&AUgYrgBVc;v?4G9~cOf4f=osl0Xu- zd|yjF7p417Emu4~4!&VwH_pvOe8m7^M0(UMW5H1@7-K68tN31CD945SeIO}FA|ws) zfH$Pcd$hRanq`x0md(yC0?wG8FvLYM&x?2T=9*YT5KytDdRtBT47)ZOP0$^$o~7K& z`?^*`<-G;jplJ?x1VSueKn56?0b!F5W`w?D9LNWqgM%G| zs)r01tK^%BmG{;1f^Q1_Nj2SLGK-gcfWt`e?IeEa4&@Fw*w8j0GJI1o!yAm2@BJ|m zB>mtKDA>|aIHMDO@HoKFTOnP8M+Tm6DR>7u3myV8$3AX&^P2DGsRYTG*@N!mosN<_ zj46|*)o5$HvlH2W9kwRkS(Y3Za~Lph4g`RG&$3znx{cv!@A*2UPZx6-SOW&^`~Uzk z0|j?+;<=_R#39B~eINvfi*#}fkt*{uoC%6YLgkJEoIiK)X@M2j*b}6tUXyeN)k;X$ zw5C|Mln(o7XDeV%5^lJ9;5oQnMk9w@FQZ>qhdnPid?x&WMm%5$vNGWVGQa=~FwDUi zQ|LA)+y-NX&x_nfoN5aHK^P<_kmI7M3jYN%A*eIb>d1THbfmR$;QV`D^~4Df@rh-hca z?n^us^Gm1|u6C`JfVd%w<`9P+nyiE$34;{+ejg6zM^4o;= zuat1RfTCaYZlWS`vgZ#W##)bpq; zTW_bC^GE~0h+v7k<@4wP^0eiiXvbQ$+@h7?xn$j5DHRISBs(>VkHk7LkX`Cgg3biL z&p9I`N?tCzLJtu#*<`+48Y-jB*=lL)Jo&vD1&93d*2D`oR{EP}^eB_hm=d)&z74ep z8u=F|=|9wnHkt2-d?KW+AmO20O8EUY5xXVQ6jp#Xp7hKmtuH`vN5^L6dXm*bK*495 z_3P}yBU!yDu2Ta6*QqGh^(sqQLNu_gm%fGUu3V{J@N*Sv!&|lWYm)ek`2!)%Zw&5C z-)ZvA&pll(HBQHMhL!X(GtZDA8#6UHIzCGHEY7W=wAHU09K|}uSV_i^Ghjh&qc%;^+##mUqVgetX%v@yEK&gy0KoHQs>MtW&z3L@I z);b6_(Pgz2l%AHB#ryWfUMq@b*m(@tqfz))a3Ay5U&yKk2eALYJ&HYyKs0~>m0%K{ z(lP?ifHdnJWKGI~z5zoGhCmcRH0#g{DPjKtXZr{+f)3gcYHdfjXgAcHV5ensnRBTF zqG+EBH8bW+lFN)`myFfWU{8WSsD$gam|W--%5i`-AQ7Zbx4+qKZS{qb+{A*Nc;LKR zbJs6U3M%h%_Jlxd7}UPbseU#|sB6_uD3KK+tbhDKvqeIB4+Q%8t_(L@I048HDsul~ z8jl8zm73P9g$w`ZJl{A?6Vb8wG<%SgPovUf=jDOGdhCtX5)K(5 zx<@320*a!uG86TSkmdXRLh35c6g|QFeRmhm`0Cw9m=Im}@I#3w6sBHt>Fx!)%RNcw zqA5GI_e>TrToM{=sqt}eyK7fjV|69V;4IB8;z(UVWFcNBir5HAC~ja*nhTx+$DG-x zo*`rkqQ|9)$0ji8l0;QkJdTKYJB42$XA9*B89Ukip*J{M`d+*Dm79C@ziy;R?V_bY zF`5j*;y+znx((WSr>?80RW_-J4xk?Y^gmLioPzGmzeM_rV0-=EWVP3-;Ne6AoS}S`T&akTeN+(G=ZENd@KPU22v*MQp-uoMlR+G)$J6 zA=cI456=^|85G?qfGy%lfD-3P5&O~v2YD}W~|NiTO(UtUBT_9ep$97=Pl(@oiRjRzgz$QUU5(<9Z|M2T>YKm zi+E$oI$QJ>r2?`yJ+E;RJDF_>9B0V4&iyxRVzI)B6IlV`iZ2}PwvcHR>Ch1=;avZ4 zHkSoj_b6l=LL#9^)3TpUXRu+eA|7HoF%ya&#vE2(gh&^Th0w^^QTdSn;D{GybKpfx zGC>w7gCGkWf_yis1(GI22OjlfY$m5R=h8*(Fq;A2eAyhm;fX4cU6pM7lSW~PzxG8s ze3Bp&A(UgTUm$Px0?s40JOOVU=&YRH&RBt*6!;hzX%HeQ&fbx11) zhk^}3>nsVgKSfvsTYXGI=)a(1*qqzDIqXAs_v_`z+iyI9P^jN7)Nw0RgGTtlZ}j4~ z^+D06#o=Nz0i}~87vNfL>Zq*r*3(t-EU|tjv|U)ms}eEW``%-wAlY|j6egynCghWi zg2xM!6Hu3>7m+*oaYJP%_qb)lj>VIool(lomdyXCm&Lc!AW1PIrXBUiarmF6;oCA7 zZH&0>RBb*KgLg5Lw;9zZRH*+u7TJ44gZoV*a&>$>X64hkac~_DM&TTC1&eDF#D91s zshCotb`m^$sn25^y`+_Vv4<-}@JQ%$oprU!aD z@3%3cybs$`ZMIS%vMsL=CMMl+pHu1AaW_EF*BBvUbm)zY&rG1pHHjMkTN{gvE;SpZ(GLU~!OU;$)-2KPd^g9)`G& zZir5iA8UOu5J?x3r;(JifCC+-fhK8ntU!a9?D~2M*w-=4YJS2!zQfv=Qg4y|fpU^Z zb}1?VHuIg`0(a19hEs&c`e$}JmVcnv$L4PtoJiPHCR+0~N8Nv%n#a@*2!YdVKf?D^ z9Xr$UK;&jic7nyZl~WU&x3#2%O4Z}cj*UWu8V_)rDJc%SUNs2>^s0&kZeQ<-FJ#&N z`#lsP1OdS%HA=-W!Fj;zXgvbQ>xy?IRxkaQE#g2B3|{L)3dcb<4dSTl!j-~=VZnx> zVco6Uh#L!;R&{5(B~atHbuw*vwYZJuF_v_;Wv!#M+sY7|jm|g_H=Hq%6Q=gH3KKa= z18CQskb+5Zo*M`pO{qCXoeeO@oWNJGjhn>qff*+r?F`;oWvD$q^6cfk1Dey2JIYI#;GEw$_4w zFQGg%6h19D3@+sgIkN_QsO9Kww)BC)Eg|O{Qgz+oeBv*W3hf1gVn`LvMoD>_25Vn8 zFr9$J^I)l%T}6SOWw{2rQM70Pn_OPnK;SHR=ca)P^tQ>V8Q&c%wR6(d3LmM7V7tA8 z@=c6Prf-5PLGQy}17NQDR+tQU!}%|~p&kcw8(49?v+Ug2Rm@r^c?Nly!KU3cxuQzH zf)3w(oUz6;^{NnWn`;e=G72-h68zGLN+X$mcg` zST53-Um!Dw+SO+7;+uMB-}6$U|I4TO-{nF}+_>QKzhvv;7k@A0wEy848*-BuxO5Tb zviUfNFe3-Zr9>W!$1ciypIX!N289}B_Bku_ySHE&#&OCI##Jq%)onIN^rMD*NO3 zilqLxVW;CtCdjL#sz+e@H{Ef{Qw!hvCJWEoxy{OPTkK}Pt~??1&1F8e?X=4SDo?j| zE1;={NY(@B!Davnw%QRFBAHP1LKVi7L48)v$&vdc+J|5mZVf2-u^8+ZuNYIq2DBG zL~2Dp2^L@Z(~>RS z*jL$OTJrH1D|9K15hE91ZrINWZYhT9h3EN$4;7Hiuwx3f;EWl(cmH`1(+7`e2NsT8 zTq=kz`gC1ia&?0e6c+TFx`PZ)1rhYC;nX)^59Av!Gq0_ig+f*fu&tHqY@@Qlc%lMM z7r4g3B@`0(u)xctn?#Scox!}6+riMHtyQaR!kL&-tDg7=A-8*)_uEthZ@Lbm3 z!rKlS!)>OIHB~M_ad$?K_EME23@3hFZcRdikJ+wJ;z$lqjlU4NLFDFZ7*o9Xtu{Xh z#>`Cuo#$0Eg%)SKL#GaChp}T24W)XhUeAO37&Aw6x7q(+{kzwxIclCs zPKhdcnXV}=q$WRZ^VM~pTtYR zy8&e>V+!CFaJc_JA^F=X@haCS53Oz-Fx>z8AV!AULbHK~;H$lM+XTl6T13N(w%7NdA}1 zt+QXH1fmusOOaf#bh`(cPJJ=$ct%i>*hJG&$MH86y)Lgd>81#BrMPWkW?p%_++~9O zQYndGnY~tW79G7kHor1m(Y1)htTJ_Cr3QdEo1Te8Qe_dtSFn^_R_9*~U7^VNgvLpr zi5imwyLOxxVuwh4-dWL132&PEuwJ_U%}h%jfa>ntbIZ6} zT%DrpOu#s~rDArU3nuduSco$}YwhoU5{~{(x`JW_GOxf+P`YS(eifodJqgQ2Loo>Q zVWwTi;qR*w9E(mMi$4y?z?KIZ4euh&V#2yMXSkGOpnbibDy6SXil??qM-1@`yZX{v zmSjf0+JN*@6jEGw4+~H$pQb&Ba4tSW16C=W={Q*pt(SHIFQk42GZLJu%&uOzn^N%xA2e2Ts&R+CtM;`v_29u22lG?ZoRHN!h%@p=u$ za*TIH5?GyczW;1N$_6o~8{eB$y`Q6O7&e ze~rHx?dFiy{Uu8#DM_MXl#=C*d#*_?YAxkl1i9 zy@YSAS{u65S2`B`#LzH1}N&!mskx#T?Z{d#p;O4llH;TjoFw#R(WXP(T-N>;LQAv-so6eqtG_x3&UF zAm6#YlXt-$|GxMWl~;f${4FKAS8*8JepBO0`tYf~wq0K%_#%>h`+xj^a(VcINbGUCd?pIA+PHrq3OsRvG{>4_XHhzem^9VAi%)%$H)2fmx<7;vDzID ziaVBwq~}mFlTW4jdd>FBhD44*V)CR9^`nSjtEkgqn-P+9|1>a1(V6U87-e8?HLkR$ zQ0^O_kzh#(RhX#Ig8)tb1c9rihfxG~-ii^JepVranIS)F9#Pegih68mVCwCF99L{9 zMsN&|xv7?L)kPs<0c4b-k(dsHV&q=HC&|1FAi;bJNJ{>ry}3h%TDU?elpheI?mnDimt>SB)R>g(i)gg_dr0w~?r0(bRH3vak`9j)?!Rp%ZOcx(YUD_L;D7%H z$N&IFW~^uc02IKxw8HA66Kfp!S3j+%6}aMY91`*LY21LR>g^;0t&pr)H*NwGz(RX< zhfM^o?e4or!4}JwhjT5qE|$|rqW~;8(1 zQ^pE%Z9+~39~)`oUmNgGA@XT`eo^(+3Y&g9J3e8DGr%hcFRaDJlY4POQ=Xr4XDW$& z5M<1BS74^nbs&%X^|DwhS*!Y09gETjQmtli1o#}{gqJo;=L3=18}xw7g6E=>VSzFY z*IUUFe|=7r8P1imazkbH5DEAl!@DDanIA0r?+{QD@PcwH%L2c`2TD~>#3GBoNnQl} z5quIqR2!i3^82U>KR|ivmvGvB9xpsE`esk0rPY0Cf4#31 zZ*UJml~@ibRRKP^5!U`Igsr$3YA@ z@GfxJKC=+4iUGIU=cYwZ^qnI3`%|s&iH_PwQgN`~8G}^Xn}}FgzA7?%XTQ?2f{U5f zDh%4BM}@;C5x9STjPZ&7zfIT+yr)M`Pbzd86(Xnp$bdRm@v zglFz2*hmR7^}t0f?(@ausQG*vbt8!YI1<5Fe=<&~m}Se~kWLbx;{6?c_-t>^>&m$K zVVj@M9b=?3b-^E8az;CEXJ?lAT-@8BEwJv%N8U&i=4tGlY+AJ(q= zIK@dT_;TF@Gn?&lM6aTyy1pK6))lG3 zS5wV#V;TYPj~MCa@n=6=`nag0DRmb*c9+^BD_p=D|Es5|s>_lGNEs_|@2UW2-Re#@ z{n_K|q8Bb^)s*kfsvHnMf8=0Iv8$H<&2gz)udk&|ApP8X$4)oV!;=crlcCe)G*(8~ zi#4S$E@pD05~&qizc$SN^b_6-LJG)WeYj3z>#@6!=+kw}7Skls^(PN^&)_dyuvtDH zQdDXc@U;Z=a^;$7PJ#fMbZI5o zYMJ za?P1t;({zkI)2Ab=u!1)jtqPbPWUsQT`xP|HZnA!iWh&I9!| z9;nSJ_(i%;6WLI_5n%}ahwxetfZ@5V;~>A@VMBEp9}@1>3#>BG{cyA_u9x~epBh&b z*x-VUeL~}^RR9){y~tO=g6~~^lH@6O0>^F+J3^Gawf)=#nL4s1jMDo{PH=ZTT1*Z}_eQPt<^o-lo{xW#y*mFqfF;T19wREI=eiM4PcjMIF zDgp<+t6yF)=-j+nCV*8hAnvW3ad`rLfqMB10bw!RnD|+})m^&@m$lOw#FGUy#wMa+ z8GHXLGq!Z`U@3AJfx3n6z6TwKsUbcOGn>NCH!HWYeqNpsAJ znE%e0c{bjyMY{snc5Se-Z#ETrGgIq504y?3H<#W>i{YCh=b|N4&KqZJ6nRlzOBsaa zw{Sy!)f;SVEk4IyObDCl?oxQlzFV20wbu8nI53{;1NLU#dwq}n{bOCqbNKqtVNmVq zDlM&TW(xFKRjun8?Wif&i{u&t`_bGX zurkJkr%qQ}R8#RLm~P-s_|HO$Ovjh-7gk}lk9Gc+cq{=qQ*jB&*y~({|D}}sU4mX{ zh1Ocy>}rcHqHBTmtb4f&l>aQk{gic^XGdGG#$K&~w_DW{Uyaksj)837;v!tPyVkn5 z%6aYogR;ck8^!Dq+(gvt(i1P;R^hHjnBLA(V{iutc)`fB9@Rrfa~Y3Du{QR< za(w!oCb#PMUQPGbVM<^6gB;()(f{4m?h>5^=f}rZ^m)#dtM~1r?_O#Y66EX-1YU{0 z>qH4ordv0xj$2n$kO}I)`eEux@6&xVnO}w6!F>KJB4sV0aNKjecSVwRU=!Nz6g6uH zP2}=c`!(yw0V7si|56Y%en7|XMQ~X~bsfl;gezYQbKv`G#WV!KJ@KNFcR< z*t%K$Y9HjgSFo4~%)d#5fFV~4+Gp>i8bD0iPW;{`t`2KJUU!NsUrlu2M=G}z26xQw zjxZCnZ0GB3>+Qa;#})W1D558vs3Q}uzI5rbuaTZyuRTPz8aom1{0Q(=X!ihGAWmi7 zdt68q?pVaUPPs{93JUl5CJMbLvIOuS6<7I$E|RCQ;0U9ix(*GF>`-`5A>>j05lH`z zW|II?FB%n*It8t-l_`~_T}C+AkaQ1*Ym}Q zqw9Rl6nL0Jx$({M7pBLkW3Jy_hXqQ%nDhF($B@m9747JV(emvWcZGs`8;R~3_>Amx z8T<^1Y#c0Em$upI1;XkKH;UQGlg7pA-zkjVAKIabwZhj)l#u~DU_ z6Ug1#bvc-L_EVFO!N@p@X8cPvsZUhaC z?|>%qpy*XCKOnmzBZ^O~2wb#E{ix_idq0oPfueVPw0@DJv1Dx9f%}jbL!18FI>gKi zS5vflfv#9mvhb-yUnaa6P=gY&tHvBtQ>=;joRA(VZRrw|*;wRm^&bK5bv)OLArU3i zCI_e&)857up3C%`AcHCNV@lywQ;a13cjbQzO5VB`#$2n*&$0%5gQt`htHu+m0$E7m z8&ha4nQagK65#mk13c7C`?(WWQAubNYgQ;?yd~S3@Gl{uUKQ4 z^v1znIo%m*eoWn$HWzPH%j@=I|GWb8zRSBy``p3tYHSj{?svz;zN;N~6VcK|-_V+-?D zHjK3m3)PiTH*yeD3i>H|9oW}GE3CS1Swv1Mz z*dx5xHD)mx?}-W}qFC)7QRccfKE~Bm)n2_Fj48Wmh;a=;@QPyLvwLyg5=vYV=u zgqsgl$@gA$MejBe-ZT@t3%0co+kk|GZ|kdT3W1FIw`=tMzK-*4cU^nQnsUbMt~-5C zs#B@m`D#fXF@az-smfyk{Ch2fs2er*g57K)J>8kCLEU{{F4S0>yzZF<$ec`6rVOsJ z|MB(Hopknh0zr%3{z)wSt1Inm$mleku5j(%h3m?Y-Hw^AWb7c#Q{Ya%?<+-I+8ov| z2NZGlOYgJ+z+Va|fC7d4*o@|f6zK(5!?#|4iocD$>637Z^iG0GnxY*+9b0^BF|Yi| zoPU|$=j$P$uv}Q#_%-fT)pKb{zI^R1+9DR5^U!BO*hpN9|F10CZD6g2z#7dm&5*7(GmroFG~S}uH&T=Fzx zkhMFs!V>1jQfe#{o>5FDl&Mpaiey#c`uEdBJ+~?{ZYe2?Cb4q<)0^oE8;7hkkwMl@ zJtx+bls}aV{-6jcjh>bc3ewRFmN}>7f2fmb5=dlMJW`7-XW;Y08a} z)GtV-1&}?u#QA2MdDde?&Unrgo@r=&+I&90EIV`SYAgKuu$C=Cbjy{27%wPPsK9+? zCex?AS3}QEpj+HQ6m)eIrXK4@5|u^dF=N|U^g*F)qvfue;?mIw)jM|3~9HG}C+1oK~@(^mLYN!c^D}iRfgTO|xu- zT$pOtNrDu#Uxam?7nYU<%2rgC(76(Ify)J&4K1-nZQkCn3NyB4YBNQyGKOvcat#vN zG2Rr%(=xJlZ_E8>5sQk{ebzc98d@aO4bNB&2xbaXxbw8}i2K8|Qh`k5v+Sz}o{fl( zK&J?+D_nXk)~jghu#s2jWeO?!>GUd%vwqQX*&w)~4#m?rN-Zkub;`spatzCq=|!UG zR4fj$W)7*!)@D9bA!k&(@nz+ul(vn)^*|n|gU-AyQRr-KYwQ=>n>h0ZI5QXjZUhiz zeHuv->!qTl)mCdh%XASViaE3NFMgPkVQ6<{NM$~fcb;smwi?XAJn=mZFaqTHktB@mEu zWKUTT*i6M6*NmllCG;5+q!;LG@({y_NU08fD+0CR#T zK@fturV}R+m~x;_r9gqos6A^zYIEBY76dleKEWWw+}K*|XyE@ZZIDJlw$|^6A2{H& z{+|y!1l$Qn=yJ55Zs8PY51vFE?~ze6kGn&-sl3jAME)$~&;ESs`Hx|alr)kK9E+;? zEnn*)cnc~3;Bbejf8YLU1_c|Fc?D#epiKFW&jQxhCHj@rQ)jZ0*lX;>=O#@~|LX%7v2ai|~AAlc^ z=4I7DDcs)-&GaPjU!OQDEC7mPA~6EVe=FQT9N<{aR{6*;SCuF-Ff|_HYY1^FnhPsL-r8%?g zyt~SInkU!P5KGZ8NtHHPtmlKW@1y=C{y-xZFj0x2qDVB$PLwjmT4o2y0-iP1+*K}< z@4l5kAIAOh=Z zeDK-UVIG8r@%bNTG{^rHd}tKf@mfYbiVj&EXOp=D8S{?5SHv%W*x$#w5Sz z;m3@LUMA52nj@p^N}RN2c)=kaHZ_$f$y1smmvDJp>!fgX60n5id?ixJBRQ7bHwQL^ z4@>Ra&5D&R5iW-EVW(j#Ml<0`ek(#%a-#sxoFXEsT^)$W4cPWg6JfXiFJk-ahX*GjE5t6u|;N~st%y^)*wiRF`=y$mI0MB#HCU3fdubD8mpjD4+rgpnwV}fC8-j81RM&>83yd z6i@*LvqMkr=?G6@kVE%ToN|4D1_u`nZ2YrKAj?t18PSbHuu47MHv~1pk!^fsLK@__ zlA#*V>98^Bea>ovT<$&S!{0r@OSH2yqCqmuPreD#v?Mt!L;BH*Z!>QZ@M_+E zT=P8i34-u%iP4-WM+Z)5QIt%>iLkGsN(1xI!$>_6 z#$*Hm%TO7wO#w`Y2ovVsa8@3yJr-9bmG}#uX|Jk=000mW z0{{g8GE-DV003W9m?~QZS{f$s{Yl^58+#yeHV8>10HHVc0UP zfJo2)4b6ZV6aa<-e1(gg*qPr}Ix%}w?W)3Tn7ccSOvb{yqMEt?nZ12~5b>hS@Pq;d*Gf(Sro$l= zS)(?_O(=DPecpG12lSx7H!dES*dNCSk?wci8yQ^b=ksyzz8i1ij=SsbqT^TTm&oKV znQpp$y}EeA5@#RcQxSqD94@;hcb8HT@UA!c!D^VmOwOFoa~={HYLE_6re@Y46QYbP zLY63D6%j}F;l+6Hot%^RP}J*=`w~U>!^H2FL?eWKG{b1EKurU^u|; z?0(E94rMrT&KuGde{w$f_9TrDUqe>LragCI*L}!!cjv(?^5iJ5xl8-X>dpdIW8;(W zXWc3{^8VqAF2<)CU4O7JCohu7;hCK^ap#rkUWg9n-{jCgZN`!J!SO3Nf936$(;vSz z4*!k&F8+HT^mW&jCE@F@sZ4rwdBfSnZi=_#C*|l z2lYj+Bl9V0U3tvUJ-z|n%}>#o?3sLwonO` zTHejXy7bj_7Z*gC*9}KUlVcx0}HK zT+|{y@i@pM>kgvx4Zr+%@v;(|{rl~sRC<4M|Eb!aY6|VNM%Bl+Ez#bA$alY=OFV~$ zE%Ki}uIjwHjLyG?e2$Dt7oGc&quY8gD^Ly-@PU0f;}%txJse#yXBSoM40?a@&qA~k zj@H}9iYmanJfAkJbVFhERCMnh{nEi6)&J|$>4~&NbWT-T(5^T1>Gr*qZXM(O>cQz0 zJzbc%ogd+A{A$hDCk)n&?$LR#eSKS}*FV1dFgh>#^%J@06;X+&9GTtde_r2W@e^J5 zq@y$9;@Bv9<8)6ay`Z|F?IbbfB984E%2{VPsgpFb5FuAbt$=pe9(s;?- zNsiN)%p4|1HYS-Pa>wy;?zkam$jRi!(`x?1Zl`Tc+zSh9c>exxjc?W0|3gWB*f;Ph zS8`;^`$xojbxb#VEm-Ti+Qj18FR^{`TeuznNRE)!&mtQ;{-!00BG=1QuiFZAnU{Uc zKkJ>}``68*J|e&5f71#v^1Zu%fAqQQq=TeOIl{enFLfmJpFj6Ub-)Pxo815SKlkMH z7%6>LhhfM1Ay*3?+$qxK{SYpDA%wzkLB?%B2!?@DEiA|ZtXUZR(h^)zgfq)7?K%$h<6!HCZHV*G!)4oYR zqhh=mFezMkVwMpk2mC?eO2ZL`V#tIbnP3l=MYy3xWp8j{^mh+wYOp3|sP7iP6ILF_ znNp7EJ(-w_M8M~Bln}$oDNG~bkxG(CENt|t;s=BIdEwuJf%*BO?wj)#kV93dkIwlh z;2dkqh}JlO5u43)@cZOsNb3o*bon)96APYijxQIv) zQ!`H7=Y-we*@!Y8yXiESzRBZ$Gpi8_^}-3|5qp?^mmroN*<{k=zu#}@>lYJe^Dfiw ztuvPW&;GQf+m&|&bgkz3mPY!xX($s=4WG}!|E`Z+iw>oLogN3#pqLtEHprz! zr99#xv|%6zE!yh?VKD#sx%h{yUh+bD)^|>vR&Z8oAdH zI5gFDfp#oQ34P~vkl;L&nzO~8Q7}*)01<*F{D~dZ9uD_@w_mkGq_xe_@I(EzJw;P5 z#c|(Z2Ko8EFRAbOMyb6HOazorP!)L6UIG11Wi73xRB#mE$n@xSevh=-4HcLra#pi;=J(oE*$Ue#_#Z$Jc{d*S?-RR|AIy)>xdq^;ROxrF; z$wHx!^{H-M{bUGGt@`)SV87O0z2^#`6ELcVku|%=ft8pUI~FSs$Whb=re!c-4`*Af z8dFxIk!>asLrN>>Zq>oDLGux-iNCwP_1>7f!g>`R7BqJfV4?-WQG^5`eidj+NCc`- zt7@?b9qIiuS!l|e>;vs6R&j7R!{H8xI~>>lPSGoAXgn!jB>Z<32}&3X1!@W z&n-sCN6mPBH(0xL@?o*R5O5s~s)i$&GssGNRzYG4q>Px@Bi8Ou@^8K4&-w+Mq%W_{ z2*%N-czYa^pmtReL=4gfS^!*F5quFb^qLz%S4+G~?>go$idZX8k9o*=KlS7hL{an{ zlat$+JuV^-VKWN1{8g5zigS-PhR_K8YTx&i7V~=w3lLFMw4lQ&xMsehfd|*dPwm>@ zCg90dX!2ybM0}rca)Q{wM6EEUqmvbe%Gj+IyKXD0;KwM`l!#B#H$9EL7h8q__7{(* zUp@_Tb_q1{*?679ibGTQwA|Dnvht&&8e$qG)6-(|6-Z(>TIcIm1I~6q#pq67f16cv zK9;PCU8;qQW@FgcYcOWGIJEO^r`Mb++H&G-gt2KyKe?hdX|S|;YiZ6=8*(i*{KHx* ziT2ps^mI*z8i)EG<5{u0mFl!O`k&f%+9ZPQFWsG12mh&mQ}BKyJ%TO!JKjN}A{*I% zEk@60ux%z|nx{va=4ucqDb@qpHMOZ~0FG>`iAblb@~N&<9E+DdPrFDVs`<*e3ZEkY zlKGO~H6NuV4#%##QsNezy;bgo#R4Qt$T5>#siX82*XXPMxF4D||Mb)^8WBCahHb9oOiz0OS&mvYIgQb9dfpp;rx)mjMC z2#;L-Q``f_(oZBV7a#`qlL(2%I}@$5eVe zUd_t-TM*(GRnKIXuLgdi%M4Y=o@A}cu|E6W+KY$)%Y^}{dXT5HoBtO-3lj$mW)8T= zWxZYVc#l8p1u$8Ie`NxXmo-0GIh*TM$WM6KCB2l<4V*c9iswLC;~tgIZnf#BQtc$- z3vuONBx_Pgg{SK7jdM+pTeJ00h^<{Ck}w zr)97HYK>VoXx5f8yM9=kQw@5!+t2KD-vc=Da563T=>?5hBj^X26MKCfigz?VwR^yH zgVpz^z}(drlZ(dJ@%pf~B+}hg_Jvt`6aD=4v_+bV<#lwsQKw>$k#G;`hM{YH+Im8?8}^h~SdWl1*c`&v)I`<&(C#z*5$n;` z{&c?F`;tt4dJXF&ZLP9}q4u?1#;c$n;_I?R;YP!vPI2`YA zxcPF;Ew?3aq}l&1*i1d9xa<=DMpD@ZTLOH5G%IBAPgfnpwQImbX-+huLv+mM{to2If7GPa?1?b~wu%%8E3c(QD7 zpj2%-$&H2f*l;R#u6A$+JbWKKX?ORzuMBCf!Av@)Vfzl=cCZk~TMz;|>CPgB2*9Eh z<#e_MO$$cI!(k}<(DXoQlUvuZ;7Ux|NjYq?AF+0#ZX0e@3B8k&?Exy6Joc{i!4gDm z3m=+hm%>7}nX>u5BeLp5rs=@gcI?t7r?8}GU%Q1gZKZFx0x*iT*x5x#l)mt`9$QyB zEuqQMo-VGm)ZV9~-aLRB%K{%AK#H99vBzEMEjhQH?QLuz{#F=1M>c7H^U2fx!kY+b zVuR^=OZ*tSdMv>CU5~S+$B4cUC|cL5&pq4F4Az^5T57zS8u|H|-6ou9^OjaHz2~&F zPeIZa2Ps^#^Ohf*v#G$#!_V!K-7i-DmOY&M50S{S?lU+*tMv`UKc5Ud=3Verbucq@ zdO~=`HfnvPXFs2si?Bv%t;PlRc(~xz?PlqHA;BSN1BpQ%@mQ5*a=hKQ+N>g0la)g@ zS1ZiSPtZ1D*vs1*2HJTWOyjxD`i9m(Y2v?HH6zKq6@VeA^nvlr8ml>LC35)ve2d!0 zoY?IRn`V3GZxwl8xDhESj#fSyN&5of=>L(@H18O7<7^p>|)XCzEVr zw2c#bik{w|V$-EtpW*x5(j0YWS|?h~9hRS{Kmx9dpW;=U7-sj2%@qvW;Vg&491d|f zBCdb;`g;8#f;EojI2`kLjd|sGJNcR0Hv8-%{S-l@eUTiI*jB=}2FXhZ6#)Tq+3;|T zCf+N~mJ}dkgp2v#MC@m%cN%X;A+V4MWL;HXaJ@SZ-o_ zU?D%Df5D(_+*?7NA-!!S8{#PAkubFXz6{L+@a$yY167H#eD-ML2^a~vb36B7aKJ3> zWWT%TWwEo2!A+QBcQ#hEfKOXI5uqz!G)#~zPov={4inj9lOXWzMZM%z8^Y0lH4w~;?+w2L+eGLVxu z$F`rY|51kp1&#;&({0NcOko%y0X(gv}ZHc|47r6~Ut zQAbL4;fBA%5Uk;Fhr{7ojl-XhX|O-8a5Tf=Tfb}EFW1J(ve>_%p0Xsus<13vXB$Hc zbp%8h4Kv08hotIZvV8WcJM~u?AhgQNgI#EX5N0Pzu2&#Ww9#*n4z$5OO<) zAQL_yG^c7NA<}TD>Wn3V!VHa)d_otltzqYFXrwn^$grOLJmNRqY1*QpFrb-F+_u!bv3= zfpkcz=&03{Nej_}f37;J00V&jV0^gobD7+kFxXxffi}oz^>i8lRsFYw?cAq_@T5Y* z*Gy#bPKh`i;c$n;91i0ZoT0n(QyM9kD!fT(+#1I<8mOT_DTFv8|8P8W zJ9i}3R7Mff%7nn78>KE4#*k^0lD-ba|ut|K;In>-tPP~L=bb_qDqjNpumK!1W6CG z7DVH6P@}UApSckyb+_*tGtmsBFmoVhSO!S*4jjK~LY%0^HW=m^eQi4ti>(sQ}(&n&>fg0KZUKLa0Do0#;l zR=4q*G5_*gwXQ^vp(ucXOCfw!NaCD_Y8xniZyO&&Ysw(>{pbJabM8cu$>wCt1R?p| zo(zR1$uc64F`9=dp4=ZrOTgm4WbRax2?hFx=#wx{V9f=esg*L(k&N7rCZ%Mm_BcK& zirw6)H<2U*DJvyk1C5If5>uR{>#$BSMwo^z2k5zvP3l&<%^zUnOyc6zJ#<)Bl^aru z11*5TJ4??WkQR{invtEf#3i@Hipl=2=-lk1h#)NCQG*R)C@s+tA;bn}P-rDon;U&p z_?33+(C*3#tu+z|KTAX>9mH^zb91Yqy>2;&HrLi{>QOmY5L#i@UHYVj03(WHq!)q6 zR4_f65kaXm^$3(3`QDom^Cs1n1Km*mgB5$ciH~SaK+cKhpe{ZyFo`1uf|#HxyUr7- z3DxqvsmtaVt517rRbj7V^(eS%285ylvT};u(J>@G6=$&~30DP6S{Z4%E^U)U1!P#K zm{}``PQtuQKdA-+{D+@|cy7O@vp=PPfBc2a0HQw@u!{I|{C}e4Kg7Uk&xMP}5tF-e zCF^Fek~#Zmn^MHjsi1x+c~;HMT!c=E^DQ&`QUTidyOu*~CH-_5FmtYGXA}~)>JSziov2nb>0$c#joi?aPW90$VxCau zQf~s&pco<$Oh#AJ&`vHPI2_?{hr=8W{c8?plD<*Lc~pm^{|>P)oUbLk%UJ>S24=BV zJ1ZS*o073P0|9|Lq&~BV>QDH8%?}>sy+MS{0mEXDBPIzV#jA`(gT8FqLQyp;%m!cH z&w$$5DIiFtg_Izom`0KU)DBwdo6q{Q03C@fp8${`jsyhuo=5;!QHH@Rs~twi?~s9+ zGI`GStL4xylL@sf0@=RyRe+GJ#6nV1p*0F?jm8HOtYEdeym+raRP_I!8;jn<%v??T zc7aT;SHJ3;pgc0I~Z z5Rv8T8-^j3`?BT^X(;ql5F!NO9R(6mB27af2SOkNXvIXXoQD1UK-)$UF6T>5J%*IW z!5%3b3dr;q{EwGN7;wY&4u?1#;c$}0ySB6aU8PynXl}#dx4zf-Tkf>{d+;JmuIXe@ z+dc30ee2(OJF-Hb17mgGz#bWCh7ukvHuqM*Q&0QJl28%{H-lQ9@B3dZlCQoj-aAeX ziT5>UMeHC%1Q<&syO&pR6f%K`7-_BU~Fgh7BOLy1{H zi^%p2F`<#LLj*|}XK8CsJ@KoF^&EBrGd2PaIX9GN)yG(( zpypGErjYxEj4CR23>7&6DRwDlZZw`@lu``wfG_N^&kBHw36lEUv|&4^CwH$cqRUz? zU>wjC5Is||C_q2t7i~DA=HCEk5jbFSdE-mhDy;#(%f{L>NdQaGM7B41tGkLve z1Y+l30!X0}r59MF2Adt3Qz4n`9=b_JWTN0h}+Q!?JRfEMh-skLnU>w$}#e$mGPa zZ6(^&zZRV)yu#t*&r%`JPZyqNMTOYvk9TQ{*)?ob>P&;&{ zl+?Ce*VVUH^Q{8FWVTT~kQh#}xC&@FfS&9ZB*LxR;P|BsFg-@9Ymk8`6V_t1ic$;} z3^DZ}z@}P$7~Wia{?k>JF%i)Wt0NgF5F&`l<}5)XPlcpv>Hl{T!Nt+vXXgA;)&s__ zffy}?5Q##T8sf>8%=8@{QikxuOWYyQ6w%d}TZ*0XNo_nv`utd@6P^9%%vu?|o08wk z+$T!UgCI^_iBSqi$^<6U4boU&RJMt8OWpA^?k{#>@J9%UCdbN$XQxeGqnmJAS}}D% zu03{9&P*_GrLaEyoeq5EutfLkpz6!HGA&Sj!4zPKdKLaEof_#oy(8U<C|35V&k1jj z1iP*qR!z~7eU-Pf9Byzp(cu7h5D)_Z1OP)r1SS9gPh~&V7Xd0Q6a1Z-Zr*LuIKwCg` ztAiG7;?`Rx78+_>ahE37vVA*OlI`wX2g&X;IkK|7a8(Q0>vS+-j@&xEJV>TCGMbd2 zs_Z^a70avB(-Tgtm1(r31wtT#;u`=d005yOGnyNKldO~OB=7Nhz3$16w>f+xP4wiq zHjeg~s2xxNU@<`SMk+8=X3H{CpQ$Rs6+#NJs-RRE#a8`5`f=u-`u>;U#On9NW?SK6 zyWwU6-P@J>a9odP6`_HFF@qda+uk#Se}z2XDoouMx$gcd=jXtL7xyqzS@9P$*q7&? z{kJ*m>sKqY{q7dE&H)c*zrNq)3^`EVDxTbRa7DSW4aV)?c7E|OZ}Fe!HnAJ~eD;fr z=H>xA*jPMxZ84X=&om`o{sHFr#Xsg~5hmhPkrF8tB z3|!yD;9$m4qpE)0{@lkCv<<7gzq!)CpPl{VGUoZPlg+5~=sVKN%&b6=Kj9ijpWpR; z-lLu?y5eyv;(YNs1=j>JcFavdH-WGnpi{?A6m%2#-|=1rufXqdu3!rckAs52ps(0h z(D(dR{|cucC$Y$d7A^`uzzSf2;A38DyvgUm@D=cJDQPIo78VyiM>&DR<6*(!kXTGC zNPJ`}W(vqW7AuPdi;rUku|V;$uAmDXkB8L5hnt7~{@mYoac>7R;|ku5_TDgQ5VvGz zt4X%vZfM0M;NRb~ujO>m+-G86MqE?JakKe?{pG{`_Hqd?58mT8?-TN71jan}vGaep zqTX;^rtf@kz!v!TyqVye{kz+_{Qi=BXDZl+cQ{iHJ~;O$M;@*59l=~e753oA>+<() z9+Ho50n_MBw}<$h^dIs+Q)E86x4m(3v*MZgf7`DmPM+x-d6o8nW?l*_pz^nv=6k3< zQ%2=j$%%j&u%QBI-pGkw5TTZzzasvHgZ6XXb2n$=Bh7i+`9W{shSe3W1u6qog?TUG zJCIIy=0=Mf2UVnEKKL8|gJzs%~xx8H8FE2&^sMV&y)1JF9-;Uw8 z_T<6MapH4t+a>;TxA)ldnf{fZF@ zLjSq%bKgyAy1U?dUt$M!n7P}_d%yY@S0sN7TSN!G#BT@w!Uy&Lc{?kfPg#vQ5c;`% zGVyPjbKOXvlT#r4-1uVZk9X4vha`63-`y4iuC8untbBXhF21%CU*6?Uc&6^3!5QP# zLtR&Q&3h!e&PD%#x2LY|`F7!qX<-+}&VO$ues@0UEi2O>8k)-Y9Q*Fyw`x#csUO4V z#ysOpzkC0!xVVn=kDGG9^M23orl}$O(cgCKs?0Kj4RoCU#bE{O^i;Hp&KzHZkH&s} zF*k=9g?-{P_msxU#8+V+z8BP{@p5l_iG9`7N&Keb#oLj<-*@wX3Xsc4Z_S;gK5nnf zA^eYdfZwm*+a9WY%ItOAey#XxI9RUEuDS31+u!2y-((M+8-w#LKa#(T_MQH&zDjDH zX1d~Uc`prQ?YAndAHlEV<*v=&_Cq^-qK}LouSEVcNyTdbROE@-+HTeANdw$!1Kxr|MGe8o%2Y&fH?>Gg!u;Y&)b@x zcA%%lj;D{ihoo=JaM*p@Pm~{w9D*DE!+!9G-hR=S(ChkvCl7>bPaa0iA6|a!2ZL7X zMbdu%o8A!gk6>j__XFMbLa0Tjx}`#pho)PzH7en`d^tLJuFz z7sB~&xu1>eFiZUvsQB^O%*9lo0dixfC%Gb%HAae1BS4fD{buOSXBMwo#JmftaT3s~ zBmNYjL`tYA&YGhfQzGU)R9MxI_p$_ObDhUv7SR`(fLOev73}m zw|r$(N(cu;!2>XwIcxy;$$qP9o>fKc z7LXxNm!M!&7+Mw7$&7(Y3&(?^RO|_P#wM&>Jp=F*&ZDU^)wkL#4pdx7^rTrh8OsTy z+76nd$kRg_V)-XGJ)(yxSHk}mmvE)6kPe#gopVHw21XZ0{G>F5Ea9*)S2=~3{ z3sD)qy7+_V^ZxF{GY1%H#jQuP>fBdi`-%H; zP@XQ?zu6wlFyqf#G#I>$u;}G;!yCU>=5?$u|2jCB!{~94nT!19u&S&vKNwfFk zF#ZpDeRq9#nv(1BpU>Rv2!3O(JP9cnyR&IRpvQ(Soc>w8`$mpY;DgQ{;J${TtYTfNUH2Q9&+qJ zTx!aLQNprRoHdnTD5~qbYM`!A!os4&+t>7=Nz=tBiBGLMbpaM~3!Sv7JQWEwzgK1E zei&C6X(oqTUsD^WBr{m!nU{K61*!YhAT*m%Q1`U17jbtbuw?f2uT4ca>0MBpaHU0K z?;d*lhbqOI?wMOntjr0!eK|9M<0~U29TY)e^SMtPzP3xFAru^GHjRgX_Pg$vQT!MLpw>?ut3B zte#XU0M#TWxw_?R^~?-@Gz(URa7kDP{I~Z6T&=CTxh3ctroF*eR}bSKUA&%PjDUqY z0L~h$BL;~|jcWQ9xxaxJrcKe483I=mI!>wFj*3zX5q&mId-fUp|^ydYb*q=|%Q6*QYkIPHC&tp!GaS$+R-4AiiW=NdMsf z*X^2xbUzE9O9$>p^)){{ev228l3leNdhREJ<%!FKDufqe{#aam;H&hCvo#`SJl zXewj6a(%GAFghTC0yWHE;2HT2KvR`w1Q!lTb_5eqR{T6aGsL|aA}(i)0>%;`i)D*=}){11p0o^LzL|R-dJBZK~Il6UuE* zG8JVkMc`WJzYzBy9zHvAu(0Y(onx0E(H3OOwv8^^wr$(CZQFKLmu=g&ZQGuH>&?71 z@eA@puDF@!#y(;HwG!&|^Pncc-q^rglqH8wER^Hsa?Y$Fnnk*S6pVwkVVHrB*0fZY zY5_0@J6n|WfbSv%bSRnR^QmiL&h9oCBUv}Z|L0w~@z^4C5!&PDla6-Ia)`SJ^u$*T z0(MDsJ~GA@VjKAy2hbF+L`j*l4eYw7!ceM746F0;`RDP($%p0ks)~02Ga3^+Ib4bo z7-s!rP;?|mRrZ}b8E!iKf14jXbMQ^fa{Q{30SLb^t0v{Bf(ZPb04 zp=4=0j@Rqz1MtR7^jZGeaV7SK&Lgnq?cxVur|hgxZIQvx;an*IpQvUW@ge?%ku0CT zN%``P>D=LQc;60vrn{GmOVnJe$-syL97=(mwmNzxi#N4*a#Vx`WqFf)V#Nz1=$bb_ zBO1;21okrM6}jUYX83G2`!UCrUKZJg25&U8kvhsR_Y&90Hceez4vfuu8&C{LM4%2F(`5#ySYK^4Usx09{vM)f5fti$vP zR*>+*!1-MvzZm@TLkVb;@=WwarKcFW(Pw-yQhsFF zul$753l&D^&{#|~Ha(nc%h?&L*ee=-b^4Lb@k+5uCXE6@#U%}u1JGARr*8&x?Yw&| zwNI2zk|maI#uNhi`-NtjzlB$1tZHy%m4v{_@)`QKb~9a$6yAs$`f?nS`2%$87&Qw} zw^eG{Rar%E`t{YNm01-oslnBCAsGvv11d?dII=(#DO*|D??Ue+{iy){VY}jC^u|YW z|M(>Hx9iDO$g)Mk2X_>y`PiWqQNAc@tYj3_5a^X$sI zSniP$%KnEG;MVq~WjQ9|WS^;USssz{+md!$&j!jGrEFx3b`wi1AAQ(gpjwnBm`~ik4Neci%q%FfDNZkC6qj>O z-_G+sI+t~uOC1UhqZ z4X>2<&ZF+>DWy#GEC(lEieSU!@s?UvZvL^uq}WVEm^-%od_q8INAt)0TLj z)ZA|+WRii!-Q7*75lrcdxEke-fz0sA1D$1?i0nx(%J{A|M)e!Qq)OJjuHl`1DJ5C2-Qwks;f zuaPtp9YbnXEXm*;U8VUWY}FK@;!S&{|HwjBq%hBG-`f6h@q>fU>%;!S@jmJzjQD-@ zSWj`XYo2xGX3FwuaJ5g9!CVh-S~O`;wBcAW7HemLkHhBMJDC7!s<_$o%DGk+T^$0p z4k}h;iN3&(vcjS+k4bBY$^dBEM2=&SwY@5IEDou8 z=j9%-Mb61Y32A;sYN_|ZuVZPd{_47IzdaoI$omtAON&z!EHA8@Y9}fw1U4LS<%Met z|BFU+lyq{J+lK9C7t`QD8B0(gQX2|Pia1qaL9qVPQXr|HE9MD(8P%p#Zv#}DXR^B+W~9#f&=i0I!a|k$;NfwD+?qwZZ(Xa z55|$v7119Owe2{41VHP9`lebWHw@$u$X|&!2F2v5;>>4Z4q%+~Znv%M_Ta{$GgBmp zZHb$eRCL+Veu2ZeKv6%|FmCQO3+L2{mdnf9EHNByy>e4*UhtA+^^K8zH9e0zQ@e5H zC^Y_Wou^i=T=_)#ud`={-hh@Wzhr#p;;!g(!{uQ|_rOQr694}s#J}=6HGp%Gy!b&F zmjTWQ?RvpkCAYLXog2N3J-O}cx2f~MF=YeZ(Mb2~)zbhwDO=dx-q}&qGM%cf!eAfZ zhuMpp~S!EI}X2p{7SIIqRQjc>Kz+V7%fVGtu&GgXG9j}QE zx(sOSrN8CtU8M9+a^pm5=9>YR(Kw*)2GYdwVHS^94bE@tIX)Y^^-n$iEX%!4yiEbzwI&w>jsoo^37wg?RDD2@*XYuu8Zla#;jEz>+TFd#X9`0>HyGe_J z=QM|cf$c`ei~hAGHOeR1h=t?16rP>P14-ZEnj^VPb~fBhD?xtBsWsy8OsHI|?FMPb z`uqY4BaC>vxkC;(gB_QUwD>97Vk7X;tAy+h_6I~ba*V&c4UC$!JY%4lt7E=@F4WtW zy?;yH1vgBeI#o(2(;zEqitv$Sas(a%V1G>)o>vn&36W*A*;oxdBbSB{U{!+$qkqiY zd|0s+KE(RGOf7JXh4fBGAg-cy0Yb7S1eDSsndGSTcnMefHL4U0ZKS9GFArEbj);{E z5KL4PB$l;c6JHIFM0z9=hKa>~UAj0sM5KdnwH51!TVzgyl8wm*NAKWp*k{g7{wxF} z&7Z|;?r)0n-%>b^wq6K^?3%_~dRC*&d=Xez0Ucri+x<8K$Ppsh0til~0dFo=js3*k zu^aQbreDl3PJ}|x?upryH87}4oNX9Q4InX;U~|eq|J-;e=A6H>j+z<=d z+uWo2;boLZz9Wf-a)dR4hwrO7=tyl3*@guG8-ITKaz!fP)1ee(iKOWjt~e&)5~RUw z07H0OMZoV%BrxQ5=J)aFkYM|OFI6{ARKbUN62DyySs2gltv;LNuctnLPbFfdPI+V zX9W$;x&wl7;IL4q?LZ_xJmgb z1QoHVxeagjsQ+0R+?8CS<{zm zNDnq6IEE+pJeOgw!%xTb4OkZmWLT~)1h?744U3?K^;*VxfQr>!Bh#L{rg3OJY_vk=aS-=mPrwn&t0>7ddAY*ps0+LJFt<{H%|Q&;?olQBf2}+2Q5M zE*ID?jbWLa`_*?@JyoFL^f0pB_j-u8%LPcxdPZ`eBDnyF3C4gyP~hgAuBXB93H(3P zssATVkUk9;H*jf$iNWRl(eJ+M^xp$l6P+sXh<| zLLr3q=918#P92@d61T>ytd!j{%zq5V#z6?!fnTD7a(wja%f4?bWckMs5VqcLY)bhW zYK#&7^NgQ-d2A6g6Z7T2C1wyanPF6wTf!PERGJNA22CajtL77bR_0SgzKW=#GQ?HU z922Xljmb1M#*~^G66!5X{ZFLWZ!-9O((~M zT&iyPt6h&J3FKZAA=wihXBJT>EFtFzq8-$VUH*7cp$DXW!iyGI4nH9-7?`I((c(xD zp|45?0<85nb6_pVkuuomwm6zNM`{u}p%)urV0rnmTt1<|(7sXPd)UUiMyz&CC7We0Zf@Ph#!phb78G)d$Nz4*|9pt%P(Q4tR!owKWAce!BmdQCws=n9(3=?H@$`Q^Pt=Lq{@Y!hr zBlaAW!OGm!>LUh<6o*kZ5pl-=d@$ACJs6x~@X-)lT$bJyWl^rNa|y3?+_mw# z%QDOIYRz3AT^Ng10n;Emaa%WaF4cfxE3NtO0Yn6ZYIROpmB22IXB zc<&7Csgrk8l1f~~7DGymyidQKCncFLS1Z{h$BgA@(w;87Y)VCHj+(8$@LVyOEw z`;{(zh(OvVQLqX7i7UdzzpgM(Hi1q}2kF|ZJ@D*~XzTzivCNQf zFie|Vo?^MDrq#?~)XYo+LTt&H3(KK&sD?BVrUpln5jTmNR0yYS_uD(5i2N9dU~kAl`sN?4uwJs)L(3Eh}}Om(*D#9b$Mx6>?kg} zKcrtTU1rP~(T8D&H&ZtUwnWzkp$_p+eh@z$lOf#mf*wHs6oOxKXbRqSgotSDBq;R2 z@2u^Mm#o4i+8U1&yb@tEB4&l`#=Ng-0}de@O_O633d;pKMw|^8-89T~5Z_i*H)2qX zg0ZK#Al9Z#U-zgPA(o8DE9$%PT&h7a=+Brgh=tNp;@c7+j~SXWbxJ+A@oJIB6Xo@H zfFji!$*q-?htvZJrnm`99t0&bG|f_Iq$oBSo^*N}4(KE+VXsZqYil{#0ZCE;gTJ?Q z96O&#)}jO{$TvA@ahA|5*rXZK_vTV1E*QijEG9MuzqT04vwnF45GKi&()~u~b6p0X zsBexbe<@k4EJrCLn@!mt$*fGnilOtmHcCBda{>kE2!tm4#F8gdf^pQnSrlcX6FsK8 zP5rvT8+Hp6p%#MiiW$JZKtdRK^G^w~L+nOW@yj57`w{hEy*!ZvP{#l@&=oToHY14; zXj89VCatY&2U0i+9n)c8wm<@^`#5`gJ|?0$)uM(_Ix3QXySP&c2H7hYRd}}0tr?aC z3Z!K6;-%oIBtSFTS*eWyRo|FhLLwxH@T1cJY{J6D4HX)ZkO9sO@mN#@pggKPu?Ezi z$cfU$=dwDM9x z^e<~}*xHvjK26C)uDxstmp|FE>)WsmH;s35-yd0F5CW zTmh4#!gT|+bpf=qpMcPT{K?I@gfJoc7^wrg2WKYd=S(cgC5fnCZ=**yHterhh+-t1*v zZ8|$sii)agmy0f+YRUCJM#oJO++@|{8)mwi9#0CZ1zhKEvvyf7;4z}(5itkv8wMs! zZfi9Tdd+=*m2|cB%YJ!1d)vNp-sps^y^q99&M!yJ4txr28pzk z^FEPl4uDl2i1A++pSy%~AO5hEHZFmW9_z&kuI~DaGlw!XY3Q+j$r_k}vbp~hu+WaVLqEyphs8M;E zKVdJG*KYM1-FrKJQ|0uY-U6|aq@m|{lt8WW%+a`yjO@e4Y^|Se72bocuB$D||9L@j zD@V;p=qETnMQ8b5xAI$_G1rMuQ9*0ARMd56n3K9W%2*zeU^Fm|LbR4?vzh?GMRr}y z$M1g&-e9$Xv4{Fc1RZo*w+p@Vux`FC~)eY1&XcdsE zquOF!gZv2b0rTnKy`$T!om*d%$X9~c0wV=M3&QRP=^yY8B6AOkdpSh5&iJkWy0qS% zj6Kijdahy0eV0Aj`9-JmTJmwgPCFU=5mbBF@ylgClkzP#!W=KX zrNjQ=m~~l#{*?@Vn01>{smymCsac;~PnGntX=`tn!NvM&dPG?WD7#20Fq2f)fIAVTl5G59U_&kqAO7qt zVG#;;E*j(E;E6htjZ^Aak%$g6Kc+U`G6ER=VNRL|9-XObZc$~)JP*Wd$IDBJK&=oS zx%C2K(*;>W3t$#!{Aiv)Al33rf)|`)*)<#@_@x%=Mw}}WH#Ue~ytTg8r>TXdg ziEmlp57W$If~B*@oMMnDig1CUXo^?%^5>^l=LkvG&wD4&3HE1IDt#A>*oBodjC?o} z*vf&|)ic7qTPNq^WD1iHHZlaf%Dz`T3RfR)kn-^vRzs#hnXwxbu0 zx7}OyzOrQ~ol8#yaOw^D4gm@gien=9ari-;F7rBgpMJg$x?{Fo$xkoV&w36N^j(L^HM>IVKX) z2o?vQ?ms{>c^XwZ8(kXznEj?@X~>O)&7I!Ip}Xehl^jNx!Yyz>hcwR0S$&DQ5uh=E zT^x)S?cZ!}hxl6GqM20;6}2D;)}~b#AQO#LWKS~J^gYi+7Vv1EzxL8x{Qn5!-w5z( zGa;qCt=gOMrWTG&--gjNrDd$e$h122w<2o5t_{CwHYtYCv$nPq{W&OKbgwc~6=RmvxNnTD8d0oGHQp>bM~ zcuyZk1^u%8+F{R%tWNil`Q7FYu8W}#N#>pyJAR@PhJq=RCHdBm46Q{pUZAH=GG6G1 ztxb)$A_6wdqA3iER#ta(2Cm~~u2+!SCt!j49QbXa+_!ekDYWvy5KEnG)d9 znV%hepHm#aYARcoHW8)jx;quJG?U*XzIOXLnC|67kCDBWq(2vYHL_HHS!mkya_fgD zd(PXj1Xt$3G2h)Qd>DiGtk+M*MOL~u7WIBt`n9@)yJfxafZ@+auie(ZdXarD0$QO} zGYy8fo0o$yG?U4FM*LdS=3S8_LUZh1UUSr{OdfOK>t8(c-otMVQ8hk?+umoqFJ1K7N`i7Z%$)Rms@|?!;TXN6%z8qx6C$Kd*8RTrc{|bZ+ti85 zXLP6(_v4(sXW!RaqR@P_8h(FdDA#x^Gx?}JW^?wx`k|j2t}a-58=%(r166!5U&L$^ z?Y^w^`5(VyadL+8d~9WtQ``WOFygPWyU)GFo{D@E(xzr~iGLXKDAkQV((cB+lT6+4 z9{oP)=jd)e8v*aEV_uP^84V0*=@#=z=%J-l($Jm~!wk`pGK$4n~ zWGyBwsVcX$v{lyQXKnhlS%Q`=u+pe!?oqBPDq1$5#&WjO&B5TX6q+G1DhT1pDj(b*Q}vJzO`>l_iK0S?t|?t!*y88+$Axz*cYRNo5$5w)F>UJu?+((G>Uct=-nXInP) zm8E^EGu0^rR&J@1v_x|G>GS;XIO_8Ig?7V;a+$-0hjUOm z=BxN@CvxMxJg3<;p!fFoC9?|QrQ5}mVOW#zdp0bFB7EB5{vsTx$iwx7=)mLSs0c6} zs~H)P-7s+8MN?`eQ;p8}6pnIW#7bbwn{=&Q2JD0j$@OKy^5{nnd|7x3zNEsf*f z2-hmblqMM!Xj^C3+>TbnJPrnpe`c%tddbq#T{(<{*^u>jMQqfDj$rrK-CEf<$D(;0*YF>O%ypJTn%cEP9{r+* z)lE!syC3Uafz1jYkL=%2fl@MrLhn~qYKnvlIfLQ?*6L-o+QkJ*xbZUBinz1Ib39*q z@SLHqO(Jaf`5}dAoHjq_7CF<-M}uCAh0B zTN9)dhZ)GGCp9%Dd}lM@e(J^o<$Y|$G=0o+&ZP!S8v1Ru3?uTe0uYkFh&V#Wyuxu= z>;UoeRk5W;S-*v^pe-F(Lda)I4{P{$W#qJqwNa^vPi z2J+uG9w^`jpZT;5+`S4Sm=Iy}UCY2%07 zcCcz4&C!KeC9sOw2;a@w8B-;pAP!}7bGwucuGJYD^IDBprKL6thcc0b500fxxM}sh z{7#7lTw;LdXfLbYh^^Rwhn*IRG2I~%Wrc4WC}q+1c8wP-7JB1m=T3%}HF#($V>?c6 z(cU8N+qKAZS%wLek6ae-L-@UbYsc~YQx~r{MYwTnNek3%cSNy}HfzZDuS0sf+r|~6 zg96cFVL2omc~_V+uK9rE?z^hnYC-EsXZc6*fX+e7YcZ8Y&sve>#}`)n;t*~ zZ@H1+fbI1UDNahZI{OY8okp3Lc4mgArl6%$qrI?meGTLePY1#FWRp4Vaag1vI_LPW z-&;ywJ+>xkxr?)+t71Z@cefEAaq<3d=UyF}EHv~pS)bIg-|tthLqLkWDn-i2m`EfM zi6Zolbh6_A`a~GN>$l{cOHz(mJ(ccS10O@h(zzM_8*`?7(U zQ1BT08v9M1_@GM4ro3CNUI-?!rpF3gFhwRpOiJYOR5h7CYro62ozH30-B~C?Di>e^ zhpkznVKW$ZPJ&YS0Sszl&DWyn9l{)3Ga#DKF`J z7n7rRyJ>K$gDGmpl!s#dMHe`yC70%^Rzf1eulKwiN$hFsdY_uyfQQhY~{JfccV*eS=DrUMex)M=_Wcd@~4PMw#W|w98BMt zNQ$fbszEM|o_-x~$&~KBgI5LU=Rm06@LQ^KgIi5H2NfTu2NLFxH5$2&`-9XoJF81K zWl-s7XCa_aZTL`jZ4RSP3?@V|n5}>tE%Nu^XjxK1GMbqhtxAj}5UJ^Lk`_uZd&xca zX~mK}9I%@GDfYD5{;3PqV}Cgrlt5D4c`p2EWbF`@|E3+%;hn<>4v1v{bH*gLEFIry zXtBOe-_jF{1dbxHeNxX&Q~9*nA5!S@D6x;YARoP!-w^f@3LN|Z+c$7x^YrndgG_L3 z4YRn9u{4<9CmaAsV4o-XN+FGn9){h3aE3gPeKEgs_4t~`GCZKW&b_gg(mvzYYBjlK zB`-{YNvt^Zh5+alSI{Y_QT;mOnf{wg+WPMh+{-+*VeZ^9wrPl}KZh_uZ;_6IKUbgm ze|;)||GQmB(s8CnjlB#a_#45#M!zPsYhNYcQM&0E@3u*ifI|sEnh6E5E?=vc?^h!GEvpNTi-~(_&IyO3pJ`u4II&*7FL5R~+FDaVnY;tbj0vt< zGP@BW-%`ewp-1p&0*(kn)n+MyY0@R&I>)QoP9t>uXVL~^qmi&c8Peh|u_EG_x3zX6 zp}!sz7;+8z5CtdnPbNYOiMm^H@m=pJGs0s4Zc= z`z%R|+Y2(hL^zMUWp}`Mt{N6nbfK~VxfftVA3@!JybJ*%g%Vu!$w?M(xC7xph+rjV z3H1_ub%pORfhX9}E9yuXt`>fGEMj1ff{>6!!(u4Y z3U!5?#g%K^O*RUYo@o0Co372MDjiP0y`Ybs7-HSQQxMVuiGY%?zI=;oL3_f|bU?o1pninFqs1SAOkuK&ODKEeZbAE|Uq8-F#J%ymNH`?Sds~AnuGf zU=CpdP?k82&{+K8s0r=%+nN@L#Q#oZB>yrIKTHxj92-(bDJ4@3FOLW!N;k=X2m~t< zAntF^lb4miL{q{nB}mM1cje+!8sAWaB!X+n4+Ow6V2Tj(s>ZZkBJ~@vYnXtXMt~Vp zMREuTf`}!*m`Q#(3La_|lyf&KX5p3oY;rWO!@G^B_Bh^%{r|N7=}8W?w`)p@tH$Ov ziVKdB?Fc3(xWb!7I^tW+L#2AQ>|Y=9}YupT%y^L+tL;~>mM0&phAf+94phoh70iQv;QR?=rWM|sZ!G;q<14xTK?o& zj@M^!=!f-c0llA?oxS;U*t@|LsT`gt{KW65;tyunZhYP)gAUBp)qvxNKT#dynSZq zBZ`=U>`H-@3s8)Kg*n5SDW={Ji|8WDR}_9-sb!c!s?(1ewPK%8NF)CK&j3X2GaN;vs@D~G8q1kpG+3Dh}qfxs5d9-WVEp*km?|4r;5suqnG-_pxL z(&*jY{no4P<-~`+M2bgkSlMpD0zd^b3LtdskJQ@GBf-5*LvnF78G^ryYq%XPX)g=F z6<%n& z{z5)G8~_9W08GNxGA`id_vVA&1GN@y)~$w*6m=Hc z`XzP)F`I^?OuIgJegyC^b25`I|A9dP9*n$^KU^VrAGn7?-TW8tC2$fhn8t5H2fB~p zbB^9P_M-3CJ_b(jk=q;i@2Ab*rnWrfsOSA&`McFR6OAP;+q#V+pdO`SEA5}Vt6az)}yHK^lii(tZ}}fc)134zHHhfpymFnnRnag+ zFquZ`#Mf?kSVfxCg9?}4bc%b%An_ap@>(g81gaV2C`obb1!zDZ4X>MPd9Ko0>$_Hq zW)(!Wzxv+Vv{L%d=Mqpjq4&N9-YoOUkywL^9?X5~p0q>2IX9z+&lgG5U? zl@g24IO0p!*_z81lB^=9QH>z<%$GR>MMly}V=|qUbR)@S_)Y<+!inMmItz=6e(XMCE z-E){zYmZfIwxC^c1BD<09IClNY&5cLZ-dTx9y!d9cigXfyXiZA{}zInFF~)P03GoY%H$)wQ&NvX+8OU=T?)8na<6m>x6VtBF!Aoakou7dLG!xgZgg87*oEi)}wV~ zv~qd3unTUF)2=<6e-Ii*wAlXGY>9EhTX|&l@}f;6YjnL0+F8YKBD0-&CV9Qe9|WZR zEhggG*^QYZD#k>0*$LE+s%BV$U%78daWMALcOO;%-RUnP;A>rO9{%l`fki)9`pl)W z8*OV3Y2IkMZK*cqt$El$-}t(mk#g!S9=-*$A`kLQN1n_O9dX_ALl((hwjI9j=(5`k z;@t`O^sCAtiWSNL=cxNoayQz+Zm}CmbYsqOz0T{KqZykw*-DRUll0zlCpC>o z{#l(e>UFw2_1>feyyAW+eH~A|#Xb(Z^wTovM0Wp-49D=%J8Qy3E@WoGU;Ac(hFSc< znn_6TwC5hAYlL}J(0x4_?O+8Mg^43tVe4>%BjhzBCnr9!y`7C^_Y7+EOzFvIkn6I29$~qAL=)q#0 zWDM_!J^VON?;=Y`n%Im!CiA28L!Vdr82ymUekPlIXmSibX0fKK9A=`_^-esJ%Tm}0 zc{1edtok9v&)n<07n~myPT7_UTcXbYWDl729n3qW9)R%9*^WW>O2bqwoiKRfZv7=$ zHw!a?7&LP^^rL)QVm;PB3mVFcCR<@$(N4BRyDTke3mwr54`bN1cq}^!Gj@Fp`fTm9 zALrxU+5EV#`|S92_}SLxb(B(md)&r3Okj|axcGYPufn8c}O1}SMqC^ z*_AYFuXPK5R_;-X@fO+0i;C;PSNEJM&rJz5EUA#%JY8g4HQRC_Bwav?`l}p1-egEb zgDK7#_9ibSn#lBL1WP87*>ESd_Ai~owxZNRgo{)Mv7uq1l<;4}gF-2hM7uS$@iZ?R z=<{aOqT4*m5)Sb;NC~44v4bs%jzE+V@c_F}`hgBA4Fr1~6UI781%L+16#s&loV#?R zAO(>7DeUIjsMGsME|i)0=k-TZARH3fcDTL-a2*vBMu`wa6l|<58k*YVc1b)-JSg&d z-NhsyRtrcf>cG^YOM6nvR1Mu@L7s+OBLTM~H!qVNm6f0w**=c?vN0Me7HT}TKlUc{ z*_8$SWgF;FVmUd1ls40ja~7nawR=vkI)k+BY&Pxf9!ri`U)_~~rYEG1A>>w}Z-*!S zf7?l*ktJ^+L99b$%IFaWB=(5obbz&1zIW7W+_{!f^-2qPHJgch!64RAs|wDxbo`nl z*wj)CybO}0hU|{8wHYYi)&~-RYNxCXifVp`XD}dOV{2f1G1j7`L zHE&(yE($oJkdJf`fiao`w1DM^UCNx5Sk_WBCEC_A#ep@!3^7k9?lE#(JgDKGw}{e)<;Xu+v7{MbW6ScgiJAFM#|@S0Ty!EOEV;`Su+!!eWJwb26hlhbIkG z#rj<}M6_l_QPs;PkG1XMs8YXtr%W@8(9eyqwdDkw0BY%OW|Tf+!{CsY6+J1uBYbz8qvvXROI8cu zlvmPBJ=jB|G-IB_Ovls=j5UdTtf6B1BHM%8{BU}n{W5}F0-g!WW!m#SPKW@VVodfS z!V=D~!N1#eLJOOOJI?j0r$@2h82Q&6;!z$VbwlZcg2I3mcOlP5^GSjCK5g_hoKk4a zit)UvO^s1WV3Lwm7Kq#dWO70g5UmQo$WDp=pptXC-Cr_r(%x4c;gW;uIi;7`IsNA; zUu2=)vU6xEK;HFsV5No{uW_X4vQYBYrCg_2(cPKp?LC|KNg_5S1rGKC2SD|2q%)-{ zE%RI%eWzqqHhxZ4-;}oUr`5^Gx!K4$`!N!aN}$r`4l1z%S<`3n<{uoL_7RL4-RH3@ z({3Kdj2F<#nOfw^-@=@Kp*J-6HdbA%q}oIpOC8j3MwX^)b%0Dy*&>v9qsQ!tC_8yH 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

{zMy}s|dzcBgU*!rCa z^`D}iCa(*emD0up4odk3CfK5tYIZb^p!Y*-|OM z+~X+n6}mJVrXh?~c5B_4t6klvllOc4>ZVj8R$@?GoOhj%P#&92zR)cl`FkR> zgxGSqtMlnKCEm?DfGs!Pdf^(ViQK^y6vcWjw&zpwY~BDAI$1YY3ni2VU|c{F=u|ni zx)yC3;DuZLf!g?clBM6_r(>pjDnKU|bJ1(?=XRFX#o$A9BHg>dw}*{uLq>p46`&In zIi()>Tl;Wnr56A4w_Ss)7*?7M+iT#;yL!_b^4w_#w1(YmVXg=ss0M3q77pJNOlVH? zs|Ufc0zGwC+Si?>&AQ&2`>z{4^LU>MBHIq`Jur@a?g@K*#cnku-Qcle+2LtFUKjgz zs@th4!V&w56CCo~eOP5ORS{juo#NuY>#4pGLWl0$*d4<^nT1k=Yc^Y=wmHmUYJ+c! z6|eH|V5M80`nYB`sB)0#q)V#){^2b->BQ%G3l=+GVk}O8y`Fw)BY6WWqhkikL<{4o zw$8Z)s+HURqSzw1C42R{I;Fn^w8hx#ddViIqa$C+#00Kd%Mb{rL~w@Y7Bjaq8`#;q z>#U8Kgt^1w!1YWRdBVqfnU}(s?a8hbHdbDCY2^);tRE)LEyB?B)b9D`OrQ#rYE$V- zs>k8?zWt8;47Hh)?jqZccwy6;jWS;ZZU2TcPZqBU{>MrDGrJ9*l1JxojD^^YfPsHZ+HE*aA_iB!f({nkC$yd8zkdBmJJN)sz?(0=C%`IlDx$1Ex?V zj_kOuMmeda#7l{(AIeSyY1I^lI?2uL(SYsm=tx@$w5ux8d|#uNY?c$3p}QSDext&G z)V*qlbYDn`Evs=uLmr8zxC82{0(AZyEBCuQ>SxN@A|V47VN|92_xKEPnR`p z|Jfn92g^ZVm%dtxj>JH{I;x`zfD}UO!R%1Z9X4j}bXF7{U1RfF49DRahF-lJed~+J zCKm9j>>3vn_g`8EyRJruiS8jbGp`AL0k?x1Nq;PphtIFw0AJ{OalPO&6njq2^NQLv z(uCgP@=Q;V)7HGRs{7;BJa?KbG)_-7d*jmEy7^{7u^-YvTJ7-_6#u^L#r3=IYSMq# zE>Ilp{{|lm)mX3BGl48F3isU#Bttd+`v}16kwZ(9vhI6+)g%Y!3NARx%Wm$&JCZiI z1@U$U*fKBPOH@5;+^Rb{<69HepA)a{T+cQM3N_vRm2Xgmm)9=~-5;Ds<-om+;LjcK zQ{tBwy;8(n)N0eA3AUFQ=VLCc!}R}Nu}$N{vv9bw>9Ci~Skj5FEz+JgI3&%y3p~_i zY5fdtTjz7v1?Ph|yS%Zup000X@7^-aVXC=sO#AOONA%ws@}^ee09K_&QWlgpw>RBd zu-p0k2yixZt@Xg`>YPp9)X-=h@Do$771y?z1;mp*Xzu4`{cJs)KXMJCLSbVyTC%^$+>CJb}!gz+6u7El!5;4qll5MkPkha_cxsp9ef6{*IUXc~sY3 zGaD!O4+y0XT@EDpUo14b*6-IB9Z@=_i>C%w_=4o&=H_?fFj=b`xz7%auK39A%Oe{j zmgQU3E2(2r8^*-04vRbMfV&#%I-Zp(E4gIa2zjJ8_tT&rEMECN!r6Ac!n*(2e0IrQ z$Z{<}^$lWHf`fqlvo_&O+g`6;j*3jlx87y33gUKZZ0Mr6`}xT707vQ1c4=q7%+*uq zGnc*3XT+0ed#SFE7YI7uR0roPn`>|I_5$50Bgeb&kC-zxBQL1;N8(=tbo*y+Jg&Yx z@g45Jb8pfHH*50~X1b1GkJL*@WX9op7bGve`gbRIPPhfD>gH9{MqN~c| zD{T+WVX$bO%zGrBNBV%jbj@$!IY!qPUPM9Zlik8dwK^&$z(#x1uB;0ec3`v`xSsDW zKBBxY3Lj0;CpV%9s0#4Fll6Ul9g0R`zL>?gCwC!FrjPcakqfuxiGrwYq(^@O8A}?` zP(3#60>SB9zT{dBRfmIpUXrSop!Dn#yJLQ{=sK#Cw#U3#A{yuO{%X?Erz6uqV$63OWC<{((aB&?JzS+P>78rHdWKp;4#121mie(!KvB+GzomOxm;S)cE`cRr zNC8U#=yq;93rNjb)_W)v7?J57Sr?4m2Nj|RXs1F@4lwmN5&Sex!J&`e!G>p3{$;(D zC&x1ky`9Kyq*pjYVZaV8ts4|yUr66TXu=rKlIS(WJh9kO2q_IPrNEA;$@icT_Gs=o zJl?6mFhhemWg66ff{Ywo< z1EUq3j}(fMKZHxsNIf_e=&1(Kw+nGAiO-N$#$v5y$rgV&Uhqh|fh}%=;vMlqRSc_8z9GhXLK%a<6HsT&NF!B#53ZvF*Q0ZI45t&*#HGuz?cLb1hO;?f#RY)#g<0qAO zRwldm9naEV_&o;?$`7*u`v6W94G-nv#;mE;Y9$BOU^ zBI;($kUA)_5RC=fo8#ifV)4*9!SYK4;#$y-_MWFVPOy#j#t`@dLX|6y_ z#577W9)T0aI{O&s{V&0aN6?8Iqwc-GLE3M4kAaw-BTWkCkC2_eI)o<&X0!?Q>QgtQXr)DW|xJIwUJwWL~otWlLE&vhhD#MAO9HDFJ*0%;|~dTUdbB|RPk7gnEai7Qsm zT1-A|O9S)>#F_9@`GyzG+-PV5;`x#d0GZUMu=t7nj6K7GJL`npL1&IMzzLDGFtRjU zE5Ltv8MH>avef)Y*v9IvPrGo%eH?E)|X{I-t4<#w*HzE9TTpC9^u> zNi@=m6iMe*X%K*-=}R8jB=+_1EMB|3O??Wqd0+-Ik%LkG5TzVd=Wa^aX6Dyd5~xcc z6H}l+U6|+wI*FM2`X^8|j9;n!Gt%#VDs3}`;ioh60Rt?V zCg)<%yGYdb+V&M7N*gNtLj0-1K!s0n_@y4Qtk;Oft*t{^6k67_HT=r89URaxpdnI& zq|$b{+RSePB?lNaBfO>x0VPgrl+GGhG)HyAb~FyBmhoelPy}MC6VTKW#!01iChBSt z_bx!HQVb1y5Z$wiOM6wDez()5^&2qcw}0aJ451*d6bppaJrVASX-?OQ{EDP(U-5 z$)j{Hf=bP3bk^FUqLUJqTB=#ih*w(g7M(sU@ULlA??%#>OWDnevu zI@_TNW3{OAUOzfp4$jthSPKV9Ixs+3$m7w`NhGcW6#>Ot<%||9c6&Illax1Nh&4)x zs+QI=fyJCuhzB>wWBa#SL*{oo7lmt|p)@2o$bD*G?KP&jw=i&;f`+i2yq#RA+|btk z2zo(D)*T=+4B#(J0B=B$zaY_AMz0lK`dXNn@v5H|cL5hp;GKgqNn6RVAxuc_0WKti zeUC{b<}}3S^qQlf$mHIHk+w2yWU(iVVCmm9_iwz1Oqh2=NCF8Ums+@k1g$#=C(x%)jagN5aeisdh|;TQ z5RYNY0w4btVpzIDgG54_s3;=j92Of~-H+g45mzAGc!^v#=$6NN5I(E{hIB;AL0Vu% zATLZdL;Tnt7vsPwH5^UWm&#$p6p7-PaDf#FZHUf(5;Ob%zR`bF9sgKP{01hmmq0uX>>ARpsYqhHZl24C~iFQ-c70dyM$8y5l_COY0rcp@s!sDK{k&T*tz%8 zYss-gS}LT%unZhN_BLnsp9flFgP_t9&!E^OJZ5cXhMK0+g29R~kvyj3&&y!kp4L>3 z5w52N)od&r0NNxKjMb;mo%9GCF)ojd4aCpTP%V?uJ5Tow*O2p@)62$=BXu?_wK5C0} z{0_+y9mRBHs$-8AQ*oq|9Ki??40#Wy{bFPy5Sk5YD#OGsvN6yB2x^=tPAY4g3(;kO zbny`qeV2K(17yj{aTdGcSQEN=(etAIIu~)nJ;kD?N~ayIBB)0)r$z~Rw**<&M~L&X znLxXv*z}g4VqELr^cwQ^eN2rX5zb$_BT@_2cj86g4n@Ym5ZWooMW=L_LX7=$CD7WO z$)9ODq=!&q1(5_js_@#`T0Fz;eF@>l#^j{p?>bwC@b19Jfat{NWRE%0LMku^-J>~# zXt+%;Jjg4F0!3nB@)Vb1uYKt z0I+$sJ6O0=85gX*qEJFF4{5G9#CSRCzKh}3Oo(Eg)M_$=_eoNWd}z)NoG)A zX>6=fTWlylss3w(>7Y>sXUVOaxS60#bj&y~%|KWlF;M^e?k4kg2e=oo*rL_2gK=k8 zkg{8vD!e7P`%o=|GvwAd-93Iq)QDL-&{R(u!z#+vbQ~w_4skCEp@!elnhCrX1++O^ z3d#(D9}Gk_;kktp8s1Arm2R}-y)9BnjQ@1c>^l&>u(8L(0&JTfQ30<5PS%PcsVM@{ z;+%oEF5o7wAc59>fDsMjh0bzDiU8`sQysWo*u3UE012p27qyIj^4F02GEX!p(!JEiIO51bctY3jFu=uw4#@Pna%Lnpo!Ae2w9nY!opZwY&o(o z2|UGY=4VMa`Jo;@0)(+@!GIhnrn+>smgK!!w;)vysqnODiGw~q=;(ukK0fH^gPwoj z*RBv_&WFzO{rhA;-s8PV`!)>2kHYDHs`%b1bbI9hUwN(k^P0%}SWL z`%AoL_WPdSKS%o^PSQmz1dL%@j9M}w9ArHdIiN=NC|RT9>!D!B7vZ9w2(4p!E*C_I zjmQbO0@J*srD!l>OnQ9sEC)PJ0fEIj1P-z1DT**0hYMiOO&^(x9?q?X103PY-k5c0 zQn4ypV6?`t0M#e`wgbQ_jks-V+$m zs0mx%S-p@j!J$B3VLZ3AOw}~H1VccB#jqqTTQOnf1fvQzwKdSECua?58bdA*QF0a{ z__Z#UY^+At3o@;LRfZ6$ry;tAB4S!15Z2Sfr6P-< z2pYB=fVQe3?Cm9b*K0~L>>?;oMh?#mECgxD7KT-p5i5yBWBdPU6Q+jlfxr@vy~G&B z7Fcl&aZpBHM3uBjjB7IlC6Clx$jpa-n8bMhF z5ofT%uFIjdm?!m!W~|^N_Sy^+YsDe3j~t{6QsHe4#IioCp#b>q0KEi^Rm0+2_c9)v zFFns0Wp+v~jJrmohkJ9bymSuH7DCok*0cx^u2Beu1vzKB4#0WB zfU(9)LBwT3D~$6I@j46LME1w&Bi z8HPc5tpQqxd!+2WW&aJmS^N4Wh1gT1A|?zVje2W#63qa{lDxDW`}`&Lhssza;E)T8 zL#ALNT9_6RTSbc{#;^;t&2u5fX4qg2W(Xi~j-{B&v~hbW18UCAR|bds^8eo_iN>sQ z<&Yl5tr}=sLM4{ax(3_WWR4P+RPEN)vn=O(B8JpCNtLqzGf)K*i@{mlaMgNYDY;BQtneHoo+;QEwFtyW|3LakSuY1{e)! zqA*cT!mQp>p-It>aO~vAv+OHHg)H@gPI1D@jhvF?<>hy&%DTHKZLSI;w}SO4hoXXo8N{(R#> zPq5EFe5*sB`ClKeu&<1(KlSj{+0XdC^YtGm{^DEz`F#E~HG0O-%E1s;VyL2cZEn^U zxs0*=4$6NY^k?+D^z=XSw;_L(UrPNy-|fyXA4^luo#)qjbT9p1E2Tfu(XP|z>2+?x zb(Unh1R5D22?8Tfzz-Q75G{v7C))%iG^>WR^jn0urf@5c{xd#N%uB{?QSBR!w=|Y= z_E2n+*_S}ARmY5BG+J8}n=5Dda$DSO8OMcWWLz{9L1PI-vBE`-rQFBrWGUOSB6;KK zckroU(IAa3fwaVEfzeDcHW=e_k8O)@DgZ=g17t^BG~a^uH+o&cYred}mT?@? zveyWe-q$uVNt8sRud6rx9v+a|A7=g=!Gv(6m;l@ENbkW&N(zd*w(sUs(4=db`*nBDpcg5HRo-kTeSjye|4{QbV;CF z#t_)yyx_cE!S?bF)xcs)ppLpTL@#=oknT^1F#8c z!Zr2!wJJR>%~O}5qi6z{Ya$CWWlp{hmflxnn!-kQ97#te`FdJvjsNLJYpVnr)4W#8 zn;6KP#t|nsT$EmmSuumVAhtE@)M@Bw>g(t6X~4w6NoecHmE(kwBkZJ_bf+cZ-g<)v53vCdn}4JF2WxyF;D zxRKVeJXv6-F;>BX30}a7nzM{I5~Cw)ifk=^?okeOF5Jm8!K>|WLcL1z9kvLxqm*ZQ z|0e8?u9tsI$+1AZhl1C@f_Rku+p`@ZoZyrWZ@*8ss8}YgdLNGXZ(cbO`=57m1gy;T zK2Lz~2(M3D)F+2q^E5O?0II+Pn{4t)+#(OGPbiddMS??!&K|5orf^ej+Rg~_~cR(o_65=ur3(yIT<}0Xq$H! z$Au-vR}^n2(6^{qCSF6&%V;n&I1GeC3KKTgJ8p|sClAK^4D%nlEs(lnB}4|Jv0_Se zopl%5j=~w!sAYTG(7|Ufr$rtZVS407{rIb67fLFyG=?&F^}!aG3^LHQt;ImA0>N)h(*7IxH`AxTPDcT{}y#1rF`zr2~lN-!6kJJovy#Bl$ zvz87Sr`Ee#UTMG~i+*7+tTM06yrXWaPyr&qqrVqom|)&*Z(rp-$0He>|m?w1uPJD!?+tFI(++#8cx4UsN!RT9mxA_uV z^!4L_V3U1Wdci*cgPFZtA^+xbbL3tT`Ug&h`NlcR$So*>`O-kv(2f7>2S1su-`|7` zSoRDlx))ARf^TBpXLeBNQS)fDuHAp2|5^yMAHj;gfbiF~zxFoW-lAVDcm8&h28%j& zBre0j9YFS?+b9V9A|n$#No>b{4V}qlPHtq6OT(}302IJ!zIHR+wM~Q<724Ga*&B+) z#0454kaN(8t`{RJw_O{yzs#cx#Pn~nW_Zk)tsqSp@^%M@0My)^Dt}h%aw}B8Bx)kq z?xNpGJFaL8;9yIzdp?*>?K*wBju{jvVKI$H>@@gb_}Cp30X;oNdq;eh~*(6(z~ z5*>d*uKA!4n-dLXN#KyB)1vlWd+4DFCGZ+v$N)E|2cM_u(zWwpbyfjQ$-NX71ItH2 z@VpuU3Y)`NL~pSV*lYC^#d8l`dN@ViLQK|850l1^qK@KENm(VGZh{X^^K5c~ErpC;Jv-c&*f`;2--xdE;fZZ1@Y51u~-ugkaJ z15|D&ZAh)=a8sq*wyPKarMi#ZGH!u_|4;i$dPUcPQ! z2G-#$`n_2+ITifKpan;q{&h6tHpshOD$(ncN~5Qg8Y4P|n!p4Jwnq=Goz&E$(NWSg zGD&=16~A_`qyE*$w6)7W{!YrO9z7G*?fS;XzyCe-v8Q#`!m1l+@iq`G9Musn4Bf=?MI4RcI25 zZSaURwh0b7lnFRSq?os!P6Yt};o+BabiPieDZ8$Zv=bm}Y#GzD7MVMJNKFi`_6Wv- zp}Sr>d(G=9FQ4w4oPEi*zF!L`59dJU&3$DEAJuXZ9&2t#J$rx(R;nf(_oB@9{-Do^ zL|XaE-V#FulKgAvRc?hQ=Uqn_B{G| zuhlEl%WW=Z-RN9aY)Z>1>zH2cMgtaFR$o}8#4&MC@m)bUDi5~s zVcN;h@LbxjzEf8sa__ueglEa-p$dPHC+b0O9Eht*7>vq&y$bgZsX(e=_lAd);~v)> zE)aX&z+PahL#Lk?_5{qc{fh!KajJTPDtPClPFvrT9P8&1riq5Iho8^BzaHxbIV@3hRuIg3yP>(r2dQC5T9 z^>gTW`ku3C0vYeJY|!%7szy~r)p7aEH0vfTzk#eqo7sO&6cK!~T%4+5iwKjmDqw!8 z!0mkClhtN>bx0$TG@A68%F?03K|dJ1rU#}Zo83k+(dX02@3|fJ!&Whw$K>7I)cWH| z?-YAxJyBja11NrK-Mn~Y5m=@9QyRhzKY|xS;(Y#rBfpz`6r9)@?@}-V*NdtWGd`Ow zdzLp(%mzZz>Z6$gN^obKA86(lXG9&S)xnDzaP_M-xOExxY`RG$0FtfVRW^qynSQ_EaQ)RGD=joRWQ`e`APCEj`V`?1{<|gTQg3+{GWbH zN6DojAqzL)CR2H?k4f9^8Dz4NdF5a*WgmjvUPkC7oHLH?d^_C)epFSt2H19Dmdc)DZF-tC^eceZVKO7|v}f zBt8Jft{POueXZjvmA27m$@?z8rE)prh>$ox?hyL{JOo5fptr_&zce`IhYHppj@?Q1 zDhw!rkEfL^0dsiA{z`{g@v1?E#G6-#jQ)*Cf_@1IY?7soYqltWYAkUOPooO)d0fcu zOksk%0%*y55kX&DK2j=_im~`yodlG+NR!8#F&hUR2yYYBuVPy4ECtHP|dfD5IYLo)Kd5$@fLjam2n9c>jh&u!#p9Tp7D;Fgj+%W1`}#OIaUxe8_#D-&1>MP%gDCC<-~-<-?oQgfG5 z?$8%01ob|-&H*%N4^T(^*5$9SA-@a&^3!-Sq2Di;Pjaci<dcvHi0(No6yfd6`uty2@M+L9FQc(xEZ4|OWt-qh1$2G8YwMKWPmzD9Z5~NZWm=BFu6`O6xdDfioFnz(NIf(u(IbsA$X(6ZCMh%uQ4QMZm3zMM}BRdvxty{ju@ z?&e^U)v263L1Y7SMoHCl;_&gC8YIm#O`*M?UkG6>DkNyHe-z}b|E zZPHuC*raQB28ZFOU-Uaisid6PlKRjHW&Mi%>R88Epm_Nbxw8}%?=rWiS229<_kP<< z;<|VLqnLHV`}gB^JJYWO_I82F_2|u{hzOw#{kE&jsK|mZv^n>5c79jvK@%8Ok=5i! zDsdz4e&uSXyTy|+70NGoPp}{YZxHJ{L|g z_AS`RtMtsy%~x#Y(g#hS75nylN56ZdE8Fqg$Yp;g&f}+RG9VM>+5Q|2vy(CzP3}HSsJElPKft=9gGq~4ys#d0T*BsnK(N- z4P0E`oA8!g)7&m7X2K0Ali_C5?6~^)_9nUpZyj#b)>vo5dS8-KiR6LI&WgffVSKZF z?XS)%U&DLUuh7$r`$a=?abj|pr5Za?56Rr=MuH`NeOWcvnOr=I?1Y743t?GRzh-pO zWIdm(Myf4pWhAS_bapS39FGVSzo`x{1>73-utxD1RdD;^ z`oHj+0r)%eQX*#t99PC$WE~-K@juusJIaatvfqv{$T~NJYbagYngLCw` zoOfk4m%_9OYCVC)rL$8~NjA&ycUxPpozl*U(_dOV_c{ljXe8C;l7}M6D4wLbt1srG zpYrr7a~D^$rXTt$z5a<4!BCM&W)><@C!}NG;M$tiet4Mnu70NXz{)5pLcBdmW09Y_ zF4GWqKI)aIRb7z^?Oua#Cpa7kWhH)mfhmcO$U^3nr?OcW>+6lXqTYH?$w3fpz{^}6 zG$W4RvFrfIA>j>8rzdT~7@$F73&Rr7Qto^OVk0l&r_Or#=j;G=b-sF^E8gHz(J66^p|RQpW=lx zPKeb^q%$z{SK+fm^}zRXkfx(%Y_KKOsFf2$c<8oxud@ZqQb2Zrm6Q{5wW?n%XDW%_ z4hr(qwj!FE$_Q+LnscM0r_QbT)Fn`ia1XCPxCDN6BHM z80aS!FPY3D|I1jP2s*nan`7EKs$Su7ViIFG3>L@iY9y<&CO;8STLoMHj&ibTW@Va5 zSv9>KG$@cRM+Z?)g)87+oS7^NaEi(iI*hjyW%P31OJ86~L3`pLXKdLhm=!5DuH;p~ zW=%f*DRBv}eZ!1_0(6zmiO>$F*0+rJjet+86lmJB1&mqFKwC?+e_GHlpa1fr;ffBc z(Z%=p5~VE$Ied8D<+3qLoGLEx^4D&~SpN5y$Wekr-qr6|Bs|Z$_{KBfu}9y}%#3`* z@mC#Cl3{$hi5-1-iQCgqj&X8i)}D}BbE-B>w+7JgzFrq7V>GRxXpmYDlGaCh%}H@h z?PD7QlYL#fEzHPxJqj%oYM?d`%Y+mM1Ih?K_Qf z@3Z7GDGhAi<_h&{WRObFlKxK$g!{=N8_am!l&XT;s&@C3pegxYV*xgK&a_5W7&ETZ za+BipaY{c$G4W22nMf5O{-OU{$1*>=lLu4s69v|lD z(==5XrZA!?W+mmh$`BjYiNeg`NDjNRYKF!jQLhX7A{oH?khTTB3;#ale=oqGw3S>> zh>CDV=%yg}#;YF4Co=UBd1c$_V*;qSI*zM&GuX`)ynjStZfsyQG;SYQ0t!iHJ0K6f zGIdtS+@UhbD-*wCA|vvsf}$;!bWTed>;*~36ON$;;FUkSoI$bjxOZP^5Di=2`#`Sl zW8?)^pXM%GRcoem0~Qh*&d1?PHH&j(YQZOQqmfAzLJ{nXmPC|ex#a&%3W6Y~g~CZm zxM)xk+$Na9G!?8?PaRKZ$7;14`;%bsUTG*`dTIkX;2oE0E>^m5!}EQ=f;Qa86W-ebm( zRT3DFdW3Vjk!Cv!nFND=Q8FM0DuB z^?nz-nJgnS5gqMf+krEOG;IGZao#7KVlKM=%Occq0S(K0q!3V1#BLGYXPVgWa za!y@(e|F7AM*%W=0$_oNEfWmvj!swBiWXTUY}0*2HYW*lDpGUhwf0-X+|v7MlvTHz z*Vfd0=~^CkzYa$?R2nhz#>IN&MiSRme0GeX*e*oq#I)9;8524vU%zF>DgrYTP=lEZ zI;THA-puhM^O9^Z;Ns(?Di#z#83DO!0Zhetki9&N_pOh_ly_3$;E5eVQHjwBM@L*s z9Xo5(E85FGJ)e$=B)?R&*bD^LYfDoef>|kAzzs!VqJg2>T2`Kr#9nD=j2vDT6tXSC z_ofs8T}GY?!12{UOfrp^o`&C6(xohyrpza8j&;puH1V0|VUPy$k_{l_w8gl8!QzVG}?hQcSI9 zt)VJhGRs{Dl!U=f@idmifkHWNg-aZ3f%v_F#g=#xkyub#+^`OuQCWhbQXq3A#*$7) zfN~3zS2FPQbqE5d2~bO=g-7BtG};R_YcQ&rm$C$5ze6J~uog0NB@Ph9^9Jbk5%%Kw?0$8E+Mz(wN4nh88x%U=)Uj1=M59tYFSXg39|^RaVHdRyq;T*^TT7^I643ZB6y80If;hT0b-CXeLCq za0dmQBoJ^0w-+0vmjTOjUQ0Bd94L5D>Hwz$Xh4#8G`C z=|EtOvv5oSjOz(DUt88MpRUbiAID$*2l}Xt@e(1B!wifRSz2g^lDJ6EKWvE)6kcz) zR#VaMI6hbYMC=fv+2G`yiVH+70kt%!2 z|Fgx~`5c=05M)CL%Ub6k9yt+ykA^_aBTR;CWpYCp!k5#)m*P zhDkPH|JQjJwj{%GIB^<^IWno3Dq>au7^^5_8`C+AB9XAWqGuCAW1azYO_8g@77XHZ zmWLilFyUGkk~VRrh5$;9h?~p@kL#wzETH=d=Dv}!N&Y@Jzu`<(Bvyvoa`Kx)MZA-4 z`U)e)#)NJR4&6t*81ZX#pPNj%1y*nb=Fq6y9Lypd`AIs+0l*d2O5o4CD^}#NW=w51 zy}wEe4mzoJn*cI_s=$|KMgHyOv4j{j(?4u%eBD%0&U2N-em2gCuyBf}WnGoT|6@a-YKJ6TV1aRI?xs|HASvH@ax~VBn0k=+U96TW<)bliR^s4gUnwPGhN4D_h z9O-u@6Elf2-o=&dm!FZ$-G#;?Fx`AONSJdB9%SU&0Hw1TgJYDzFNvcNl5nBe`U~Cm z81i%Ku(dQ$ItqCuG6?ia;+0w$jTHdxmprBs2Aw?c&t{f3UtilXwNb*z(Y> zwB*e&R64_Nc=)OKQx6@aTnHZYOpB3N5gld2Yf>nN*+5}EckD#d_BW8yp?~T(=)d1s zf&u`jN@*^hB7#7MHaX%Dfm(xpGfV{OS6B0)PuSCke1x@WsTUl#8?;@icGf{+Sehm+F-<#a9;&4`x-XxSsc*KNhI zN1lMOU|J7?j%BP>GDcWzVNx@6DRnWKuWJ0GW9ZbFWL|sG&=2Cs*G&1?cGDDwtaU9> z!we7|RfD|Ij&t%pt}H|XGRz%g$g!A>g*j`wkLj<41xk-s+`+*F^nOk(q|qnq*S`p3 zCsvcC1SZ+Y3HFqwV~qP!9Zy`DyCVA^y+rX#J@n{^t_^m<2mixmw?Wva;*~7pGUj^A z9*4FQ%!g=T&+Ko&Sb-9Q*pGM#u}X!h7^Oa5iH(}D{SokN0>Ho!z&)3fq-NIzIP%n(t zK5H@>ghoPt}1erRw7^&0j0)yl~++FYKh3FEx}NMr83F5QX(&_rw<7aMTOp z(^xdE92v3+7T8=mlFZ8l4T+FlbI`%>A7?T6g4lmq0l%)DbFCbpogyv+Snrs-NP6R* zb|B+<*Xw~FF2B$*iF-}pVU0sl&X*L1F#%rx8U}ialnN{&77MyS9teDoH_4Zbl?rGt z^Is8Sup!+Vk{Y|PAeZ_PViE_|40z$86!wbmXiyr?St(*9A_qI^)cW+FTGxYL$q(Z% zp*qz4;_FDty?*QMW)zK2e3|pKn!x*5kPIY*U=R!ma3@F_JB&AvJ^k|e52zpVZ}k`c zmtL$kyXMAv*cMAk+5KX_Zx`s~BI0^BGX2IY1`Cv3gvx@r+D`gC&6=i=ITud+?!xP~x5LyT6e&Xh=Rv@-DLB+O^tNx#p}?>8aAiXlB~MWDzeixr_;0W7ky z$!`qg7Z+Ffu8REjqRq}|Kg(yL*#kt6fvNf6CQ@NBz|>}t6E#EMs>G-*mcz3j^PaK= zJ>FS(9i@aZ4Wr8Ipf(9m#)XV`c)m!ncyVwNJHKx(?saZX2ox(jwXT@>@x-0*oCj2o*83JQ-zuIZpe#J(X2V-yneZzm8_mE% z9+io2z%uaXB+O^tNj1&hJbC&-$CyJbWrLZMuZ}^eVXcNh68qYRh}3bCP`1TBc=kZa zQyWVZ*PAj|#qbg=h+HoqDVKyVxK=amg6yDxuO9s3XZB7qb#_aNh2KF{KuQg0Wg`Te zj}3XSyaA}+HKHZ>P8oPPiJ9bljk8?(t#ArvGDLHO{1DWxwPHS`IIJW~N&MnivAvT8 z{+vYD>?N5~al~SUF)}DX7wWsnfiRF}2e0ervEF?|3H^))pJmHhJVk+|Lu?(OIAa z#>EMs_r%=im0=Y;eH>8cwVwNO7H1^z+&gdCDB+(2`Y`(V`Sv-(*P%Nh2NKe#FzYi( z%?SHUc}NVqOMAuKnN| zE)foJ#o=6MmCi6B1zLjIS#-j{63y;ueQ(h}zFdyZNk7ehaIu-JK|z5t1|n_oyvr0& zV;yq{toI-CKZ}%m+hRV>_O;C4JU9)w!Odx41Q`U$EJ#e7Pv0AB-~kh&c6PmQ@jt#C zg)?0Mz8C*qu%KS?HLvk#zkNaOVB3DT&@v2TRwS{6dKA`qDw2X0v=A}QUw$ z%@esWh^-bWiZR~RfdTLku9J|8SXn2#CwSMswR`955-ti#TC?OOgxYfzH&I9FMY+IP z4)v_*4_oM(9$=a7Cv@!aPTrE1yKeCIRIP>Dc)(2@0-(;~8t?5>H(O64MjH8qYJ-M29q12oRG?py?t}^7$C({6dp*YzqJ_g9LtraEJMUafX>KVegGUB2BzckFOnx zjvXfcF!K;|wD<4+%dQQKkMQYS10>}GE7wir#ULki#JajGAxGXaBuh8JPNAnT04tz# zPH_t`v`;)c8+1o)H1r*5@cDw1QLCxxTx)7T=5Eb@ep6HB@Brn>Xg*4Uy_B$WA(5=1 z;R65`UC-FH2r2w+?xBsA}KiHUQaB={o?+#;D!xrYk z$e=P%&giQlP#kG@|02=CWthZB2#^HkrN((SJE6EwDGFfC?mN0^4vmDBw^|~Q60x5V z%_GEg8vt0lNHAZs&kz3}EQ0(G9!Va!WEjwE@2kMKC)vXQY`aJUC@(})AA0*}z9M&p zgA@@Z!5i#w8!SpYAeE(zA;m$SWCb0{)4%NBD|&Z$&oOWYwMhzM+h2vjBSU-)xBuNK zeby;D_y?b??pJsI`4T^X`T&tyVyP1lZlD%4n@C(REU<4RstR|W&fizWYJs^3gM|p| z9gK6C;GKRm0@XOOYfK`|{7oG@UH*4QsEi{?t>SiQp zY4`qKpY7e2Zq&kofk6(c5(L2$;RlkZ%a#qG{V$S#$G^gd`p8$=3)QU>AoOa;OM*$R z$^Mr9F5<>&xR71RRh8#h{>iQ=LWY@Q9S9f#=9)n@$nT{n-t=PB>BWN#c9}?}bu7U= zi$f>cW0)Zk^4*YbqaBxB@kDeS%R6Y$wxnWdD@H~cr8KA5ZP{{)i*)N4Jy(~E9cy{w z46C&hb~IKOrJ&CC_bW2gs|VP`phLovwxK+M?+@#3rQ{6I0lJvG*YtB6u)nMfC>rI| zULZB)LcPATf$ssl4B$c|vCIX-P#uVd$+2K)f@6YjjDxA|(D-*?sx3lN6V_2B_#mtf_y+O&vkoStQNB>j5*DQ_*r z0hJJIG|X2T5s^?BCVs*gxO{Ur;IZtLdSllszrS)-Y{k+?JqS?q+Y!(rwPGuHRlTr< zpj=_4-EM%9_k-EMK`>hlUg97Rvij7duXSA>b|kPP>Ta5Z^$pGvY8f`{j?klLq1U^m ztKVtY4o<$wry}0@$Y?!4EQA%V#cPFO6I>51$0yFf4S`)38Wf4fes8&?P>^u0?j@n| zHc<*&#NrbCM|GzPx;goW-r)4RNOHo-2}DcQVzN9=_ah6pyCPu{!=J6rqXgur*n!-+ zW=LVuG8*?L%480J6~`m>71+*X0ryVJOL!IS&cUnTf!t|qn4%%Nz6)ELO5f0-q*lwq zXfW`=$0U!waVU~nmG3vu3IY)4j8=J^0Fz0w4i=?=RiRtrZzD$0Pj%?-S~_!g-gd}W zfBfR2+h0Om+P3_{Jt}$(4Lx2Kq2ZmBt8qm{-hYutqv4(TR zu;&}z`B{$|-)`t(FP6jbzUQt2SJA|byhjL`FNw*neSGyk|Li4F#yNiqVkh1OrC4wj zxj-slRjK-)+xXQSdM1G8t8KjAKzoT01(hC9*N|dJj2DGyM*!>*24yVQJH;os63#5{ zZ4&3_x-O>Ej#}8MR#_~fsg%ZbY0_h1U#IXf;bIf+=$8uTfqV!9B%Tb&BRF&= zo8GlV6c>G3v5tPCtvhP>(U#4G^?+z=EdcH?43LQ>6I#Nb>kC#^|KBKo6-ydc+##cAQGnVS}i!Gl1Y39B*DUf zx75xS)ux8vpRE@I467T9QDnzG+}iGj0hpn1f%uzDKf!!zA{Okw7?c1p7F(ml+9UHR zD}QW^=R)E%@kl-&qZaUpIIwn1LvXC)75PGIe|GElg5sGkM4t~Ws1PW_N-);PrAc@d zEjUL+q$1y%qC>_4!29ViTrr zhQJ?v+K1o@*9%qpf|7p|=vbK=A+&NDN8QD*2iI+v&dWuA@zXxq_;()8qR6kIcd_}h zGx`5CXH%%=9Y8Ak3euW|tZi0Bpt{@mj<3G*pW_M)24Vm}Lvfkr29BN~kg?1EFcKl0 zM05D!0{{RJ5CZ@N05e1cAOHYQRnIC*ft7X@c>6Zpyvxgo#2EoVSOGAU$3l|p?kjU# zlDm;#dH??+^@9LZBWOeb01R*=Sy8*z3^Nb_QuuSfP3nXWzzFo_9W}rkgQ1o({Svk# zn z@AuOx9?!kgwz={IKm7Nr_l}if_A>O#op-{MndQx$a`w;P-tLLS6)9&9UD}6E_;}?{ zT%Ie?=bk(_f9K0-XESr0pA*mM%K7qVU#a9#W>L%u2;fB3%>|(-r*i^2i7$}8@OoMQ z%%6yK*zU=UP zM?c5mvAuYyaf+=sO7r21dds2r?5E_{jS}58s!qK4$&Z}zg74Llxc6y+|2+y1xWes{ zZ@pR{Rv@o3Lw9W zAMMWY30qz?y8PkrUiRHx-d$b6YT5ZbynWAi&i;j(&UuFP=EHwz|0>U}uBe0D>`%-T z4R+B_Jb2`|c^@8}U2?vSqON=;$KhhC{Y8Ek08&7$zm3|{-}dY~e^b7LzOWx~^U}}p z&aO+3`rQ3L6TixhujbFSpF)LST>qJ)FZ~ss%GVzHDkJNhiN18@9lCuVPStCN%nrV2 z`j7mUe)In!9g}kgJo%6A<-_Vd#k?4Pp$~-tlsfZEjf?)=gnVuRk5wtoI8)+zvIo99rG{O$(3>FBR)U> z9jo6N4@wi8^esP;-_QNBe~VYi*65b2xOY-h4~4O;PcvDCi|i+lK&rm*=Mn^1&-+}7 ztxgE~Yk%?QI6v?@otypU#izo5`R(Ur`E&NZ$f5F!x_Q!z{g-TqJ#U+S zx$eGmXDD2Gf8PJ}V_vDnt_SiD@}K^Rypa<+O8(-3wHNuP#Fqs9DL=-&3(rvXC_WZ_ zoVP`O0l)v}_E*1iuRDL*)Bc*e&c1)UY&L&({(bk-tMuDz_;(Ji;`7WSABhjQ^o8G0 zuI)zu5nsN%;#ZXqPwr1@KkxVK+knnJo}DjESaCDA@!Ut%7=221JiAYe`-N_+Bk%LE zFE>B6U6-+wkKpd^+{aQq!LGUaEp`$f@2DrtoGvc^PoJMJ`q=ctXD)hH%(eW{0J}{7 zPCjztgJI2d(beqEfqeLfu|E&{&i$TWtS6#{w9m;}^IgM#^M2zSt48_IK|k30$MA^J z*!(<5!}$!wR}Nb%zwBH5|D2dFU(M76(O>qP(L6}z>6YW$e9=pvsT+2m)8R9>TklTQ zXU5B_<;(c%c|tMn%^$Pq$-HCdzUDt~k6gbqEj47Gb2hs9K=6Gg&!a4a>{EmOJA?C+ z>+j|}SKIDGezo`C;w|>%ckRDze=FzlZR#fa zZBOTAA|3vJ$UY^R!b3>?FdvsMyiWS8|C9fVeJc5~f_YFb5YlnyvvLE1|98T%=fSC1 z_^G7leBL`u`=9vZG+M4O08zZ62;`QI3;->No<@eV$4uix$AACs0eKP+l1z%gKupAl zfD^HSD{IM0mir4vr9_cKkDtX@Bm!^8&u6`~hO}pYrmcfw;Sj@NAwT|lB!)zfA`obg zrV->@71U@@&a~+{>Lf>mFW1{x#ME=0%5)MS>2Zar^t8pD=^%*T#8nni!Z{((G>Iea z$W&~A38p6m{mBtBuoxp1_4n(`l-`D~btQ<*EfMH!#7rd#@UZ=fl`;xRyds_8rYeb{ zSvc4k8Tr|nd3Yp{#S|==hOuNyRUPF?Qwb*K=ZAX?1;v9Q!pFeQ{=#jBof(If&BnmS z$jr;iFoCHW1|qtd)v3W;Q;fR6mHDz#7%>#p$*_EkYz#JzOm^x_Rndks8~}h;1d*~M zfWAaiFldSbwV_%NF_?=d1y-0eo^<2W6vc-LjgVejQ?R2@G`SH0XceqGeMEvawx(kb zI|rpNR+kABu$mD=)ij9JPUpSEdUhEdt)lBHXVvV%^6|ScSc@3BW@h0%FvobNUGI2;CNsY9x0CJJfJy%f-s8!LzLgT0#2a%N{mzSQ4 zic~-#f(0u%_zq#5(4#cAjr#x-9CTlf&AZ0K@xe-r2OO-i_ zuKw{zax?SraaJCjMHPV2O%7;0QQ)K%zLMhn zR@)Z5ACE5sNZr9z#l!0H!okeIIoaQOS~`ZR5WUqTQ(_ZI6)%elX`e{I#$-$!=;mN& z@^Pwh&C0{Vlj$>1VAGW{pvzA6CYr-KfWkcgTg47tV60@|VBpTgJ_Zd8vZr>UG%=OW zE)mWeC@F6GMNu1Q!%$hPG(HCUQ=`!(D4jcsRHLj$s!$=*(rRp?CAd|Gd^{*DLJ~3^ z(ZD4QP@U4dvWit=G|^MTw|M32kI|KpjK_~3gNdgjYF-l(A{c#k?Gea`ahok7hmk6r zROq6U{SrIe%WyQLP}Q8-=^fR6^>o*Y)k)f< zZF>%79&8tvggmJV-fRVhQgczff$(D!;sa7<%>y=gcb{iAZKn?e?MjN6eXz2K9(ctG))tItqLz& zQmj!9c)ZqN7bdxQs*lZR%_&aah~X(KbiG;g4tP&g&TIxllDe6`vPNW@ZstrGAs`gm zvBCubE~Nuj0%BH?nMxqr7R-5XnYWjljh(oMNy*-vzg~cL6y@nCLli;{St-3o{NGLW zqNQKAhn7s^i9Y4xzk4U$F|eVr9GYmGpRd%XrO!H-$tATA*DNf<25Y8{EtmUx=7=Gv&x0*qP-IbDunN0$v*MKvC_CPoE);c1|< ze}Fs%yhSPWPanKgSlU+O3Cw)V5&DgCRmW+UpY6q2WSw9oobE$ z9rKsCHDtDp?CM1-HlrXr16eR|qoc8=C1m0wE;%~swNQE_sEOJ3(-z%8eHMBG17`zK z(CwP|6GCl00H3^;y_F}BIdL@`MruLIO>FI?tCdy73BS@h(>Ki#1za}H9!@)bH9nG8 znR3Zz|FSzY?5mzsWt+A7mftD%L=0gu>BsTceuaw1jn9mWEzN zXVoai+B9IE>l&CnRn$nMAq|G~0PTxvoNSs6BHVI7XymV#>FQdFD6DAjOd~{@YW;$H zWriAgq1F0TG^)xG1@m$0r%aBbq6)Hk3kV>vIkBIYxkWW6KCr2ymMlD)$`aHy0gSEN z;uKCo+)8x1X?4{5%gm%)MD#=TzhVUCx5P_Za{+2w83D)ahzt~&C^CNvUpx9(ro)hG z!5uk68W?P~R`*q(veZkIGMS*Pi4Q4B;MKTkcd>E-vD+Pm7PG9H0YhW^qX*$+P_&zr z9QNEwHzbphLgO@!wn$k}fMNyL)XhfUMdNf@H!BGkO{%N*U^`ve{dMKq*0NN)iXCbR zq@APsw$N@3d*Wc{YI@mf@cA4_=GqaH5jQ4@Ys<(R_-!*=TdKoIs=1Nl-B)Pc9-QM$~el!lxS9 zlBiay21We0M_;ql;PJ6S^^nY{XR(5^YL5MqVM{Grdc~-?!`16(#9Lm3xedP8CR|3k zpV%#sr4(n|j;-o|LolajCt=rW3G};IX^)Y(4U-7Ha24G$RtOHSsde67H7VGNv7IzI zRi8zdt;=5bUR$*-2A7Y)ahZ0yrZx%?mwvsYD`tyr%%LqvI@SlD^F*f{bYP7#pKe)&$BF|DIfO+huq6_gqo z!D8{Lp>30k2zNE0N3L7I12(>jW(qt& zD1Dx)k5wpEN3F*4TR^fl$Z~O*(JmvzL`+VkHxz~&$h(PcTU2i**j_-+h{K9D7$usL z&(LZM+dU!=RDjc44088s(U#T2WBu#8r;@s9z?p-lY$=~jHhhKd%E8wV=c>m4zq;eX z7NMIZnk;owO$@r$hbe*v*+Hfk>$+iF#^5nA@o?s?8ZXbHA{z*SVyN%LQhRWP zSlZr{_Tkrf_01=mi-_@iCSi=|AR%H!Jsv3d?m6B9p zF-s{gYOo)9>$nrw%fx1sbDc}mDQH8S()#7gtA>sW%%np#P+X5CzOtNU0PmIU@X0gJ z>2tawdO!xvm7(|UPT5sPl*Cnp%}fWFs;XpJG*y%~nJGPb8e@wDFhE|>20pDXP$C$Y z73dfi?Nq|h*y}dYWYkbnVJ(Bq%g|c)ioPZ+DJ?3ALxtri@~RBZr)ak#u*v{cRBNr) z4&ajN0Aqlcs(CrBHEunyC?kM|>MI`CvHMztVzhJHWb^0xP9v1egYtqY+9sBIPejq- zaHz1YRSg~3+DtRsN@-;^_br$?mn0u4Sgu?(j=br$0}pbj=6p(aH?b&QeT|yG2-&kq z%qMfI>^83;I~P)BWxBxbiPICI7Bf`WW*?eQJ9ehK#YZ9p%%0Vyu?$x8+?0K%lYR8m z+mO;`la)p;IJ=3z`gCsEBALz9)8t9LV;xigEEAClt+Hz9mbl=Aenm$z)nl7VT==x=yk_rpdQCI1=V zEw35bv_dX~J8CAnK`|a0z|=bN4SexRt-{M|Rgz%{j@CK@LtZ4n;GIfT<37#7>DVDpS&^e^VsgFT=XNk0u+o)NTNuwR#{3>MBIZ*STUrfsZF|HL-6M zTWAGRVYKvSZrLOMb0^GgZK)2Ap7oQ|P&h}bqJ={wN?+z;brV zwoOl;Qhl|do_E_*e*^)1sAF!MAr0p@VfsTaI)k1U!z+Z=+LnYDgV#b)$g_$>@i(H^ZntysBXw1~-Z6 zHJY!&W|jGF=p}XG@7$8JIZcZYv^s5HdYeSQXi9aPr4W}X8ZINOsj(xsT(uLwek0`E zVq)V>K3=RIAeYb-v$oT5cR7HB1D?u8slB%74UL)VSvGODW!0tW zHkYAR9qL7f;riBw%xtbPOG)U%8Q&voTbyxsuBTHMz^1qBC`k3w!R`zo72-K$FK zM+81qVEfgg+31fk|MRYW{inOBeP^_!QRlqM7_WwXT)VBQGdy3$GFj@=0xy>O4gtv? zZ;wyXZ4+II$BSJ$RQRCUFWt9{n}?tLz+ZOwUQE%2WrWToJlOHgO-6T9@J zEK7YvZ`DIma0A}_Cyuixw!FyjYo=tJb0VirnJsfJb zOXQVll?8@$MBvGm7=Ps2SzzwxRYrSKmGR=XZPdZh{Upv?Wt>q)qrcGh{t8!n3(lXh)kC(i&`Rjf2MvA zcaahR$=HSdjy1}HK$MBViH(amO5`Q&olsej-Ik1fcdKHGG!Rb`MhF*nE1;L5EeFYM}KReo(n+0=t42)u>pdOsGkckAl9LD_w5e<^(U z7nNY+@nhhyL`}6!6N#>Xh6;HLX-RNe;%pT5u-#6jq8+gt*5j_L8NYRNiac}~kF}91 zGbXeHJVMSMR~8ZgwleJSZCX#DG;qT@*#HJ_9&hs~iHp!zU=hd;_=@fħ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 ~jb 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≪+QgTSe}vl818dk88#V@UDWbyFqrSC=Xk=9S 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 +7E7kmkbjjt+k(z9FQ#%T`*U*OW< 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

1>F2r7Gg(xj$iqJ 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 z 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

viDw|8 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

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_rf.h - * - * \author Gustavo Patricio - * - * \brief RF Abstraction Layer (RFAL) - * - * RFAL (RF Abstraction Layer) provides several functionalities required to - * perform RF/NFC communications.
The RFAL encapsulates the different - * RF ICs (ST25R3911, ST25R391x, etc) into a common and easy to use interface. - * - * It provides interfaces to configure the RF IC, set/get timings, modes, bit rates, - * specific handlings, execute listen mode, etc. - * - * Furthermore it provides a common interface to perform a Transceive operations. - * The Transceive can be executed in a blocking or non blocking way.
- * Additionally few specific Transceive methods are available to cope with the - * specifics of these particular operations. - * - * The most common interfaces are: - *
  rfalInitialize() - *
  rfalSetFDTPoll() - *
  rfalSetFDTListen() - *
  rfalSetGT() - *
  rfalSetBitRate() - *
  rfalSetMode() - *
  rfalFieldOnAndStartGT() - *
  rfalFieldOff() - *
  rfalStartTransceive() - *
  rfalGetTransceiveStatus() - *
  rfalTransceiveBlockingTxRx() - * - * An usage example is provided here: \ref exampleRfalPoller.c - * \example exampleRfalPoller.c - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup RF - * \brief RFAL RF Abstraction Layer - * @{ - * - */ - -#ifndef RFAL_RF_H -#define RFAL_RF_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "rfal_features.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define RFAL_VERSION 0x020200U /*!< RFAL Current Version: v2.2.0 */ - -#define RFAL_FWT_NONE 0xFFFFFFFFU /*!< Disabled FWT: Wait forever for a response */ -#define RFAL_GT_NONE RFAL_TIMING_NONE /*!< Disabled GT: No GT will be applied after Field On */ - -#define RFAL_TIMING_NONE 0x00U /*!< Timing disabled | Don't apply */ - -#define RFAL_1FC_IN_4096FC \ - (uint32_t)4096U /*!< Number of 1/fc cycles in one 4096/fc */ -#define RFAL_1FC_IN_512FC (uint32_t)512U /*!< Number of 1/fc cycles in one 512/fc */ -#define RFAL_1FC_IN_64FC (uint32_t)64U /*!< Number of 1/fc cycles in one 64/fc */ -#define RFAL_1FC_IN_8FC (uint32_t)8U /*!< Number of 1/fc cycles in one 8/fc */ -#define RFAL_US_IN_MS (uint32_t)1000U /*!< Number of us in one ms */ -#define RFAL_1MS_IN_1FC (uint32_t)13560U /*!< Number of 1/fc cycles in 1ms */ -#define RFAL_BITS_IN_BYTE (uint16_t)8U /*!< Number of bits in one byte */ - -#define RFAL_CRC_LEN 2U /*!< RF CRC LEN */ - -/*! Default TxRx flags: Tx CRC automatic, Rx CRC removed, NFCIP1 mode off, AGC On, Tx Parity automatic, Rx Parity removed */ -#define RFAL_TXRX_FLAGS_DEFAULT \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO) -#define RFAL_TXRX_FLAGS_RAW \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO) - -#define RFAL_LM_MASK_NFCA \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_NFCA) /*!< Bitmask for Listen Mode enabling NFCA */ -#define RFAL_LM_MASK_NFCB \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_NFCB) /*!< Bitmask for Listen Mode enabling NFCB */ -#define RFAL_LM_MASK_NFCF \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_NFCF) /*!< Bitmask for Listen Mode enabling NFCF */ -#define RFAL_LM_MASK_ACTIVE_P2P \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_ACTIVE_P2P) /*!< Bitmask for Listen Mode enabling AP2P */ - -#define RFAL_LM_SENS_RES_LEN 2U /*!< NFC-A SENS_RES (ATQA) length */ -#define RFAL_LM_SENSB_RES_LEN 13U /*!< NFC-B SENSB_RES (ATQB) length */ -#define RFAL_LM_SENSF_RES_LEN 19U /*!< NFC-F SENSF_RES length */ -#define RFAL_LM_SENSF_SC_LEN 2U /*!< NFC-F System Code length */ - -#define RFAL_NFCID3_LEN 10U /*!< NFCID3 length */ -#define RFAL_NFCID2_LEN 8U /*!< NFCID2 length */ -#define RFAL_NFCID1_TRIPLE_LEN 10U /*!< NFCID1 length */ -#define RFAL_NFCID1_DOUBLE_LEN 7U /*!< NFCID1 length */ -#define RFAL_NFCID1_SINGLE_LEN 4U /*!< NFCID1 length */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Returns the maximum supported bit rate for RW mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrRW() \ - (((RFAL_SUPPORT_BR_RW_6780) ? \ - RFAL_BR_6780 : \ - ((RFAL_SUPPORT_BR_RW_3390) ? \ - RFAL_BR_3390 : \ - ((RFAL_SUPPORT_BR_RW_1695) ? \ - RFAL_BR_1695 : \ - ((RFAL_SUPPORT_BR_RW_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_RW_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_RW_212) ? RFAL_BR_212 : RFAL_BR_106))))))) - -/*! Returns the maximum supported bit rate for AP2P mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrAP2P() \ - (((RFAL_SUPPORT_BR_AP2P_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_AP2P_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_AP2P_212) ? RFAL_BR_212 : RFAL_BR_106)))) - -/*! Returns the maximum supported bit rate for CE-A mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrCEA() \ - (((RFAL_SUPPORT_BR_CE_A_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_CE_A_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_CE_A_212) ? RFAL_BR_212 : RFAL_BR_106)))) - -/*! Returns the maximum supported bit rate for CE-B mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrCEB() \ - (((RFAL_SUPPORT_BR_CE_B_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_CE_B_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_CE_B_212) ? RFAL_BR_212 : RFAL_BR_106)))) - -/*! Returns the maximum supported bit rate for CE-F mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrCEF() (((RFAL_SUPPORT_BR_CE_F_424) ? RFAL_BR_424 : RFAL_BR_212)) - -#define rfalIsModeActiveComm(md) \ - (((md) == RFAL_MODE_POLL_ACTIVE_P2P) || \ - ((md) == RFAL_MODE_LISTEN_ACTIVE_P2P)) /*!< Checks if mode md is Active Communication */ -#define rfalIsModePassiveComm(md) \ - (!rfalIsModeActiveComm(md)) /*!< Checks if mode md is Passive Communication */ -#define rfalIsModePassiveListen(md) \ - (((md) == RFAL_MODE_LISTEN_NFCA) || ((md) == RFAL_MODE_LISTEN_NFCB) || \ - ((md) == RFAL_MODE_LISTEN_NFCF)) /*!< Checks if mode md is Passive Listen */ -#define rfalIsModePassivePoll(md) \ - (rfalIsModePassiveComm(md) && \ - !rfalIsModePassiveListen(md)) /*!< Checks if mode md is Passive Poll */ - -#define rfalConv1fcTo8fc(t) \ - (uint32_t)((uint32_t)(t) / RFAL_1FC_IN_8FC) /*!< Converts the given t from 1/fc to 8/fc */ -#define rfalConv8fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_8FC) /*!< Converts the given t from 8/fc to 1/fc */ - -#define rfalConv1fcTo64fc(t) \ - (uint32_t)((uint32_t)(t) / RFAL_1FC_IN_64FC) /*!< Converts the given t from 1/fc to 64/fc */ -#define rfalConv64fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_64FC) /*!< Converts the given t from 64/fc to 1/fc */ - -#define rfalConv1fcTo512fc(t) \ - (uint32_t)( \ - (uint32_t)(t) / RFAL_1FC_IN_512FC) /*!< Converts the given t from 1/fc to 512/fc */ -#define rfalConv512fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_512FC) /*!< Converts the given t from 512/fc to 1/fc */ - -#define rfalConv1fcTo4096fc(t) \ - (uint32_t)( \ - (uint32_t)(t) / RFAL_1FC_IN_4096FC) /*!< Converts the given t from 1/fc to 4096/fc */ -#define rfalConv4096fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_4096FC) /*!< Converts the given t from 4096/fc to 1/fc */ - -#define rfalConv1fcToMs(t) \ - (uint32_t)((uint32_t)(t) / RFAL_1MS_IN_1FC) /*!< Converts the given t from 1/fc to ms */ -#define rfalConvMsTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1MS_IN_1FC) /*!< Converts the given t from ms to 1/fc */ - -#define rfalConv1fcToUs(t) \ - (uint32_t)( \ - ((uint32_t)(t)*RFAL_US_IN_MS) / \ - RFAL_1MS_IN_1FC) /*!< Converts the given t from 1/fc to us */ -#define rfalConvUsTo1fc(t) \ - (uint32_t)( \ - ((uint32_t)(t)*RFAL_1MS_IN_1FC) / \ - RFAL_US_IN_MS) /*!< Converts the given t from us to 1/fc */ - -#define rfalConv64fcToMs(t) \ - (uint32_t)( \ - (uint32_t)(t) / \ - (RFAL_1MS_IN_1FC / RFAL_1FC_IN_64FC)) /*!< Converts the given t from 64/fc to ms */ -#define rfalConvMsTo64fc(t) \ - (uint32_t)( \ - (uint32_t)(t) * \ - (RFAL_1MS_IN_1FC / RFAL_1FC_IN_64FC)) /*!< Converts the given t from ms to 64/fc */ - -#define rfalConvBitsToBytes(n) \ - (uint16_t)( \ - ((uint16_t)(n) + (RFAL_BITS_IN_BYTE - 1U)) / \ - (RFAL_BITS_IN_BYTE)) /*!< Converts the given n from bits to bytes */ -#define rfalConvBytesToBits(n) \ - (uint32_t)( \ - (uint32_t)(n) * (RFAL_BITS_IN_BYTE)) /*!< Converts the given n from bytes to bits */ - -/*! Computes a Transceive context \a ctx with default flags and the lengths - * in bytes with the given arguments - * \a ctx : Transceive context to be assigned - * \a tB : txBuf the pointer to the buffer to be sent - * \a tBL : txBuf length in bytes - * \a rB : rxBuf the pointer to the buffer to place the received frame - * \a rBL : rxBuf length in bytes - * \a rBL : rxBuf length in bytes - * \a t : FWT to be used on this transceive in 1/fc - */ -#define rfalCreateByteTxRxContext(ctx, tB, tBL, rB, rBL, rdL, t) \ - (ctx).txBuf = (uint8_t*)(tB); \ - (ctx).txBufLen = (uint16_t)rfalConvBytesToBits(tBL); \ - (ctx).rxBuf = (uint8_t*)(rB); \ - (ctx).rxBufLen = (uint16_t)rfalConvBytesToBits(rBL); \ - (ctx).rxRcvdLen = (uint16_t*)(rdL); \ - (ctx).flags = (uint32_t)RFAL_TXRX_FLAGS_DEFAULT; \ - (ctx).fwt = (uint32_t)(t); - -/*! Computes a Transceive context \a ctx using lengths in bytes - * with the given flags and arguments - * \a ctx : Transceive context to be assigned - * \a tB : txBuf the pointer to the buffer to be sent - * \a tBL : txBuf length in bytes - * \a rB : rxBuf the pointer to the buffer to place the received frame - * \a rBL : rxBuf length in bytes - * \a rBL : rxBuf length in bytes - * \a t : FWT to be used on this transceive in 1/fc - */ -#define rfalCreateByteFlagsTxRxContext(ctx, tB, tBL, rB, rBL, rdL, fl, t) \ - (ctx).txBuf = (uint8_t*)(tB); \ - (ctx).txBufLen = (uint16_t)rfalConvBytesToBits(tBL); \ - (ctx).rxBuf = (uint8_t*)(rB); \ - (ctx).rxBufLen = (uint16_t)rfalConvBytesToBits(rBL); \ - (ctx).rxRcvdLen = (uint16_t*)(rdL); \ - (ctx).flags = (uint32_t)(fl); \ - (ctx).fwt = (uint32_t)(t); - -#define rfalLogE(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the error log method */ -#define rfalLogW(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the warning log method */ -#define rfalLogI(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the info log method */ -#define rfalLogD(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the debug log method */ - -/* -****************************************************************************** -* GLOBAL ENUMS -****************************************************************************** -*/ - -/* RFAL Guard Time (GT) default values */ -#define RFAL_GT_NFCA \ - rfalConvMsTo1fc( \ - 5U) /*!< GTA Digital 2.0 6.10.4.1 & B.2 */ -#define RFAL_GT_NFCB \ - rfalConvMsTo1fc( \ - 5U) /*!< GTB Digital 2.0 7.9.4.1 & B.3 */ -#define RFAL_GT_NFCF \ - rfalConvMsTo1fc( \ - 20U) /*!< GTF Digital 2.0 8.7.4.1 & B.4 */ -#define RFAL_GT_NFCV \ - rfalConvMsTo1fc( \ - 5U) /*!< GTV Digital 2.0 9.7.5.1 & B.5 */ -#define RFAL_GT_PICOPASS \ - rfalConvMsTo1fc( \ - 1U) /*!< GT Picopass */ -#define RFAL_GT_AP2P \ - rfalConvMsTo1fc( \ - 5U) /*!< TIRFG Ecma 340 11.1.1 */ -#define RFAL_GT_AP2P_ADJUSTED \ - rfalConvMsTo1fc( \ - 5U + \ - 25U) /*!< Adjusted GT for greater interoperability (Sony XPERIA P, Nokia N9, Huawei P2) */ - -/* RFAL Frame Delay Time (FDT) Listen default values */ -#define RFAL_FDT_LISTEN_NFCA_POLLER \ - 1172U /*!< FDTA,LISTEN,MIN (n=9) Last bit: Logic "1" - tnn,min/2 Digital 1.1 6.10 ; EMV CCP Spec Book D v2.01 4.8.1.3 */ -#define RFAL_FDT_LISTEN_NFCB_POLLER \ - 1008U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define RFAL_FDT_LISTEN_NFCF_POLLER \ - 2672U /*!< TR0F,LISTEN,MIN Digital 1.1 8.7.1.1 & A.4 */ -#define RFAL_FDT_LISTEN_NFCV_POLLER \ - 4310U /*!< FDTV,LISTEN,MIN t1 min Digital 2.1 B.5 ; ISO15693-3 2009 9.1 */ -#define RFAL_FDT_LISTEN_PICOPASS_POLLER \ - 3400U /*!< ISO15693 t1 min - observed adjustment */ -#define RFAL_FDT_LISTEN_AP2P_POLLER \ - 64U /*!< FDT AP2P No actual FDTListen is required as fields switch and collision avoidance */ -#define RFAL_FDT_LISTEN_NFCA_LISTENER \ - 1172U /*!< FDTA,LISTEN,MIN Digital 1.1 6.10 */ -#define RFAL_FDT_LISTEN_NFCB_LISTENER \ - 1024U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define RFAL_FDT_LISTEN_NFCF_LISTENER \ - 2688U /*!< TR0F,LISTEN,MIN Digital 2.1 8.7.1.1 & B.4 */ -#define RFAL_FDT_LISTEN_AP2P_LISTENER \ - 64U /*!< FDT AP2P No actual FDTListen exists as fields switch and collision avoidance */ - -/* RFAL Frame Delay Time (FDT) Poll default values */ -#define RFAL_FDT_POLL_NFCA_POLLER \ - 6780U /*!< FDTA,POLL,MIN Digital 1.1 6.10.3.1 & A.2 */ -#define RFAL_FDT_POLL_NFCA_T1T_POLLER \ - 384U /*!< RRDDT1T,MIN,B1 Digital 1.1 10.7.1 & A.5 */ -#define RFAL_FDT_POLL_NFCB_POLLER \ - 6780U /*!< FDTB,POLL,MIN = TR2B,MIN,DEFAULT Digital 1.1 7.9.3 & A.3 ; EMVCo 3.0 FDTB,PCD,MIN Table A.5 */ -#define RFAL_FDT_POLL_NFCF_POLLER \ - 6800U /*!< FDTF,POLL,MIN Digital 2.1 8.7.3 & B.4 */ -#define RFAL_FDT_POLL_NFCV_POLLER \ - 4192U /*!< FDTV,POLL Digital 2.1 9.7.3.1 & B.5 */ -#define RFAL_FDT_POLL_PICOPASS_POLLER \ - 1790U /*!< FDT Max */ -#define RFAL_FDT_POLL_AP2P_POLLER \ - 0U /*!< FDT AP2P No actual FDTPoll exists as fields switch and collision avoidance */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! RFAL modes */ -typedef enum { - RFAL_MODE_NONE = 0, /*!< No mode selected/defined */ - RFAL_MODE_POLL_NFCA = - 1, /*!< Mode to perform as NFCA (ISO14443A) Poller (PCD) */ - RFAL_MODE_POLL_NFCA_T1T = - 2, /*!< Mode to perform as NFCA T1T (Topaz) Poller (PCD) */ - RFAL_MODE_POLL_NFCB = - 3, /*!< Mode to perform as NFCB (ISO14443B) Poller (PCD) */ - RFAL_MODE_POLL_B_PRIME = - 4, /*!< Mode to perform as B' Calypso (Innovatron) (PCD) */ - RFAL_MODE_POLL_B_CTS = - 5, /*!< Mode to perform as CTS Poller (PCD) */ - RFAL_MODE_POLL_NFCF = - 6, /*!< Mode to perform as NFCF (FeliCa) Poller (PCD) */ - RFAL_MODE_POLL_NFCV = - 7, /*!< Mode to perform as NFCV (ISO15963) Poller (PCD) */ - RFAL_MODE_POLL_PICOPASS = - 8, /*!< Mode to perform as PicoPass / iClass Poller (PCD) */ - RFAL_MODE_POLL_ACTIVE_P2P = - 9, /*!< Mode to perform as Active P2P (ISO18092) Initiator */ - RFAL_MODE_LISTEN_NFCA = - 10, /*!< Mode to perform as NFCA (ISO14443A) Listener (PICC) */ - RFAL_MODE_LISTEN_NFCB = - 11, /*!< Mode to perform as NFCA (ISO14443B) Listener (PICC) */ - RFAL_MODE_LISTEN_NFCF = - 12, /*!< Mode to perform as NFCA (ISO15963) Listener (PICC) */ - RFAL_MODE_LISTEN_ACTIVE_P2P = - 13 /*!< Mode to perform as Active P2P (ISO18092) Target */ -} rfalMode; - -/*! RFAL Bit rates */ -typedef enum { - RFAL_BR_106 = 0, /*!< Bit Rate 106 kbit/s (fc/128) */ - RFAL_BR_212 = 1, /*!< Bit Rate 212 kbit/s (fc/64) */ - RFAL_BR_424 = 2, /*!< Bit Rate 424 kbit/s (fc/32) */ - RFAL_BR_848 = 3, /*!< Bit Rate 848 kbit/s (fc/16) */ - RFAL_BR_1695 = 4, /*!< Bit Rate 1695 kbit/s (fc/8) */ - RFAL_BR_3390 = 5, /*!< Bit Rate 3390 kbit/s (fc/4) */ - RFAL_BR_6780 = 6, /*!< Bit Rate 6780 kbit/s (fc/2) */ - RFAL_BR_13560 = 7, /*!< Bit Rate 13560 kbit/s (fc) */ - RFAL_BR_52p97 = 0xEB, /*!< Bit Rate 52.97 kbit/s (fc/256) Fast Mode VICC->VCD */ - RFAL_BR_26p48 = 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */ - RFAL_BR_1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */ - RFAL_BR_KEEP = 0xFF /*!< Value indicating to keep the same previous bit rate */ -} rfalBitRate; - -/*! RFAL Compliance modes for upper modules */ -typedef enum { - RFAL_COMPLIANCE_MODE_NFC, /*!< Perform with NFC Forum 1.1 compliance */ - RFAL_COMPLIANCE_MODE_EMV, /*!< Perform with EMVCo compliance */ - RFAL_COMPLIANCE_MODE_ISO /*!< Perform with ISO10373 compliance */ -} rfalComplianceMode; - -/*! RFAL main states flags */ -typedef enum { - RFAL_STATE_IDLE = 0, - RFAL_STATE_INIT = 1, - RFAL_STATE_MODE_SET = 2, - - RFAL_STATE_TXRX = 3, - RFAL_STATE_LM = 4, - RFAL_STATE_WUM = 5 - -} rfalState; - -/*! RFAL transceive states */ -typedef enum { - RFAL_TXRX_STATE_IDLE = 0, - RFAL_TXRX_STATE_INIT = 1, - RFAL_TXRX_STATE_START = 2, - - RFAL_TXRX_STATE_TX_IDLE = 11, - RFAL_TXRX_STATE_TX_WAIT_GT = 12, - RFAL_TXRX_STATE_TX_WAIT_FDT = 13, - RFAL_TXRX_STATE_TX_TRANSMIT = 14, - RFAL_TXRX_STATE_TX_WAIT_WL = 15, - RFAL_TXRX_STATE_TX_RELOAD_FIFO = 16, - RFAL_TXRX_STATE_TX_WAIT_TXE = 17, - RFAL_TXRX_STATE_TX_DONE = 18, - RFAL_TXRX_STATE_TX_FAIL = 19, - - RFAL_TXRX_STATE_RX_IDLE = 81, - RFAL_TXRX_STATE_RX_WAIT_EON = 82, - RFAL_TXRX_STATE_RX_WAIT_RXS = 83, - RFAL_TXRX_STATE_RX_WAIT_RXE = 84, - RFAL_TXRX_STATE_RX_READ_FIFO = 85, - RFAL_TXRX_STATE_RX_ERR_CHECK = 86, - RFAL_TXRX_STATE_RX_READ_DATA = 87, - RFAL_TXRX_STATE_RX_WAIT_EOF = 88, - RFAL_TXRX_STATE_RX_DONE = 89, - RFAL_TXRX_STATE_RX_FAIL = 90, - -} rfalTransceiveState; - -/*! RFAL transceive flags */ -enum { - RFAL_TXRX_FLAGS_CRC_TX_AUTO = - (0U - << 0), /*!< CRC will be generated automatic upon transmission */ - RFAL_TXRX_FLAGS_CRC_TX_MANUAL = - (1U - << 0), /*!< CRC was calculated manually, included in txBuffer */ - RFAL_TXRX_FLAGS_CRC_RX_KEEP = - (1U - << 1), /*!< Upon Reception keep the CRC in rxBuffer (reflected on rcvd length) */ - RFAL_TXRX_FLAGS_CRC_RX_REMV = - (0U - << 1), /*!< Enable CRC check and remove the CRC from rxBuffer */ - RFAL_TXRX_FLAGS_NFCIP1_ON = - (1U - << 2), /*!< Enable NFCIP1 mode: Add SB(F0) and LEN bytes during Tx and skip SB(F0) byte during Rx */ - RFAL_TXRX_FLAGS_NFCIP1_OFF = - (0U - << 2), /*!< Disable NFCIP1 mode: do not append protocol bytes while Tx nor skip while Rx */ - RFAL_TXRX_FLAGS_AGC_OFF = - (1U - << 3), /*!< Disable Automatic Gain Control, improving multiple devices collision detection */ - RFAL_TXRX_FLAGS_AGC_ON = - (0U - << 3), /*!< Enable Automatic Gain Control, improving single device reception */ - RFAL_TXRX_FLAGS_PAR_RX_KEEP = - (1U - << 4), /*!< Disable Parity and CRC check and keep the Parity and CRC bits in the received buffer */ - RFAL_TXRX_FLAGS_PAR_RX_REMV = - (0U - << 0), /*!< Enable Parity check and remove the parity bits from the received buffer */ - RFAL_TXRX_FLAGS_PAR_TX_NONE = - (1U - << 5), /*!< Disable automatic Parity generation (ISO14443A) and use the one provided in the buffer*/ - RFAL_TXRX_FLAGS_PAR_TX_AUTO = - (0U - << 5), /*!< Enable automatic Parity generation (ISO14443A) */ - RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL = - (1U - << 6), /*!< Disable automatic adaption of flag byte (ISO15693) according to current comm params */ - RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO = - (0U - << 6), /*!< Enable automatic adaption of flag byte (ISO115693) according to current comm params */ -}; - -/*! RFAL error handling */ -typedef enum { - RFAL_ERRORHANDLING_NONE = - 0, /*!< No special error handling will be performed */ - RFAL_ERRORHANDLING_NFC = - 1, /*!< Error handling set to perform as NFC compliant device */ - RFAL_ERRORHANDLING_EMVCO = - 2 /*!< Error handling set to perform as EMVCo compliant device */ -} rfalEHandling; - -/*! Struct that holds all context to be used on a Transceive */ -typedef struct { - uint8_t* txBuf; /*!< (In) Buffer where outgoing message is located */ - uint16_t txBufLen; /*!< (In) Length of the outgoing message in bits */ - - uint8_t* rxBuf; /*!< (Out) Buffer where incoming message will be placed */ - uint16_t rxBufLen; /*!< (In) Maximum length of the incoming message in bits */ - uint16_t* rxRcvdLen; /*!< (Out) Actual received length in bits */ - - uint32_t flags; /*!< (In) TransceiveFlags indication special handling */ - uint32_t fwt; /*!< (In) Frame Waiting Time in 1/fc */ -} rfalTransceiveContext; - -/*! System callback to indicate an event that requires a system reRun */ -typedef void (*rfalUpperLayerCallback)(void); - -/*! Callback to be executed before a Transceive */ -typedef void (*rfalPreTxRxCallback)(void* context); - -/*! Callback to be executed after a Transceive */ -typedef void (*rfalPostTxRxCallback)(void* context); - -/** Callback to be executed on each RFAL state change */ -typedef void (*RfalStateChangedCallback)(void* context); - -/*******************************************************************************/ -/* ISO14443A */ -/*******************************************************************************/ - -/*! RFAL ISO 14443A Short Frame Command */ -typedef enum { - RFAL_14443A_SHORTFRAME_CMD_WUPA = 0x52, /*!< ISO14443A WUPA / NFC-A ALL_REQ */ - RFAL_14443A_SHORTFRAME_CMD_REQA = 0x26 /*!< ISO14443A REQA / NFC-A SENS_REQ */ -} rfal14443AShortFrameCmd; - -/*******************************************************************************/ - -/*******************************************************************************/ -/* FeliCa */ -/*******************************************************************************/ - -#define RFAL_FELICA_LEN_LEN \ - 1U /*!< FeliCa LEN byte length */ -#define RFAL_FELICA_POLL_REQ_LEN \ - (RFAL_FELICA_LEN_LEN + 1U + 2U + 1U + \ - 1U) /*!< FeliCa Poll Request length (LEN + CMD + SC + RC + TSN) */ -#define RFAL_FELICA_POLL_RES_LEN \ - (RFAL_FELICA_LEN_LEN + 1U + 8U + 8U + \ - 2U) /*!< Maximum FeliCa Poll Response length (LEN + CMD + NFCID2 + PAD + RD) */ -#define RFAL_FELICA_POLL_MAX_SLOTS \ - 16U /*!< Maximum number of slots (TSN) on FeliCa Poll */ - -/*! NFC-F RC (Request Code) codes NFC Forum Digital 1.1 Table 42 */ -enum { - RFAL_FELICA_POLL_RC_NO_REQUEST = - 0x00, /*!< RC: No System Code information requested */ - RFAL_FELICA_POLL_RC_SYSTEM_CODE = - 0x01, /*!< RC: System Code information requested */ - RFAL_FELICA_POLL_RC_COM_PERFORMANCE = - 0x02 /*!< RC: Advanced protocol features supported */ -}; - -/*! NFC-F TSN (Time Slot Number) codes NFC Forum Digital 1.1 Table 43 */ -typedef enum { - RFAL_FELICA_1_SLOT = 0, /*!< TSN with number of Time Slots: 1 */ - RFAL_FELICA_2_SLOTS = 1, /*!< TSN with number of Time Slots: 2 */ - RFAL_FELICA_4_SLOTS = 3, /*!< TSN with number of Time Slots: 4 */ - RFAL_FELICA_8_SLOTS = 7, /*!< TSN with number of Time Slots: 8 */ - RFAL_FELICA_16_SLOTS = 15 /*!< TSN with number of Time Slots: 16 */ -} rfalFeliCaPollSlots; - -/*! NFCF Poll Response NFC Forum Digital 1.1 Table 44 */ -typedef uint8_t rfalFeliCaPollRes[RFAL_FELICA_POLL_RES_LEN]; - -/*******************************************************************************/ - -/*******************************************************************************/ -/* Listen Mode */ -/*******************************************************************************/ - -/*! RFAL Listen Mode NFCID Length */ -typedef enum { - RFAL_LM_NFCID_LEN_04 = RFAL_NFCID1_SINGLE_LEN, /*!< Listen mode indicates 4 byte NFCID */ - RFAL_LM_NFCID_LEN_07 = RFAL_NFCID1_DOUBLE_LEN, /*!< Listen mode indicates 7 byte NFCID */ - RFAL_LM_NFCID_LEN_10 = RFAL_NFCID1_TRIPLE_LEN, /*!< Listen mode indicates 10 byte NFCID */ -} rfalLmNfcidLen; - -/*! RFAL Listen Mode States */ -typedef enum { - RFAL_LM_STATE_NOT_INIT = 0x00, /*!< Not Initialized state */ - RFAL_LM_STATE_POWER_OFF = 0x01, /*!< Power Off state */ - RFAL_LM_STATE_IDLE = 0x02, /*!< Idle state Activity 1.1 5.2 */ - RFAL_LM_STATE_READY_A = 0x03, /*!< Ready A state Activity 1.1 5.3 5.4 & 5.5 */ - RFAL_LM_STATE_READY_B = 0x04, /*!< Ready B state Activity 1.1 5.11 5.12 */ - RFAL_LM_STATE_READY_F = 0x05, /*!< Ready F state Activity 1.1 5.15 */ - RFAL_LM_STATE_ACTIVE_A = 0x06, /*!< Active A state Activity 1.1 5.6 */ - RFAL_LM_STATE_CARDEMU_4A = 0x07, /*!< Card Emulation 4A state Activity 1.1 5.10 */ - RFAL_LM_STATE_CARDEMU_4B = 0x08, /*!< Card Emulation 4B state Activity 1.1 5.14 */ - RFAL_LM_STATE_CARDEMU_3 = 0x09, /*!< Card Emulation 3 state Activity 1.1 5.18 */ - RFAL_LM_STATE_TARGET_A = 0x0A, /*!< Target A state Activity 1.1 5.9 */ - RFAL_LM_STATE_TARGET_F = 0x0B, /*!< Target F state Activity 1.1 5.17 */ - RFAL_LM_STATE_SLEEP_A = 0x0C, /*!< Sleep A state Activity 1.1 5.7 */ - RFAL_LM_STATE_SLEEP_B = 0x0D, /*!< Sleep B state Activity 1.1 5.13 */ - RFAL_LM_STATE_READY_Ax = 0x0E, /*!< Ready A* state Activity 1.1 5.3 5.4 & 5.5 */ - RFAL_LM_STATE_ACTIVE_Ax = 0x0F, /*!< Active A* state Activity 1.1 5.6 */ - RFAL_LM_STATE_SLEEP_AF = 0x10, /*!< Sleep AF state Activity 1.1 5.19 */ -} rfalLmState; - -/*! RFAL Listen Mode Passive A configs */ -typedef struct { - rfalLmNfcidLen nfcidLen; /*!< NFCID Len (4, 7 or 10 bytes) */ - uint8_t nfcid[RFAL_NFCID1_TRIPLE_LEN]; /*!< NFCID */ - uint8_t SENS_RES[RFAL_LM_SENS_RES_LEN]; /*!< NFC-106k; SENS_REQ Response */ - uint8_t SEL_RES; /*!< SEL_RES (SAK) with complete NFCID1 (UID) */ -} rfalLmConfPA; - -/*! RFAL Listen Mode Passive B configs */ -typedef struct { - uint8_t SENSB_RES[RFAL_LM_SENSB_RES_LEN]; /*!< SENSF_RES */ -} rfalLmConfPB; - -/*! RFAL Listen Mode Passive F configs */ -typedef struct { - uint8_t SC[RFAL_LM_SENSF_SC_LEN]; /*!< System Code to listen for */ - uint8_t SENSF_RES[RFAL_LM_SENSF_RES_LEN]; /*!< SENSF_RES */ -} rfalLmConfPF; - -/*******************************************************************************/ - -/*******************************************************************************/ -/* Wake-Up Mode */ -/*******************************************************************************/ - -#define RFAL_WUM_REFERENCE_AUTO 0xFFU /*!< Indicates new reference is set by the driver*/ - -/*! RFAL Wake-Up Mode States */ -typedef enum { - RFAL_WUM_STATE_NOT_INIT = 0x00, /*!< Not Initialized state */ - RFAL_WUM_STATE_ENABLED = 0x01, /*!< Wake-Up mode is enabled */ - RFAL_WUM_STATE_ENABLED_WOKE = 0x02, /*!< Wake-Up mode enabled and has received IRQ(s)*/ -} rfalWumState; - -/*! RFAL Wake-Up Period/Timer */ -typedef enum { - RFAL_WUM_PERIOD_10MS = 0x00, /*!< Wake-Up timer 10ms */ - RFAL_WUM_PERIOD_20MS = 0x01, /*!< Wake-Up timer 20ms */ - RFAL_WUM_PERIOD_30MS = 0x02, /*!< Wake-Up timer 30ms */ - RFAL_WUM_PERIOD_40MS = 0x03, /*!< Wake-Up timer 40ms */ - RFAL_WUM_PERIOD_50MS = 0x04, /*!< Wake-Up timer 50ms */ - RFAL_WUM_PERIOD_60MS = 0x05, /*!< Wake-Up timer 60ms */ - RFAL_WUM_PERIOD_70MS = 0x06, /*!< Wake-Up timer 70ms */ - RFAL_WUM_PERIOD_80MS = 0x07, /*!< Wake-Up timer 80ms */ - RFAL_WUM_PERIOD_100MS = 0x10, /*!< Wake-Up timer 100ms */ - RFAL_WUM_PERIOD_200MS = 0x11, /*!< Wake-Up timer 200ms */ - RFAL_WUM_PERIOD_300MS = 0x12, /*!< Wake-Up timer 300ms */ - RFAL_WUM_PERIOD_400MS = 0x13, /*!< Wake-Up timer 400ms */ - RFAL_WUM_PERIOD_500MS = 0x14, /*!< Wake-Up timer 500ms */ - RFAL_WUM_PERIOD_600MS = 0x15, /*!< Wake-Up timer 600ms */ - RFAL_WUM_PERIOD_700MS = 0x16, /*!< Wake-Up timer 700ms */ - RFAL_WUM_PERIOD_800MS = 0x17, /*!< Wake-Up timer 800ms */ -} rfalWumPeriod; - -/*! RFAL Wake-Up Period/Timer */ -typedef enum { - RFAL_WUM_AA_WEIGHT_4 = 0x00, /*!< Wake-Up Auto Average Weight 4 */ - RFAL_WUM_AA_WEIGHT_8 = 0x01, /*!< Wake-Up Auto Average Weight 8 */ - RFAL_WUM_AA_WEIGHT_16 = 0x02, /*!< Wake-Up Auto Average Weight 16 */ - RFAL_WUM_AA_WEIGHT_32 = 0x03, /*!< Wake-Up Auto Average Weight 32 */ -} rfalWumAAWeight; - -/*! RFAL Wake-Up Mode configuration */ -typedef struct { - rfalWumPeriod period; /*!< Wake-Up Timer period;how often measurement(s) is performed */ - bool irqTout; /*!< IRQ at every timeout will refresh the measurement(s) */ - bool swTagDetect; /*!< Use SW Tag Detection instead of HW Wake-Up mode */ - - struct { - bool enabled; /*!< Inductive Amplitude measurement enabled */ - uint8_t delta; /*!< Delta between the reference and measurement to wake-up */ - uint16_t reference; /*!< Reference to be used;RFAL_WUM_REFERENCE_AUTO sets it auto */ - bool autoAvg; /*!< Use the HW Auto Averaging feature */ - bool aaInclMeas; /*!< When AutoAvg is enabled, include IRQ measurement */ - rfalWumAAWeight aaWeight; /*!< When AutoAvg is enabled, last measure weight */ - } indAmp; /*!< Inductive Amplitude Configuration */ - struct { - bool enabled; /*!< Inductive Phase measurement enabled */ - uint8_t delta; /*!< Delta between the reference and measurement to wake-up */ - uint16_t reference; /*!< Reference to be used;RFAL_WUM_REFERENCE_AUTO sets it auto */ - bool autoAvg; /*!< Use the HW Auto Averaging feature */ - bool aaInclMeas; /*!< When AutoAvg is enabled, include IRQ measurement */ - rfalWumAAWeight aaWeight; /*!< When AutoAvg is enabled, last measure weight */ - } indPha; /*!< Inductive Phase Configuration */ - struct { - bool enabled; /*!< Capacitive measurement enabled */ - uint8_t delta; /*!< Delta between the reference and measurement to wake-up */ - uint16_t reference; /*!< Reference to be used;RFAL_WUM_REFERENCE_AUTO sets it auto */ - bool autoAvg; /*!< Use the HW Auto Averaging feature */ - bool aaInclMeas; /*!< When AutoAvg is enabled, include IRQ measurement */ - rfalWumAAWeight aaWeight; /*!< When AutoAvg is enabled, last measure weight */ - } cap; /*!< Capacitive Configuration */ -} rfalWakeUpConfig; - -/*******************************************************************************/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief RFAL Initialize - * - * Initializes RFAL layer and the ST25R391x - * Ensures that ST25R391x is properly connected and returns error if any problem - * is detected - * - * \warning rfalAnalogConfigInitialize() should be called before so that - * the Analog config table has been previously initialized. - * - * \return ERR_HW_MISMATCH : Expected HW do not match or communication error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalInitialize(void); - -/*! - ***************************************************************************** - * \brief RFAL Calibrate - * - * Performs necessary calibration of RF chip in case it is indicated by current - * register settings. E.g. antenna calibration and regulator calibration - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalCalibrate(void); - -/*! - ***************************************************************************** - * \brief RFAL Adjust Regulators - * - * Adjusts ST25R391x regulators - * - * \param[out] result : the result of the calibrate antenna in mV - * NULL if result not requested - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalAdjustRegulators(uint16_t* result); - -/*! - ***************************************************************************** - * \brief RFAL Set System Callback - * - * Sets a callback for the driver to call when an event has occurred that - * may require the system to be notified - * - * \param[in] pFunc : method pointer for the upper layer callback - * - ***************************************************************************** - */ -void rfalSetUpperLayerCallback(rfalUpperLayerCallback pFunc); - -/*! - ***************************************************************************** - * \brief RFAL Set Pre Tx Callback - * - * Sets a callback for the driver to call before a Transceive - * - * \param[in] pFunc : method pointer for the Pre Tx callback - * - ***************************************************************************** - */ -void rfalSetPreTxRxCallback(rfalPreTxRxCallback pFunc); - -/*! - ***************************************************************************** - * \brief RFAL Set Post Tx Callback - * - * Sets a callback for the driver to call after a Transceive - * - * \param[in] pFunc : method pointer for the Post Tx callback - * - ***************************************************************************** - */ -void rfalSetPostTxRxCallback(rfalPostTxRxCallback pFunc); - -/** Set RFAL state changed callback - * - * @param cb RfalStateChangedCallback instance - * @param ctx pointer to context - */ -void rfal_set_state_changed_callback(RfalStateChangedCallback callback); - -/** Set callback context - * - * @param ctx pointer to context - */ -void rfal_set_callback_context(void* context); - -/*! - ***************************************************************************** - * \brief RFAL Deinitialize - * - * Deinitializes RFAL layer and the ST25R391x - * - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalDeinitialize(void); - -/*! - ***************************************************************************** - * \brief RFAL Set Mode - * - * Sets the mode that RFAL will operate on the following communications. - * Proper initializations will be performed on the ST25R391x - * - * \warning bit rate value RFAL_BR_KEEP is not allowed, only in rfalSetBitRate() - * - * \warning the mode will be applied immediately on the RFchip regardless of - * any ongoing operations like Transceive, ListenMode - * - * \param[in] mode : mode for the RFAL/RFchip to perform - * \param[in] txBR : transmit bit rate - * \param[in] rxBR : receive bit rate - * - * \see rfalIsGTExpired - * \see rfalMode - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalSetMode(rfalMode mode, rfalBitRate txBR, rfalBitRate rxBR); - -/*! - ***************************************************************************** - * \brief RFAL Get Mode - * - * Gets the mode that RFAL is set to operate - * - * \see rfalMode - * - * \return rfalMode : The current RFAL mode - ***************************************************************************** - */ -rfalMode rfalGetMode(void); - -/*! - ***************************************************************************** - * \brief RFAL Set Bit Rate - * - * Sets the Tx and Rx bit rates with the given values - * The bit rate change is applied on the RF chip remaining in the same - * mode previous defined with rfalSetMode() - * - * If no mode is defined bit rates will not be applied and an error - * is returned - * - * \param[in] txBR : transmit bit rate - * \param[in] rxBR : receive bit rate - * - * \see rfalSetMode - * \see rfalMode - * \see rfalBitRate - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_PARAM : Invalid parameter - * \return ERR_NOT_IMPLEMENTED : Mode not implemented - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalSetBitRate(rfalBitRate txBR, rfalBitRate rxBR); - -/*! - ***************************************************************************** - * \brief RFAL Get Bit Rate - * - * Gets the Tx and Rx current bit rates - * - * If RFAL is not initialized or mode not set the bit rates return will - * be invalid RFAL_BR_KEEP - * - * \param[out] txBR : RFAL's current Tx Bit Rate - * \param[out] rxBR : RFAL's current Rx Bit Rate - * - * \see rfalSetBitRate - * \see rfalBitRate - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalGetBitRate(rfalBitRate* txBR, rfalBitRate* rxBR); - -/*! - ***************************************************************************** - * \brief Set Error Handling Mode - * - * Sets the error handling mode to be used by the RFAL - * - * \param[in] eHandling : the error handling mode - * - ***************************************************************************** - */ -void rfalSetErrorHandling(rfalEHandling eHandling); - -/*! - ***************************************************************************** - * \brief Get Error Handling Mode - * - * Gets the error handling mode currently used by the RFAL - * - * \return rfalEHandling : Current error handling mode - ***************************************************************************** - */ -rfalEHandling rfalGetErrorHandling(void); - -/*! - ***************************************************************************** - * \brief Set Observation Mode - * - * Sets ST25R391x observation modes for RF debug purposes - * - * \param[in] txMode : the observation mode to be used during transmission - * \param[in] rxMode : the observation mode to be used during reception - * - * \warning The Observation Mode is an advanced feature and should be set - * according to the documentation of the part number in use. - * Please refer to the corresponding Datasheet or Application Note(s) - ***************************************************************************** - */ -void rfalSetObsvMode(uint8_t txMode, uint8_t rxMode); - -/*! - ***************************************************************************** - * \brief Get Observation Mode - * - * Gets ST25R391x the current configured observation modes - * - * \param[in] txMode : the current observation mode configured for transmission - * \param[in] rxMode : the current observation mode configured for reception - * - ***************************************************************************** - */ -void rfalGetObsvMode(uint8_t* txMode, uint8_t* rxMode); - -/*! - ***************************************************************************** - * \brief Disable Observation Mode - * - * Disables the ST25R391x observation mode - ***************************************************************************** - */ -void rfalDisableObsvMode(void); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Poll - * - * Sets the Frame Delay Time (FDT) to be used on the following - * communications. - * - * FDT Poll is the minimum time following a Poll Frame during - * which no subsequent Poll Frame can be sent (without a response from - * the Listener in between) - * FDTx,PP,MIN - Digital 1.1 6.10.2 & 7.9.2 & 8.7.2 - * - * \param[in] FDTPoll : Frame Delay Time in 1/fc cycles - * - ***************************************************************************** - */ -void rfalSetFDTPoll(uint32_t FDTPoll); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Poll - * - * Gets the current Frame Delay Time (FDT) - * - * FDT Poll is the minimum time following a Poll Frame during - * which no subsequent Poll Frame can be sent (without a response from - * the Listener in between) - * FDTx,PP,MIN - Digital 1.1 6.10.2 & 7.9.2 & 8.7.2 - * - * \return FDT : current FDT value in 1/fc cycles - * - ***************************************************************************** - */ -uint32_t rfalGetFDTPoll(void); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Listen - * - * Sets the Frame Delay Time (FDT) Listen minimum to be used on the - * following communications. - * - * FDT Listen is the minimum time between a Poll Frame and a Listen Frame - * FDTx,LISTEN,MIN - Digital 1.1 6.10.1 & 7.9.1 & 8.7.1 - * - * \param[in] FDTListen : Frame Delay Time in 1/fc cycles - * - ***************************************************************************** - */ -void rfalSetFDTListen(uint32_t FDTListen); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Listen - * - * Gets the Frame Delay Time (FDT) Listen minimum - * - * FDT Listen is the minimum time between a Poll Frame and a Listen Frame - * FDTx,LISTEN,MIN - Digital 1.1 6.10.1 & 7.9.1 & 8.7.1 - * - * \return FDT : current FDT value in 1/fc cycles - * - ***************************************************************************** - */ -uint32_t rfalGetFDTListen(void); - -/*! - ***************************************************************************** - * \brief RFAL Get GT - * - * Gets the current Guard Time (GT) - * - * GT is the minimum time when a device in Listen Mode is exposed to an - * unmodulated carrier - * - * \return GT : Guard Time in 1/fc cycles - * - ***************************************************************************** - */ -uint32_t rfalGetGT(void); - -/*! - ***************************************************************************** - * \brief RFAL Set GT - * - * Sets the Guard Time (GT) to be used on the following communications. - * - * GT is the minimum time when a device in Listen Mode is exposed to an - * unmodulated carrier - * - * \param[in] GT : Guard Time in 1/fc cycles - * RFAL_GT_NONE if no GT should be applied - * - ***************************************************************************** - */ -void rfalSetGT(uint32_t GT); - -/*! - ***************************************************************************** - * \brief RFAL Is GT expired - * - * Checks whether the GT timer has expired - * - * \return true : GT has expired or not running - * \return false : GT is still running - * - ***************************************************************************** - */ -bool rfalIsGTExpired(void); - -/*! - ***************************************************************************** - * \brief RFAL Turn Field On and Start GT - * - * Turns the Field On, performing Initial Collision Avoidance - * - * After Field On, if GT was set before, it starts the GT timer to be - * used on the following communications. - * - * \return ERR_RF_COLLISION : External field detected - * \return ERR_NONE : Field turned On - * - ***************************************************************************** - */ -ReturnCode rfalFieldOnAndStartGT(void); - -/*! - ***************************************************************************** - * \brief RFAL Turn Field Off - * - * Turns the Field Off - * - * \return ERR_NONE : Field turned Off - ***************************************************************************** - */ -ReturnCode rfalFieldOff(void); - -/***************************************************************************** - * Transceive * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief RFAL Set transceive context - * - * Set the context that will be used for the following Transceive - * Output and input buffers have to be passed and all other details prior to - * the Transceive itself has been started - * - * This method only sets the context, once set rfalWorker has - * to be executed until is done - * - * \param[in] ctx : the context for the following Transceive - * - * \see rfalWorker - * \see rfalGetTransceiveStatus - * - * \return ERR_NONE : Done with no error - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter or configuration - ***************************************************************************** - */ -ReturnCode rfalStartTransceive(const rfalTransceiveContext* ctx); - -/*! - ***************************************************************************** - * \brief Get Transceive State - * - * Gets current Transceive internal State - * - * \return rfalTransceiveState : the current Transceive internal State - ***************************************************************************** - */ -rfalTransceiveState rfalGetTransceiveState(void); - -/*! - ***************************************************************************** - * \brief Get Transceive Status - * - * Gets current Transceive status - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalGetTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief Is Transceive in Tx - * - * Checks if Transceive is in Transmission state - * - * \return true Transmission ongoing - * \return false Not in transmission state - ***************************************************************************** - */ -bool rfalIsTransceiveInTx(void); - -/*! - ***************************************************************************** - * \brief Is Transceive in Rx - * - * Checks if Transceive is in Reception state - * - * \return true Transmission done/reception ongoing - * \return false Not in reception state - ***************************************************************************** - */ -bool rfalIsTransceiveInRx(void); - -/*! - ***************************************************************************** - * \brief Get Transceive RSSI - * - * Gets the RSSI value of the last executed Transceive in mV - * - * \param[out] rssi : RSSI value - * - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalGetTransceiveRSSI(uint16_t* rssi); - -/*! - ***************************************************************************** - * \brief RFAL Worker - * - * This runs RFAL layer, which drives the actual Transceive procedure - * It MUST be executed frequently in order to execute the RFAL internal - * states and perform the requested operations - * - ***************************************************************************** - */ -void rfalWorker(void); - -/***************************************************************************** - * ISO1443A * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Transceives an ISO14443A ShortFrame - * - * Sends REQA to detect if there is any PICC in the field - * - * \param[in] txCmd: Command to be sent: - * 0x52 WUPA / ALL_REQ - * 0x26 REQA / SENS_REQ - * - * \param[in] txCmd : type of short frame to be sent REQA or WUPA - * \param[out] rxBuf : buffer to place the response - * \param[in] rxBufLen : length of rxBuf - * \param[out] rxRcvdLen: received length - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \warning If fwt is set to RFAL_FWT_NONE it will make endlessly for - * a response, which on a blocking method may not be the - * desired usage - * - * \return ERR_NONE if there is response - * \return ERR_TIMEOUT if there is no response - * \return ERR_COLLISION collision has occurred - * - ***************************************************************************** - */ -ReturnCode rfalISO14443ATransceiveShortFrame( - rfal14443AShortFrameCmd txCmd, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* rxRcvdLen, - uint32_t fwt); - -/*! - ***************************************************************************** - * \brief Sends an ISO14443A Anticollision Frame - * - * This is use to perform ISO14443A anti-collision. - * \note Anticollision is sent without CRC - * - * - * \param[in] buf : reference to ANTICOLLISION command (with known UID if any) to be sent (also out param) - * reception will be place on this buf after bytesToSend - * \param[in] bytesToSend: reference number of full bytes to be sent (including CMD byte and SEL_PAR) - * if a collision occurs will contain the number of clear bytes - * \param[in] bitsToSend : reference to number of bits (0-7) to be sent; and received (also out param) - * if a collision occurs will indicate the number of clear bits (also out param) - * \param[out] rxLength : reference to the return the received length - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \return ERR_NONE if there is no error - ***************************************************************************** - */ -ReturnCode rfalISO14443ATransceiveAnticollisionFrame( - uint8_t* buf, - uint8_t* bytesToSend, - uint8_t* bitsToSend, - uint16_t* rxLength, - uint32_t fwt); - -/***************************************************************************** - * FeliCa * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief FeliCa Poll - * - * Sends a Poll Request and collects all Poll Responses according to the - * given slots - * - * - * \param[in] slots : number of slots for the Poll Request - * \param[in] sysCode : system code (SC) for the Poll Request - * \param[in] reqCode : request code (RC) for the Poll Request - * \param[out] pollResList : list of all responses - * \param[in] pollResListSize : number of responses that can be placed in pollResList - * \param[out] devicesDetected : number of cards found - * \param[out] collisionsDetected: number of collisions detected - * - * \return ERR_NONE if there is no error - * \return ERR_TIMEOUT if there is no response - ***************************************************************************** - */ -ReturnCode rfalFeliCaPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* pollResList, - uint8_t pollResListSize, - uint8_t* devicesDetected, - uint8_t* collisionsDetected); - -/***************************************************************************** - * ISO15693 * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Sends an ISO15693 Anticollision Frame - * - * This send the Anticollision|Inventory frame (INVENTORY_REQ) - * - * \warning rxBuf must be able to contain the payload and CRC - * - * \param[in] txBuf : Buffer where outgoing message is located - * \param[in] txBufLen : Length of the outgoing message in bytes - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bits - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalISO15693TransceiveAnticollisionFrame( - uint8_t* txBuf, - uint8_t txBufLen, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* actLen); - -/*! - ***************************************************************************** - * \brief Sends an ISO15693 Anticollision EOF - * - * This sends the Anticollision|Inventory EOF used as a slot marker - * - * \warning rxBuf must be able to contain the payload and CRC - * - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bits - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode - rfalISO15693TransceiveEOFAnticollision(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen); - -/*! - ***************************************************************************** - * \brief Sends an ISO15693 EOF - * - * This is method sends an ISO15693 (EoF) used for a Write operation - * - * \warning rxBuf must be able to contain the payload and CRC - * - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bytes - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalISO15693TransceiveEOF(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen); - -/*! - ***************************************************************************** - * \brief Transceive Blocking Tx - * - * This is method triggers a Transceive and executes it blocking until the - * Tx has been completed - * - * \param[in] txBuf : Buffer where outgoing message is located - * \param[in] txBufLen : Length of the outgoing message in bytes - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bits - * \param[in] flags : TransceiveFlags indication special handling - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalTransceiveBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -/*! - ***************************************************************************** - * \brief Transceive Blocking Rx - * - * This is method executes the reception of an ongoing Transceive triggered - * before by rfalTransceiveBlockingTx() - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalTransceiveBlockingRx(void); - -/*! - ***************************************************************************** - * \brief Transceive Blocking - * - * This is method triggers a Transceive and executes it blocking until it - * has been completed - * - * \param[in] txBuf : Buffer where outgoing message is located - * \param[in] txBufLen : Length of the outgoing message in bytes - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bytes - * \param[in] flags : TransceiveFlags indication special handling - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalTransceiveBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -ReturnCode rfalTransceiveBitsBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -ReturnCode rfalTransceiveBitsBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -/***************************************************************************** - * Listen Mode * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Is external Field On - * - * Checks if external field (other peer/device) is on/detected - * - * \return true External field is On - * \return false No external field is detected - * - ***************************************************************************** - */ -bool rfalIsExtFieldOn(void); - -/*! - ***************************************************************************** - * \brief Listen Mode start - * - * Configures RF Chip to go into listen mode enabling the given technologies - * - * - * \param[in] lmMask: mask with the enabled/disabled listen modes - * use: RFAL_LM_MASK_NFCA ; RFAL_LM_MASK_NFCB ; - * RFAL_LM_MASK_NFCF ; RFAL_LM_MASK_ACTIVE_P2P - * \param[in] confA: pointer to Passive A configurations (NULL if disabled) - * \param[in] confB: pointer to Passive B configurations (NULL if disabled) - * \param[in] confF: pointer to Passive F configurations (NULL if disabled) - * \param[in] rxBuf: buffer to place incoming data - * \param[in] rxBufLen: length in bits of rxBuf - * \param[in] rxLen: pointer to write the data length in bits placed into rxBuf - * - * - * \return ERR_PARAM Invalid parameter - * \return ERR_REQUEST Invalid listen mode mask - * \return ERR_NONE Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalListenStart( - uint32_t lmMask, - const rfalLmConfPA* confA, - const rfalLmConfPB* confB, - const rfalLmConfPF* confF, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxLen); - -/*! - ***************************************************************************** - * \brief Listen Mode start Sleeping - * - * - ***************************************************************************** - */ -ReturnCode - rfalListenSleepStart(rfalLmState sleepSt, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxLen); - -/*! - ***************************************************************************** - * \brief Listen Mode Stop - * - * Disables the listen mode on the RF Chip - * - * \warning the listen mode will be disabled immediately on the RFchip regardless - * of any ongoing operations like Transceive - * - * \return ERR_NONE Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalListenStop(void); - -/*! - ***************************************************************************** - * \brief Listen Mode get state - * - * Sets the new state of the Listen Mode and applies the necessary changes - * on the RF Chip - * - * \param[out] dataFlag: indicates that Listen Mode has rcvd data and caller - * must process it. The received message is located - * at the rxBuf passed on rfalListenStart(). - * rfalListenSetState() will clear this flag - * if NULL output parameter will no be written/returned - * \param[out] lastBR: bit rate detected of the last initiator request - * if NULL output parameter will no be written/returned - * - * \return rfalLmState RFAL_LM_STATE_NOT_INIT : LM not initialized properly - * Any Other : LM State - * - ***************************************************************************** - */ -rfalLmState rfalListenGetState(bool* dataFlag, rfalBitRate* lastBR); - -/*! - ***************************************************************************** - * \brief Listen Mode set state - * - * Sets the new state of the Listen Mode and applies the necessary changes - * on the RF Chip - * - * \param[in] newSt : New state to go to - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalListenSetState(rfalLmState newSt); - -/***************************************************************************** - * Wake-Up Mode * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Wake-Up Mode Start - * - * Sets the RF Chip in Low Power Wake-Up Mode according to the given - * configuration. - * - * \param[in] config : Generic Wake-Up configuration provided by lower - * layers. If NULL will automatically configure the - * Wake-Up mode - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalWakeUpModeStart(const rfalWakeUpConfig* config); - -/*! - ***************************************************************************** - * \brief Wake-Up Has Woke - * - * Returns true if the Wake-Up mode is enabled and it has already received - * the indication from the RF Chip that the surrounding environment has changed - * and flagged at least one wake-Up interrupt - * - * \return true : Wake-Up mode enabled and has received a wake-up IRQ - * \return false : no Wake-Up IRQ has been received - * - ***************************************************************************** - */ -bool rfalWakeUpModeHasWoke(void); - -/*! - ***************************************************************************** - * \brief Wake-Up Mode Stop - * - * Stops the Wake-Up Mode - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalWakeUpModeStop(void); - -/*! - ***************************************************************************** - * \brief Low Power Mode Start - * - * Sets the RF Chip in Low Power Mode. - * In this mode the RF Chip is placed in Low Power Mode, similar to Wake-up - * mode but no operation nor period measurement is performed. - * Mode must be terminated by rfalLowPowerModeStop() - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalLowPowerModeStart(void); - -/*! - ***************************************************************************** - * \brief Low Power Mode Stop - * - * Stops the Low Power Mode re-enabling the device - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalLowPowerModeStop(void); - -#endif /* RFAL_RF_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_st25tb.h b/lib/ST25RFAL002/include/rfal_st25tb.h deleted file mode 100644 index dcd7baa578..0000000000 --- a/lib/ST25RFAL002/include/rfal_st25tb.h +++ /dev/null @@ -1,340 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25tb.h - * - * \author Gustavo Patricio - * - * \brief Implementation of ST25TB interface - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup ST25TB - * \brief RFAL ST25TB Module - * @{ - * - */ - -#ifndef RFAL_ST25TB_H -#define RFAL_ST25TB_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_nfcb.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_ST25TB_CHIP_ID_LEN 1U /*!< ST25TB chip ID length */ -#define RFAL_ST25TB_CRC_LEN 2U /*!< ST25TB CRC length */ -#define RFAL_ST25TB_UID_LEN 8U /*!< ST25TB Unique ID length */ -#define RFAL_ST25TB_BLOCK_LEN 4U /*!< ST25TB Data Block length */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ -typedef uint8_t rfalSt25tbUID[RFAL_ST25TB_UID_LEN]; /*!< ST25TB UID type */ -typedef uint8_t rfalSt25tbBlock[RFAL_ST25TB_BLOCK_LEN]; /*!< ST25TB Block type */ - -/*! ST25TB listener device (PICC) struct */ -typedef struct { - uint8_t chipID; /*!< Device's session Chip ID */ - rfalSt25tbUID UID; /*!< Device's UID */ - bool isDeselected; /*!< Device deselect flag */ -} rfalSt25tbListenDevice; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize ST25TB Poller mode - * - * This methods configures RFAL RF layer to perform as a - * ST25TB Poller/RW including all default timings - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Check Presence - * - * This method checks if a ST25TB Listen device (PICC) is present on the field - * by sending an Initiate command - * - * \param[out] chipId : if successfully retrieved, the device's chip ID - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerCheckPresence(uint8_t* chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Collision Resolution - * - * This method performs ST25TB Collision resolution, selects the each device, - * retrieves its UID and then deselects. - * In case only one device is identified the ST25TB device is left in select - * state. - * - * \param[in] devLimit : device limit value, and size st25tbDevList - * \param[out] st25tbDevList : ST35TB listener device info - * \param[out] devCnt : Devices found counter - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Initiate - * - * This method sends an Initiate command - * - * If a single device responds the chip ID will be retrieved - * - * \param[out] chipId : chip ID of the device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerInitiate(uint8_t* chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Pcall - * - * This method sends a Pcall command - * If successful the device's chip ID will be retrieved - * - * \param[out] chipId : Chip ID of the device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerPcall(uint8_t* chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Slot Marker - * - * This method sends a Slot Marker - * - * If a single device responds the chip ID will be retrieved - * - * \param[in] slotNum : Slot Number - * \param[out] chipIdRes : Chip ID of the device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerSlotMarker(uint8_t slotNum, uint8_t* chipIdRes); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Select - * - * This method sends a ST25TB Select command with the given chip ID. - * - * If the device is already in Selected state and receives an incorrect chip - * ID, it goes into Deselected state - * - * \param[in] chipId : chip ID of the device to be selected - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerSelect(uint8_t chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Get UID - * - * This method sends a Get_UID command - * - * If a single device responds the chip UID will be retrieved - * - * \param[out] UID : UID of the found device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerGetUID(rfalSt25tbUID* UID); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Read Block - * - * This method reads a block of the ST25TB - * - * \param[in] blockAddress : address of the block to be read - * \param[out] blockData : location to place the data read from block - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerReadBlock(uint8_t blockAddress, rfalSt25tbBlock* blockData); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Write Block - * - * This method writes a block of the ST25TB - * - * \param[in] blockAddress : address of the block to be written - * \param[in] blockData : data to be written on the block - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerWriteBlock(uint8_t blockAddress, const rfalSt25tbBlock* blockData); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Completion - * - * This method sends a completion command to the ST25TB. After the - * completion the card no longer will reply to any command. - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected, invalid SENSB_RES received - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerCompletion(void); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Reset to Inventory - * - * This method sends a Reset to Inventory command to the ST25TB. - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected, invalid SENSB_RES received - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerResetToInventory(void); - -#endif /* RFAL_ST25TB_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_st25xv.h b/lib/ST25RFAL002/include/rfal_st25xv.h deleted file mode 100644 index d7bec83779..0000000000 --- a/lib/ST25RFAL002/include/rfal_st25xv.h +++ /dev/null @@ -1,844 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25xv.h - * - * \author Gustavo Patricio - * - * \brief NFC-V ST25 NFC-V Tag specific features - * - * This module provides support for ST's specific features available on - * NFC-V (ISO15693) tag families: ST25D, ST25TV, M24LR - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup ST25xV - * \brief RFAL ST25xV Module - * @{ - * - */ - -#ifndef RFAL_ST25xV_H -#define RFAL_ST25xV_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_nfc.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCV_BLOCKNUM_M24LR_LEN \ - 2U /*!< Block Number length of MR24LR tags: 16 bits */ -#define RFAL_NFCV_ST_IC_MFG_CODE \ - 0x02 /*!< ST IC Mfg code (used for custom commands) */ - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Single Block (M24LR) - * - * Reads a Single Block from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * default: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Single Block (M24LR) - * - * Reads a Single Block from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * default: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Single Block (M24LR) - * - * Writes a Single Block from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be written - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to write (16 bits) - * \param[in] wrData : data to be written on the given block - * \param[in] blockLen : number of bytes of a block - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - const uint8_t* wrData, - uint8_t blockLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Multiple Blocks (M24LR) - * - * Reads Multiple Blocks from a device from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read (16 bits) - * \param[in] numOfBlocks : number of block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Multiple Blocks (M24LR) - * - * Reads Multiple Blocks from a device from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read (16 bits) - * \param[in] numOfBlocks : number of block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Single Block - * - * Reads a Single Block from a device (VICC) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Multiple Blocks - * - * Reads Multiple Blocks from a device (VICC) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read - * \param[in] numOfBlocks : number of block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Extended Read Single Block - * - * Reads a Single Block from a device (VICC) supporting extended commands using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastExtendedReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Extended Read Multiple Blocks - * - * Reads Multiple Blocks from a device (VICC) supporting extended commands using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read (16 bits) - * \param[in] numOfBlocks : number of consecutive blocks to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastExtReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Configuration - * - * Reads static configuration registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[out] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Configuration - * - * Writes static configuration registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[in] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerWriteConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Dynamic Configuration - * - * Reads dynamic registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[out] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Dynamic Configuration - * - * Writes dynamic registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[in] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Dynamic Configuration - * - * Reads dynamic registers at the Pointer address using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[out] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Write Dynamic Configuration - * - * Writes dynamic registers at the Pointer address using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[in] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Present Password - * - * Sends the Present Password command - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pwdNum : Password number - * \param[in] pwd : Password - * \param[in] pwdLen : Password length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerPresentPassword( - uint8_t flags, - const uint8_t* uid, - uint8_t pwdNum, - const uint8_t* pwd, - uint8_t pwdLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Get Random Number - * - * Returns a 16 bit random number - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerGetRandomNumber( - uint8_t flags, - const uint8_t* uid, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Message length - * - * Sends a Read Message Length message to retrieve the value of MB_LEN_Dyn - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[out] msgLen : Message Length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadMessageLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Message length - * - * Sends a Fast Read Message Length message to retrieve the value of MB_LEN_Dyn using ST Fast mode. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[out] msgLen : Message Length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadMsgLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Message - * - * Reads up to 256 bytes in the Mailbox from the location - * specified by MBpointer and sends back their value in the rxBuf response. - * First MailBox location is '00'. When Number of bytes is set to 00h - * and MBPointer is equals to 00h, the MB_LEN bytes of the full message - * are returned. Otherwise, Read Message command returns (Number of Bytes + 1) bytes - * (i.e. 01h returns 2 bytes, FFh returns 256 bytes). - * An error is reported if (Pointer + Nb of bytes + 1) is greater than the message length. - * RF Reading of the last byte of the mailbox message automatically clears b1 - * of MB_CTRL_Dyn HOST_PUT_MSG, and allows RF to put a new message. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] mbPointer : MPpointer - * \param[in] numBytes : number of bytes - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Message - * - * Reads up to 256 bytes in the Mailbox from the location - * specified by MBpointer and sends back their value in the rxBuf response using ST Fast mode. - * First MailBox location is '00'. When Number of bytes is set to 00h - * and MBPointer is equals to 00h, the MB_LEN bytes of the full message - * are returned. Otherwise, Read Message command returns (Number of Bytes + 1) bytes - * (i.e. 01h returns 2 bytes, FFh returns 256 bytes). - * An error is reported if (Pointer + Nb of bytes + 1) is greater than the message length. - * RF Reading of the last byte of the mailbox message automatically clears b1 - * of MB_CTRL_Dyn HOST_PUT_MSG, and allows RF to put a new message. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] mbPointer : MPpointer - * \param[in] numBytes : number of bytes - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Message - * - * Sends Write message Command - * - * On receiving the Write Message command, the ST25DVxxx puts the data contained - * in the request into the Mailbox buffer, update the MB_LEN_Dyn register, and - * set bit RF_PUT_MSG in MB_CTRL_Dyn register. It then reports if the write operation was successful - * in the response. The ST25DVxxx Mailbox contains up to 256 data bytes which are filled from the - * first location '00'. MSGlength parameter of the command is the number of - * Data bytes minus 1 (00 for 1 byte of data, FFh for 256 bytes of data). - * Write Message could be executed only when Mailbox is accessible by RF. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] msgLen : MSGLen number of Data bytes minus 1 - * \param[in] msgData : Message Data - * \param[out] txBuf : buffer to used to build the Write Message command - * \param[in] txBufLen : length of txBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Write Message - * - * Sends Fast Write message Command using ST Fast mode - * - * On receiving the Write Message command, the ST25DVxxx puts the data contained - * in the request into the Mailbox buffer, update the MB_LEN_Dyn register, and - * set bit RF_PUT_MSG in MB_CTRL_Dyn register. It then reports if the write operation was successful - * in the response. The ST25DVxxx Mailbox contains up to 256 data bytes which are filled from the - * first location '00'. MSGlength parameter of the command is the number of - * Data bytes minus 1 (00 for 1 byte of data, FFh for 256 bytes of data). - * Write Message could be executed only when Mailbox is accessible by RF. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] msgLen : MSGLen number of Data bytes minus 1 - * \param[in] msgData : Message Data - * \param[out] txBuf : buffer to used to build the Write Message command - * \param[in] txBufLen : length of txBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen); - -#endif /* RFAL_ST25xV_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_t1t.h b/lib/ST25RFAL002/include/rfal_t1t.h deleted file mode 100644 index e9e39d23c2..0000000000 --- a/lib/ST25RFAL002/include/rfal_t1t.h +++ /dev/null @@ -1,178 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t1t.h - * - * \author Gustavo Patricio - * - * \brief Provides NFC-A T1T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 1 Tag T1T (Topaz) - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup T1T - * \brief RFAL T1T Module - * @{ - * - */ - -#ifndef RFAL_T1T_H -#define RFAL_T1T_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_T1T_UID_LEN 4 /*!< T1T UID length of cascade level 1 only tag */ -#define RFAL_T1T_HR_LENGTH 2 /*!< T1T HR(Header ROM) length */ - -#define RFAL_T1T_HR0_NDEF_MASK 0xF0 /*!< T1T HR0 NDEF capability mask T1T 1.2 2.2.2 */ -#define RFAL_T1T_HR0_NDEF_SUPPORT 0x10 /*!< T1T HR0 NDEF capable value T1T 1.2 2.2.2 */ - -/*! NFC-A T1T (Topaz) command set */ -typedef enum { - RFAL_T1T_CMD_RID = 0x78, /*!< T1T Read UID */ - RFAL_T1T_CMD_RALL = 0x00, /*!< T1T Read All */ - RFAL_T1T_CMD_READ = 0x01, /*!< T1T Read */ - RFAL_T1T_CMD_WRITE_E = 0x53, /*!< T1T Write with erase (single byte) */ - RFAL_T1T_CMD_WRITE_NE = 0x1A /*!< T1T Write with no erase (single byte) */ -} rfalT1Tcmds; - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A T1T (Topaz) RID_RES Digital 1.1 10.6.2 & Table 50 */ -typedef struct { - uint8_t hr0; /*!< T1T Header ROM: HR0 */ - uint8_t hr1; /*!< T1T Header ROM: HR1 */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< T1T UID */ -} rfalT1TRidRes; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-A T1T Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-A T1T Poller/RW (Topaz) including all default timings - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT1TPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief NFC-A T1T Poller RID - * - * This method reads the UID of a NFC-A T1T Listener device - * - * - * \param[out] ridRes : pointer to place the RID_RES - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT1TPollerRid(rfalT1TRidRes* ridRes); - -/*! - ***************************************************************************** - * \brief NFC-A T1T Poller RALL - * - * This method send a Read All command to a NFC-A T1T Listener device - * - * - * \param[in] uid : the UID of the device to read data - * \param[out] rxBuf : pointer to place the read data - * \param[in] rxBufLen : size of rxBuf - * \param[out] rxRcvdLen : actual received data - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode - rfalT1TPollerRall(const uint8_t* uid, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxRcvdLen); - -/*! - ***************************************************************************** - * \brief NFC-A T1T Poller Write - * - * This method writes the given data on the address of a NFC-A T1T Listener device - * - * - * \param[in] uid : the UID of the device to read data - * \param[in] address : address to write the data - * \param[in] data : the data to be written - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT1TPollerWrite(const uint8_t* uid, uint8_t address, uint8_t data); - -#endif /* RFAL_T1T_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_t2t.h b/lib/ST25RFAL002/include/rfal_t2t.h deleted file mode 100644 index b90462fd2c..0000000000 --- a/lib/ST25RFAL002/include/rfal_t2t.h +++ /dev/null @@ -1,150 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t2t.h - * - * \author Gustavo Patricio - * - * \brief Provides NFC-A T2T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 2 Tag T2T - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup T2T - * \brief RFAL T2T Module - * @{ - * - */ - -#ifndef RFAL_T2T_H -#define RFAL_T2T_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_T2T_BLOCK_LEN 4U /*!< T2T block length */ -#define RFAL_T2T_READ_DATA_LEN (4U * RFAL_T2T_BLOCK_LEN) /*!< T2T READ data length */ -#define RFAL_T2T_WRITE_DATA_LEN RFAL_T2T_BLOCK_LEN /*!< T2T WRITE data length */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief NFC-A T2T Poller Read - * - * This method sends a Read command to a NFC-A T2T Listener device - * - * - * \param[in] blockNum : Number of the block to read - * \param[out] rxBuf : pointer to place the read data - * \param[in] rxBufLen : size of rxBuf (RFAL_T2T_READ_DATA_LEN) - * \param[out] rcvLen : actual received data - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode - rfalT2TPollerRead(uint8_t blockNum, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-A T2T Poller Write - * - * This method sends a Write command to a NFC-A T2T Listener device - * - * - * \param[in] blockNum : Number of the block to write - * \param[in] wrData : data to be written on the given block - * size must be of RFAL_T2T_WRITE_DATA_LEN - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT2TPollerWrite(uint8_t blockNum, const uint8_t* wrData); - -/*! - ***************************************************************************** - * \brief NFC-A T2T Poller Sector Select - * - * This method sends a Sector Select commands to a NFC-A T2T Listener device - * - * \param[in] sectorNum : Sector Number - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT2TPollerSectorSelect(uint8_t sectorNum); - -#endif /* RFAL_T2T_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_t4t.h b/lib/ST25RFAL002/include/rfal_t4t.h deleted file mode 100644 index 454cad8e00..0000000000 --- a/lib/ST25RFAL002/include/rfal_t4t.h +++ /dev/null @@ -1,395 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t4t.h - * - * \author Gustavo Patricio - * - * \brief Provides convenience methods and definitions for T4T (ISO7816-4) - * - * This module provides an interface to exchange T4T APDUs according to - * NFC Forum T4T and ISO7816-4 - * - * This implementation was based on the following specs: - * - ISO/IEC 7816-4 3rd Edition 2013-04-15 - * - NFC Forum T4T Technical Specification 1.0 2017-08-28 - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup T4T - * \brief RFAL T4T Module - * @{ - * - */ - -#ifndef RFAL_T4T_H -#define RFAL_T4T_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_isoDep.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_T4T_MAX_CAPDU_PROLOGUE_LEN \ - 4U /*!< Command-APDU prologue length (CLA INS P1 P2) */ -#define RFAL_T4T_LE_LEN 1U /*!< Le Expected Response Length (short field coding) */ -#define RFAL_T4T_LC_LEN 1U /*!< Lc Data field length (short field coding) */ -#define RFAL_T4T_MAX_RAPDU_SW1SW2_LEN \ - 2U /*!< SW1 SW2 length */ -#define RFAL_T4T_CLA 0x00U /*!< Class byte (contains 00h because secure message are not used) */ - -#define RFAL_T4T_ISO7816_P1_SELECT_BY_DF_NAME \ - 0x04U /*!< P1 value for Select by name */ -#define RFAL_T4T_ISO7816_P1_SELECT_BY_FILEID \ - 0x00U /*!< P1 value for Select by file identifier */ -#define RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE \ - 0x00U /*!< b2b1 P2 value for First or only occurrence */ -#define RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE \ - 0x00U /*!< b4b3 P2 value for Return FCI template */ -#define RFAL_T4T_ISO7816_P2_SELECT_NO_RESPONSE_DATA \ - 0x0CU /*!< b4b3 P2 value for No response data */ - -#define RFAL_T4T_ISO7816_STATUS_COMPLETE \ - 0x9000U /*!< Command completed \ Normal processing - No further qualification*/ - -/* -****************************************************************************** -* GLOBAL VARIABLES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ -/*! NFC-A T4T Command-APDU structure */ -typedef struct { - uint8_t CLA; /*!< Class byte */ - uint8_t INS; /*!< Instruction byte */ - uint8_t P1; /*!< Parameter byte 1 */ - uint8_t P2; /*!< Parameter byte 2 */ - uint8_t Lc; /*!< Data field length */ - bool LcFlag; /*!< Lc flag (append Lc when true) */ - uint8_t Le; /*!< Expected Response Length */ - bool LeFlag; /*!< Le flag (append Le when true) */ - - rfalIsoDepApduBufFormat* cApduBuf; /*!< Command-APDU buffer (Tx) */ - uint16_t* cApduLen; /*!< Command-APDU Length */ -} rfalT4tCApduParam; - -/*! NFC-A T4T Response-APDU structure */ -typedef struct { - rfalIsoDepApduBufFormat* rApduBuf; /*!< Response-APDU buffer (Rx) */ - uint16_t rcvdLen; /*!< Full response length */ - uint16_t rApduBodyLen; /*!< Response body length */ - uint16_t statusWord; /*!< R-APDU Status Word SW1|SW2 */ -} rfalT4tRApduParam; - -/*! NFC-A T4T command set T4T 1.0 & ISO7816-4 2013 Table 4 */ -typedef enum { - RFAL_T4T_INS_SELECT = 0xA4U, /*!< T4T Select */ - RFAL_T4T_INS_READBINARY = 0xB0U, /*!< T4T ReadBinary */ - RFAL_T4T_INS_UPDATEBINARY = 0xD6U, /*!< T4T UpdateBinay */ - RFAL_T4T_INS_READBINARY_ODO = 0xB1U, /*!< T4T ReadBinary using ODO */ - RFAL_T4T_INS_UPDATEBINARY_ODO = - 0xD7U /*!< T4T UpdateBinay using ODO */ -} rfalT4tCmds; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief T4T Compose APDU - * - * This method computes a C-APDU according to NFC Forum T4T and ISO7816-4. - * - * If C-APDU contains data to be sent, it must be placed inside the buffer - * rfalT4tTxRxApduParam.txRx.cApduBuf.apdu and signaled by Lc - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * \see rfalT4TPollerParseRAPDU() - * - * \warning The ISO-DEP module is used to perform the tranceive. Usually - * activation has been done via ISO-DEP activatiavtion. If not - * please call rfalIsoDepInitialize() before. - * - * \param[in,out] apduParam : APDU parameters - * apduParam.cApduLen will contain the APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeCAPDU(const rfalT4tCApduParam* apduParam); - -/*! - ***************************************************************************** - * \brief T4T Parse R-APDU - * - * This method parses a R-APDU according to NFC Forum T4T and ISO7816-4. - * It will extract the data length and check if the Status word is expected. - * - * \param[in,out] apduParam : APDU parameters - * apduParam.rApduBodyLen will contain the data length - * apduParam.statusWord will contain the SW1 and SW2 - * - * \return ERR_REQUEST : Status word (SW1 SW2) different from 9000 - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerParseRAPDU(rfalT4tRApduParam* apduParam); - -/*! - ***************************************************************************** - * \brief T4T Compose Select Application APDU - * - * This method computes a Select Application APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] aid : Application ID to be used - * \param[in] aidLen : Application ID length - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeSelectAppl( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* aid, - uint8_t aidLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Select File APDU - * - * This method computes a Select File APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] fid : File ID to be used - * \param[in] fidLen : File ID length - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeSelectFile( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Select File APDU for Mapping Version 1 - * - * This method computes a Select File APDU according to NFC Forum T4TOP_v1.0 - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] fid : File ID to be used - * \param[in] fidLen : File ID length - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeSelectFileV1Mapping( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Read Data APDU - * - * This method computes a Read Data APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] expLen : Expected length (Le) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeReadData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - uint8_t expLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Read Data ODO APDU - * - * This method computes a Read Data ODO APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] expLen : Expected length (Le) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeReadDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - uint8_t expLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Write Data APDU - * - * This method computes a Write Data APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] data : Data to be written - * \param[in] dataLen : Data length to be written (Lc) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeWriteData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Write Data ODO APDU - * - * This method computes a Write Data ODO sAPDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] data : Data to be written - * \param[in] dataLen : Data length to be written (Lc) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeWriteDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen); - -#endif /* RFAL_T4T_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c deleted file mode 100644 index 5fe65ef880..0000000000 --- a/lib/ST25RFAL002/platform.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "platform.h" -#include -#include -#include - -typedef struct { - FuriThread* thread; - volatile PlatformIrqCallback callback; - bool need_spi_lock; -} RfalPlatform; - -static volatile RfalPlatform rfal_platform = { - .thread = NULL, - .callback = NULL, - .need_spi_lock = true, -}; - -void nfc_isr(void* _ctx) { - UNUSED(_ctx); - if(rfal_platform.callback && platformGpioIsHigh(ST25R_INT_PORT, ST25R_INT_PIN)) { - furi_thread_flags_set(furi_thread_get_id(rfal_platform.thread), 0x1); - } -} - -int32_t rfal_platform_irq_thread(void* context) { - UNUSED(context); - - while(1) { - uint32_t flags = furi_thread_flags_wait(0x1, FuriFlagWaitAny, FuriWaitForever); - if(flags & 0x1) { - rfal_platform.callback(); - } - } -} - -void platformEnableIrqCallback() { - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInterruptRise, GpioPullDown, GpioSpeedLow); - furi_hal_gpio_enable_int_callback(&gpio_nfc_irq_rfid_pull); -} - -void platformDisableIrqCallback() { - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); -} - -void platformSetIrqCallback(PlatformIrqCallback callback) { - rfal_platform.callback = callback; - - if(!rfal_platform.thread) { - rfal_platform.thread = - furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); - furi_thread_mark_as_service(rfal_platform.thread); - furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); - furi_thread_start(rfal_platform.thread); - - furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); - // Disable interrupt callback as the pin is shared between 2 apps - // It is enabled in rfalLowPowerModeStop() - furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); - } -} - -bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) { - bool ret = false; - if(txBuf && rxBuf) { - ret = - furi_hal_spi_bus_trx(&furi_hal_spi_bus_handle_nfc, (uint8_t*)txBuf, rxBuf, len, 1000); - } else if(txBuf) { - ret = furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_nfc, (uint8_t*)txBuf, len, 1000); - } else if(rxBuf) { - ret = furi_hal_spi_bus_rx(&furi_hal_spi_bus_handle_nfc, (uint8_t*)rxBuf, len, 1000); - } - - return ret; -} - -// Until we completely remove RFAL, NFC works with SPI from rfal_platform_irq_thread and nfc_worker -// threads. Some nfc features already stop using RFAL and work with SPI from nfc_worker only. -// rfal_platform_spi_acquire() and rfal_platform_spi_release() functions are used to lock SPI for a -// long term without locking it for each SPI transaction. This is needed for time critical communications. -void rfal_platform_spi_acquire() { - platformDisableIrqCallback(); - rfal_platform.need_spi_lock = false; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); -} - -void rfal_platform_spi_release() { - furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); - rfal_platform.need_spi_lock = true; - platformEnableIrqCallback(); -} - -void platformProtectST25RComm() { - if(rfal_platform.need_spi_lock) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); - } -} - -void platformUnprotectST25RComm() { - if(rfal_platform.need_spi_lock) { - furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); - } -} diff --git a/lib/ST25RFAL002/platform.h b/lib/ST25RFAL002/platform.h deleted file mode 100644 index d86bba73e3..0000000000 --- a/lib/ST25RFAL002/platform.h +++ /dev/null @@ -1,188 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "timer.h" -#include "math.h" -#include -#include -#include - -typedef void (*PlatformIrqCallback)(); -void platformSetIrqCallback(PlatformIrqCallback cb); -void platformEnableIrqCallback(); -void platformDisableIrqCallback(); - -bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len); -void platformProtectST25RComm(); -void platformUnprotectST25RComm(); -void rfal_platform_spi_acquire(); -void rfal_platform_spi_release(); - -#define ST25R_SS_PIN NFC_CS_Pin -#define ST25R_SS_PORT NFC_CS_GPIO_Port - -#define ST25R_INT_PIN NFC_IRQ_Pin -#define ST25R_INT_PORT NFC_IRQ_GPIO_Port - -#define RFAL_ANALOG_CONFIG_CUSTOM \ - true /*!< Enable/Disable RFAL custom analog configuration */ - -#define RFAL_FEATURE_LISTEN_MODE \ - true /*!< Enable/Disable RFAL support for Listen Mode */ -#define RFAL_FEATURE_WAKEUP_MODE \ - true /*!< Enable/Disable RFAL support for the Wake-Up mode */ -#define RFAL_FEATURE_LOWPOWER_MODE \ - true /*!< Enable/Disable RFAL support for the Low Power mode */ -#define RFAL_FEATURE_NFCA \ - true /*!< Enable/Disable RFAL support for NFC-A (ISO14443A) */ -#define RFAL_FEATURE_NFCB \ - true /*!< Enable/Disable RFAL support for NFC-B (ISO14443B) */ -#define RFAL_FEATURE_NFCF \ - true /*!< Enable/Disable RFAL support for NFC-F (FeliCa) */ -#define RFAL_FEATURE_NFCV \ - true /*!< Enable/Disable RFAL support for NFC-V (ISO15693) */ -#define RFAL_FEATURE_T1T \ - true /*!< Enable/Disable RFAL support for T1T (Topaz) */ -#define RFAL_FEATURE_T2T \ - true /*!< Enable/Disable RFAL support for T2T (MIFARE Ultralight) */ -#define RFAL_FEATURE_T4T \ - true /*!< Enable/Disable RFAL support for T4T */ -#define RFAL_FEATURE_ST25TB \ - true /*!< Enable/Disable RFAL support for ST25TB */ -#define RFAL_FEATURE_ST25xV \ - true /*!< Enable/Disable RFAL support for ST25TV/ST25DV */ -#define RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG \ - false /*!< Enable/Disable Analog Configs to be dynamically updated (RAM) */ -#define RFAL_FEATURE_DPO \ - false /*!< Enable/Disable RFAL Dynamic Power Output upport */ -#define RFAL_FEATURE_ISO_DEP \ - true /*!< Enable/Disable RFAL support for ISO-DEP (ISO14443-4) */ -#define RFAL_FEATURE_ISO_DEP_POLL \ - true /*!< Enable/Disable RFAL support for Poller mode (PCD) ISO-DEP (ISO14443-4) */ -#define RFAL_FEATURE_ISO_DEP_LISTEN \ - true /*!< Enable/Disable RFAL support for Listen mode (PICC) ISO-DEP (ISO14443-4) */ -#define RFAL_FEATURE_NFC_DEP \ - true /*!< Enable/Disable RFAL support for NFC-DEP (NFCIP1/P2P) */ - -#define RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN \ - 256U /*!< ISO-DEP I-Block max length. Please use values as defined by rfalIsoDepFSx */ -#define RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN \ - 254U /*!< NFC-DEP Block/Payload length. Allowed values: 64, 128, 192, 254 */ -#define RFAL_FEATURE_NFC_RF_BUF_LEN \ - 256U /*!< RF buffer length used by RFAL NFC layer */ - -#define RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN \ - 512U /*!< ISO-DEP APDU max length. */ -#define RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN \ - 512U /*!< NFC-DEP PDU max length. */ - -#define platformIrqST25RSetCallback(cb) platformSetIrqCallback(cb) - -#define platformProtectST25RIrqStatus() \ - platformProtectST25RComm() /*!< Protect unique access to IRQ status var - IRQ disable on single thread environment (MCU) ; Mutex lock on a multi thread environment */ -#define platformUnprotectST25RIrqStatus() \ - platformUnprotectST25RComm() /*!< Unprotect the IRQ status var - IRQ enable on a single thread environment (MCU) ; Mutex unlock on a multi thread environment */ - -#define platformGpioSet(port, pin) \ - furi_hal_gpio_write_port_pin( \ - port, pin, true) /*!< Turns the given GPIO High */ -#define platformGpioClear(port, pin) \ - furi_hal_gpio_write_port_pin( \ - port, pin, false) /*!< Turns the given GPIO Low */ - -#define platformGpioIsHigh(port, pin) \ - (furi_hal_gpio_read_port_pin(port, pin) == \ - true) /*!< Checks if the given LED is High */ -#define platformGpioIsLow(port, pin) \ - (!platformGpioIsHigh(port, pin)) /*!< Checks if the given LED is Low */ - -#define platformTimerCreate(t) \ - timerCalculateTimer(t) /*!< Create a timer with the given time (ms) */ -#define platformTimerIsExpired(timer) \ - timerIsExpired(timer) /*!< Checks if the given timer is expired */ -#define platformDelay(t) furi_delay_ms(t) /*!< Performs a delay for the given time (ms) */ - -#define platformGetSysTick() furi_get_tick() /*!< Get System Tick (1 tick = 1 ms) */ - -#define platformAssert(exp) assert_param(exp) /*!< Asserts whether the given expression is true*/ - -#define platformSpiSelect() \ - platformGpioClear( \ - ST25R_SS_PORT, ST25R_SS_PIN) /*!< SPI SS\CS: Chip|Slave Select */ -#define platformSpiDeselect() \ - platformGpioSet( \ - ST25R_SS_PORT, ST25R_SS_PIN) /*!< SPI SS\CS: Chip|Slave Deselect */ - -#define platformI2CTx(txBuf, len, last, txOnly) /*!< I2C Transmit */ -#define platformI2CRx(txBuf, len) /*!< I2C Receive */ -#define platformI2CStart() /*!< I2C Start condition */ -#define platformI2CStop() /*!< I2C Stop condition */ -#define platformI2CRepeatStart() /*!< I2C Repeat Start */ -#define platformI2CSlaveAddrWR(add) /*!< I2C Slave address for Write operation */ -#define platformI2CSlaveAddrRD(add) /*!< I2C Slave address for Read operation */ - -#define platformLog(...) /*!< Log method */ - -/* - ****************************************************************************** - * RFAL OPTIONAL MACROS (Do not change) - ****************************************************************************** - */ -#ifndef platformProtectST25RIrqStatus -#define platformProtectST25RIrqStatus() /*!< Protect unique access to IRQ status var - IRQ disable on single thread environment (MCU) ; Mutex lock on a multi thread environment */ -#endif /* platformProtectST25RIrqStatus */ - -#ifndef platformUnprotectST25RIrqStatus -#define platformUnprotectST25RIrqStatus() /*!< Unprotect the IRQ status var - IRQ enable on a single thread environment (MCU) ; Mutex unlock on a multi thread environment */ -#endif /* platformUnprotectST25RIrqStatus */ - -#ifndef platformProtectWorker -#define platformProtectWorker() /* Protect RFAL Worker/Task/Process from concurrent execution on multi thread platforms */ -#endif /* platformProtectWorker */ - -#ifndef platformUnprotectWorker -#define platformUnprotectWorker() /* Unprotect RFAL Worker/Task/Process from concurrent execution on multi thread platforms */ -#endif /* platformUnprotectWorker */ - -#ifndef platformIrqST25RPinInitialize -#define platformIrqST25RPinInitialize() /*!< Initializes ST25R IRQ pin */ -#endif /* platformIrqST25RPinInitialize */ - -#ifndef platformIrqST25RSetCallback -#define platformIrqST25RSetCallback(cb) /*!< Sets ST25R ISR callback */ -#endif /* platformIrqST25RSetCallback */ - -#ifndef platformLedsInitialize -#define platformLedsInitialize() /*!< Initializes the pins used as LEDs to outputs */ -#endif /* platformLedsInitialize */ - -#ifndef platformLedOff -#define platformLedOff(port, pin) /*!< Turns the given LED Off */ -#endif /* platformLedOff */ - -#ifndef platformLedOn -#define platformLedOn(port, pin) /*!< Turns the given LED On */ -#endif /* platformLedOn */ - -#ifndef platformLedToogle -#define platformLedToogle(port, pin) /*!< Toggles the given LED */ -#endif /* platformLedToogle */ - -#ifndef platformGetSysTick -#define platformGetSysTick() /*!< Get System Tick (1 tick = 1 ms) */ -#endif /* platformGetSysTick */ - -#ifndef platformTimerDestroy -#define platformTimerDestroy(timer) /*!< Stops and released the given timer */ -#endif /* platformTimerDestroy */ - -#ifndef platformAssert -#define platformAssert(exp) /*!< Asserts whether the given expression is true */ -#endif /* platformAssert */ - -#ifndef platformErrorHandle -#define platformErrorHandle() /*!< Global error handler or trap */ -#endif /* platformErrorHandle */ diff --git a/lib/ST25RFAL002/source/custom_analog_config.c b/lib/ST25RFAL002/source/custom_analog_config.c deleted file mode 100644 index 00a54db260..0000000000 --- a/lib/ST25RFAL002/source/custom_analog_config.c +++ /dev/null @@ -1,777 +0,0 @@ -#include "rfal_analogConfigTbl.h" - -const uint8_t rfalAnalogConfigCustomSettings[] = { - /****** Default Analog Configuration for Chip-Specific Reset ******/ - MODE_ENTRY_16_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_INIT), - ST25R3916_REG_IO_CONF1, - (ST25R3916_REG_IO_CONF1_out_cl_mask | ST25R3916_REG_IO_CONF1_lf_clk_off), - 0x07 /* Disable MCU_CLK */ - , - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2), - 0x18 /* SPI Pull downs */ - // , ST25R3916_REG_IO_CONF2, ST25R3916_REG_IO_CONF2_aat_en, ST25R3916_REG_IO_CONF2_aat_en /* Enable AAT */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_d_res_mask, - 0x00 /* Set RFO resistance Active Tx */ - , - ST25R3916_REG_RES_AM_MOD, - 0xFF, - 0x80 /* Use minimum non-overlap */ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_ext, - ST25R3916_REG_AUX_MOD_lm_ext /* Disable External Load Modulation */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_dri, - ST25R3916_REG_AUX_MOD_lm_dri /* Use internal Load Modulation */ - , - ST25R3916_REG_PASSIVE_TARGET, - ST25R3916_REG_PASSIVE_TARGET_fdel_mask, - (5U - << ST25R3916_REG_PASSIVE_TARGET_fdel_shift) /* Adjust the FDT to be aligned with the bitgrid */ - , - ST25R3916_REG_PT_MOD, - (ST25R3916_REG_PT_MOD_ptm_res_mask | ST25R3916_REG_PT_MOD_pt_res_mask), - 0x0f /* Reduce RFO resistance in Modulated state */ - , - ST25R3916_REG_EMD_SUP_CONF, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv_on /* Enable start on first 4 bits */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - 0x84U, - 0x10, - 0x10 /* Avoid chip internal overheat protection */ - ) - - /****** Default Analog Configuration for Chip-Specific Poll Common ******/ - , - MODE_ENTRY_9_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) - - /****** Default Analog Configuration for Poll NFC-A Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x08, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x51, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 212 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 424 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 848 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_40percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Anticolision setting ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL), - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s6, - 0x00 /* Set collision detection level different from data */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Coherent Receiver */ - ) -#else - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll NFC-B Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x04, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x1B, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#ifdef RFAL_USE_COHE - - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#else - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#endif /*RFAL_USE_COHE*/ - - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | RFAL_ANALOG_CONFIG_BITRATE_1OF4 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#else - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Chip-Specific Listen On ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x00 /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0xff /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Rx Common ******/ - , - MODE_ENTRY_3_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_lp_mask, - ST25R3916_REG_RX_CONF1_lp_1200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_hz_mask, - ST25R3916_REG_RX_CONF1_hz_12_200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF2, - ST25R3916_REG_RX_CONF2_amd_sel, - ST25R3916_REG_RX_CONF2_amd_sel_mixer /* AM demodulator: mixer */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_106 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_212 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_424 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - -}; - -const uint16_t rfalAnalogConfigCustomSettingsLength = sizeof(rfalAnalogConfigCustomSettings); diff --git a/lib/ST25RFAL002/source/rfal_analogConfig.c b/lib/ST25RFAL002/source/rfal_analogConfig.c deleted file mode 100644 index 1058dd3285..0000000000 --- a/lib/ST25RFAL002/source/rfal_analogConfig.c +++ /dev/null @@ -1,476 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_analogConfig.c - * - * \author bkam - * - * \brief Functions to manage and set analog settings. - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_analogConfig.h" -#include "rfal_chip.h" -#include "st_errno.h" -#include "platform.h" -#include "utils.h" - -/* Check whether the Default Analog settings are to be used or custom ones */ -#ifdef RFAL_ANALOG_CONFIG_CUSTOM -extern const uint8_t* rfalAnalogConfigCustomSettings; -extern const uint16_t rfalAnalogConfigCustomSettingsLength; -#else -#include "rfal_analogConfigTbl.h" -#endif - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -#define RFAL_TEST_REG 0x0080U /*!< Test Register indicator */ - -/* - ****************************************************************************** - * MACROS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG -static uint8_t - gRfalAnalogConfig[RFAL_ANALOG_CONFIG_TBL_SIZE]; /*!< Analog Configuration Settings List */ -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -/*! Struct for Analog Config Look Up Table Update */ -typedef struct { - const uint8_t* - currentAnalogConfigTbl; /*!< Reference to start of current Analog Configuration */ - uint16_t configTblSize; /*!< Total size of Analog Configuration */ - bool ready; /*!< Indicate if Look Up Table is complete and ready for use */ -} rfalAnalogConfigMgmt; - -static rfalAnalogConfigMgmt gRfalAnalogConfigMgmt; /*!< Analog Configuration LUT management */ - -/* - ****************************************************************************** - * LOCAL TABLES - ****************************************************************************** - */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ -static rfalAnalogConfigNum - rfalAnalogConfigSearch(rfalAnalogConfigId configId, uint16_t* configOffset); - -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG -static void rfalAnalogConfigPtrUpdate(const uint8_t* analogConfigTbl); -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -/* - ****************************************************************************** - * GLOBAL VARIABLE DEFINITIONS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -void rfalAnalogConfigInitialize(void) { - /* Use default Analog configuration settings in Flash by default. */ - -/* Check whether the Default Analog settings are to be used or custom ones */ -#ifdef RFAL_ANALOG_CONFIG_CUSTOM - gRfalAnalogConfigMgmt.currentAnalogConfigTbl = (const uint8_t*)&rfalAnalogConfigCustomSettings; - gRfalAnalogConfigMgmt.configTblSize = rfalAnalogConfigCustomSettingsLength; -#else - gRfalAnalogConfigMgmt.currentAnalogConfigTbl = - (const uint8_t*)&rfalAnalogConfigDefaultSettings; - gRfalAnalogConfigMgmt.configTblSize = sizeof(rfalAnalogConfigDefaultSettings); -#endif - - gRfalAnalogConfigMgmt.ready = true; -} /* rfalAnalogConfigInitialize() */ - -bool rfalAnalogConfigIsReady(void) { - return gRfalAnalogConfigMgmt.ready; -} - -ReturnCode rfalAnalogConfigListWriteRaw(const uint8_t* configTbl, uint16_t configTblSize) { -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG - - /* Check if the Configuration Table exceed the Table size */ - if(configTblSize >= RFAL_ANALOG_CONFIG_TBL_SIZE) { - rfalAnalogConfigInitialize(); /* Revert to default Analog Configuration */ - return ERR_NOMEM; - } - - /* Check for invalid parameters */ - if((configTbl == NULL) || (configTblSize == 0U)) { - return ERR_PARAM; - } - - /* NOTE: Function does not check for the validity of the Table contents (conf IDs, conf sets, register address) */ - ST_MEMCPY(gRfalAnalogConfig, configTbl, configTblSize); - - /* Update the total size of configuration settings */ - gRfalAnalogConfigMgmt.configTblSize = configTblSize; - - rfalAnalogConfigPtrUpdate(gRfalAnalogConfig); - return ERR_NONE; - -#else - - // If Analog Configuration Update is to be disabled - NO_WARNING(configTbl); - NO_WARNING(configTblSize); - return ERR_REQUEST; - -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ -} - -ReturnCode rfalAnalogConfigListWrite(uint8_t more, const rfalAnalogConfig* config) { -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG - - rfalAnalogConfigId configId; - rfalAnalogConfigNum numConfig; - uint8_t configSize; - - if(true == gRfalAnalogConfigMgmt.ready) { /* First Update to the Configuration list. */ - gRfalAnalogConfigMgmt.ready = false; // invalidate the config List - gRfalAnalogConfigMgmt.configTblSize = 0; // Clear the config List - } - - configId = GETU16(config->id); - - /* Check validity of the Configuration ID. */ - if((RFAL_ANALOG_CONFIG_TECH_RFU <= RFAL_ANALOG_CONFIG_ID_GET_TECH(configId)) || - ((RFAL_ANALOG_CONFIG_BITRATE_6780 < RFAL_ANALOG_CONFIG_ID_GET_BITRATE(configId)) && - (RFAL_ANALOG_CONFIG_BITRATE_1OF4 > RFAL_ANALOG_CONFIG_ID_GET_BITRATE(configId))) || - (RFAL_ANALOG_CONFIG_BITRATE_1OF256 < RFAL_ANALOG_CONFIG_ID_GET_BITRATE(configId))) { - rfalAnalogConfigInitialize(); /* Revert to default Analog Configuration */ - return ERR_PARAM; - } - - numConfig = config->num; - configSize = - (uint8_t)(sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum) + (numConfig * sizeof(rfalAnalogConfigRegAddrMaskVal))); - - /* Check if the Configuration Set exceed the Table size. */ - if(RFAL_ANALOG_CONFIG_TBL_SIZE <= (gRfalAnalogConfigMgmt.configTblSize + configSize)) { - rfalAnalogConfigInitialize(); /* Revert to default Analog Configuration */ - return ERR_NOMEM; - } - - /* NOTE: Function does not check for the validity of the Register Address. */ - ST_MEMCPY( - &gRfalAnalogConfig[gRfalAnalogConfigMgmt.configTblSize], - (const uint8_t*)config, - configSize); - - /* Increment the total size of configuration settings. */ - gRfalAnalogConfigMgmt.configTblSize += configSize; - - /* Check if it is the last Analog Configuration to load. */ - if(RFAL_ANALOG_CONFIG_UPDATE_LAST == - more) { /* Update the Analog Configuration to the new settings. */ - rfalAnalogConfigPtrUpdate(gRfalAnalogConfig); - } - - return ERR_NONE; - -#else - - // If Analog Configuration Update is to be disabled - NO_WARNING(config); - NO_WARNING(more); - return ERR_DISABLED; - -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -} /* rfalAnalogConfigListUpdate() */ - -ReturnCode - rfalAnalogConfigListReadRaw(uint8_t* tblBuf, uint16_t tblBufLen, uint16_t* configTblSize) { - /* Check if the the current table will fit into the given buffer */ - if(tblBufLen < gRfalAnalogConfigMgmt.configTblSize) { - return ERR_NOMEM; - } - - /* Check for invalid parameters */ - if(configTblSize == NULL) { - return ERR_PARAM; - } - - /* Copy the whole Table to the given buffer */ - if(gRfalAnalogConfigMgmt.configTblSize > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY( - tblBuf, - gRfalAnalogConfigMgmt.currentAnalogConfigTbl, - gRfalAnalogConfigMgmt.configTblSize); - } - *configTblSize = gRfalAnalogConfigMgmt.configTblSize; - - return ERR_NONE; -} - -ReturnCode rfalAnalogConfigListRead( - rfalAnalogConfigOffset* configOffset, - uint8_t* more, - rfalAnalogConfig* config, - rfalAnalogConfigNum numConfig) { - uint16_t configSize; - rfalAnalogConfigOffset offset = *configOffset; - rfalAnalogConfigNum numConfigSet; - - /* Check if the number of register-mask-value settings for the respective Configuration ID will fit into the buffer passed in. */ - if(gRfalAnalogConfigMgmt.currentAnalogConfigTbl[offset + sizeof(rfalAnalogConfigId)] > - numConfig) { - return ERR_NOMEM; - } - - /* Get the number of Configuration set */ - numConfigSet = - gRfalAnalogConfigMgmt.currentAnalogConfigTbl[offset + sizeof(rfalAnalogConfigId)]; - - /* Pass Configuration Register-Mask-Value sets */ - configSize = - (sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum) + - (uint16_t)(numConfigSet * sizeof(rfalAnalogConfigRegAddrMaskVal))); - ST_MEMCPY((uint8_t*)config, &gRfalAnalogConfigMgmt.currentAnalogConfigTbl[offset], configSize); - *configOffset = offset + configSize; - - /* Check if it is the last Analog Configuration in the Table.*/ - *more = - (uint8_t)((*configOffset >= gRfalAnalogConfigMgmt.configTblSize) ? RFAL_ANALOG_CONFIG_UPDATE_LAST : RFAL_ANALOG_CONFIG_UPDATE_MORE); - - return ERR_NONE; -} /* rfalAnalogConfigListRead() */ - -ReturnCode rfalSetAnalogConfig(rfalAnalogConfigId configId) { - rfalAnalogConfigOffset configOffset = 0; - rfalAnalogConfigNum numConfigSet; - const rfalAnalogConfigRegAddrMaskVal* configTbl; - ReturnCode retCode = ERR_NONE; - rfalAnalogConfigNum i; - - if(true != gRfalAnalogConfigMgmt.ready) { - return ERR_REQUEST; - } - - /* Search LUT for the specific Configuration ID. */ - while(true) { - numConfigSet = rfalAnalogConfigSearch(configId, &configOffset); - if(RFAL_ANALOG_CONFIG_LUT_NOT_FOUND == numConfigSet) { - break; - } - - configTbl = - (rfalAnalogConfigRegAddrMaskVal*)((uint32_t)gRfalAnalogConfigMgmt.currentAnalogConfigTbl + (uint32_t)configOffset); - /* Increment the offset to the next index to search from. */ - configOffset += (uint16_t)(numConfigSet * sizeof(rfalAnalogConfigRegAddrMaskVal)); - - if((gRfalAnalogConfigMgmt.configTblSize + 1U) < - configOffset) { /* Error check make sure that the we do not access outside the configuration Table Size */ - return ERR_NOMEM; - } - - for(i = 0; i < numConfigSet; i++) { - if((GETU16(configTbl[i].addr) & RFAL_TEST_REG) != 0U) { - EXIT_ON_ERR( - retCode, - rfalChipChangeTestRegBits( - (GETU16(configTbl[i].addr) & ~RFAL_TEST_REG), - configTbl[i].mask, - configTbl[i].val)); - } else { - EXIT_ON_ERR( - retCode, - rfalChipChangeRegBits( - GETU16(configTbl[i].addr), configTbl[i].mask, configTbl[i].val)); - } - } - - } /* while(found Analog Config Id) */ - - return retCode; - -} /* rfalSetAnalogConfig() */ - -uint16_t rfalAnalogConfigGenModeID(rfalMode md, rfalBitRate br, uint16_t dir) { - uint16_t id; - - /* Assign Poll/Listen Mode */ - id = ((md >= RFAL_MODE_LISTEN_NFCA) ? RFAL_ANALOG_CONFIG_LISTEN : RFAL_ANALOG_CONFIG_POLL); - - /* Assign Technology */ - switch(md) { - case RFAL_MODE_POLL_NFCA: - case RFAL_MODE_POLL_NFCA_T1T: - case RFAL_MODE_LISTEN_NFCA: - id |= RFAL_ANALOG_CONFIG_TECH_NFCA; - break; - - case RFAL_MODE_POLL_NFCB: - case RFAL_MODE_POLL_B_PRIME: - case RFAL_MODE_POLL_B_CTS: - case RFAL_MODE_LISTEN_NFCB: - id |= RFAL_ANALOG_CONFIG_TECH_NFCB; - break; - - case RFAL_MODE_POLL_NFCF: - case RFAL_MODE_LISTEN_NFCF: - id |= RFAL_ANALOG_CONFIG_TECH_NFCF; - break; - - case RFAL_MODE_POLL_NFCV: - case RFAL_MODE_POLL_PICOPASS: - id |= RFAL_ANALOG_CONFIG_TECH_NFCV; - break; - - case RFAL_MODE_POLL_ACTIVE_P2P: - case RFAL_MODE_LISTEN_ACTIVE_P2P: - id |= RFAL_ANALOG_CONFIG_TECH_AP2P; - break; - - default: - id = RFAL_ANALOG_CONFIG_TECH_CHIP; - break; - } - - /* Assign Bitrate */ - id |= - (((((uint16_t)(br) >= (uint16_t)RFAL_BR_52p97) ? (uint16_t)(br) : ((uint16_t)(br) + 1U)) - << RFAL_ANALOG_CONFIG_BITRATE_SHIFT) & - RFAL_ANALOG_CONFIG_BITRATE_MASK); - - /* Assign Direction */ - id |= ((dir << RFAL_ANALOG_CONFIG_DIRECTION_SHIFT) & RFAL_ANALOG_CONFIG_DIRECTION_MASK); - - return id; - -} /* rfalAnalogConfigGenModeID() */ - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -/*! - ***************************************************************************** - * \brief Update the link to Analog Configuration LUT - * - * Update the link to the Analog Configuration LUT for the subsequent search - * of Analog Settings. - * - * \param[in] analogConfigTbl: reference to the start of the new Analog Configuration Table - * - ***************************************************************************** - */ -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG -static void rfalAnalogConfigPtrUpdate(const uint8_t* analogConfigTbl) { - gRfalAnalogConfigMgmt.currentAnalogConfigTbl = analogConfigTbl; - gRfalAnalogConfigMgmt.ready = true; - -} /* rfalAnalogConfigPtrUpdate() */ -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -/*! - ***************************************************************************** - * \brief Search the Analog Configuration LUT for a specific Configuration ID. - * - * Search the Analog Configuration LUT for the Configuration ID. - * - * \param[in] configId: Configuration ID to search for. - * \param[in] configOffset: Configuration Offset in Table - * - * \return number of Configuration Sets - * \return #RFAL_ANALOG_CONFIG_LUT_NOT_FOUND in case Configuration ID is not found. - ***************************************************************************** - */ -static rfalAnalogConfigNum - rfalAnalogConfigSearch(rfalAnalogConfigId configId, uint16_t* configOffset) { - rfalAnalogConfigId foundConfigId; - rfalAnalogConfigId configIdMaskVal; - const uint8_t* configTbl; - const uint8_t* currentConfigTbl; - uint16_t i; - - currentConfigTbl = gRfalAnalogConfigMgmt.currentAnalogConfigTbl; - configIdMaskVal = - ((RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK | RFAL_ANALOG_CONFIG_BITRATE_MASK) | - ((RFAL_ANALOG_CONFIG_TECH_CHIP == RFAL_ANALOG_CONFIG_ID_GET_TECH(configId)) ? - (RFAL_ANALOG_CONFIG_TECH_MASK | RFAL_ANALOG_CONFIG_CHIP_SPECIFIC_MASK) : - configId) | - ((RFAL_ANALOG_CONFIG_NO_DIRECTION == RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(configId)) ? - RFAL_ANALOG_CONFIG_DIRECTION_MASK : - configId)); - - /* When specific ConfigIDs are to be used, override search mask */ - if((RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(configId) == RFAL_ANALOG_CONFIG_DPO)) { - configIdMaskVal = - (RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK | RFAL_ANALOG_CONFIG_TECH_MASK | - RFAL_ANALOG_CONFIG_BITRATE_MASK | RFAL_ANALOG_CONFIG_DIRECTION_MASK); - } - - i = *configOffset; - while(i < gRfalAnalogConfigMgmt.configTblSize) { - configTbl = ¤tConfigTbl[i]; - foundConfigId = GETU16(configTbl); - if(configId == (foundConfigId & configIdMaskVal)) { - *configOffset = - (uint16_t)(i + sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum)); - return configTbl[sizeof(rfalAnalogConfigId)]; - } - - /* If Config Id does not match, increment to next Configuration Id */ - i += - (uint16_t)(sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum) + (configTbl[sizeof(rfalAnalogConfigId)] * sizeof(rfalAnalogConfigRegAddrMaskVal))); - } /* for */ - - return RFAL_ANALOG_CONFIG_LUT_NOT_FOUND; -} /* rfalAnalogConfigSearch() */ diff --git a/lib/ST25RFAL002/source/rfal_crc.c b/lib/ST25RFAL002/source/rfal_crc.c deleted file mode 100644 index bcf5ad593c..0000000000 --- a/lib/ST25RFAL002/source/rfal_crc.c +++ /dev/null @@ -1,82 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_crc.c - * - * \author Oliver Regenfelder - * - * \brief CRC calculation implementation - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "rfal_crc.h" - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static uint16_t rfalCrcUpdateCcitt(uint16_t crcSeed, uint8_t dataByte); - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ -uint16_t rfalCrcCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) { - uint16_t crc = preloadValue; - uint16_t index; - - for(index = 0; index < length; index++) { - crc = rfalCrcUpdateCcitt(crc, buf[index]); - } - - return crc; -} - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ -static uint16_t rfalCrcUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) { - uint16_t crc = crcSeed; - uint8_t dat = dataByte; - - dat ^= (uint8_t)(crc & 0xFFU); - dat ^= (dat << 4); - - crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4); - - return crc; -} diff --git a/lib/ST25RFAL002/source/rfal_dpo.c b/lib/ST25RFAL002/source/rfal_dpo.c deleted file mode 100644 index 48dd89c77c..0000000000 --- a/lib/ST25RFAL002/source/rfal_dpo.c +++ /dev/null @@ -1,232 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_dpo.c - * - * \author Martin Zechleitner - * - * \brief Functions to manage and set dynamic power settings. - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_dpoTbl.h" -#include "rfal_dpo.h" -#include "platform.h" -#include "rfal_rf.h" -#include "rfal_chip.h" -#include "rfal_analogConfig.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_DPO -#define RFAL_FEATURE_DPO \ - false /* Dynamic Power Module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_DPO - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define RFAL_DPO_ANALOGCONFIG_SHIFT 13U -#define RFAL_DPO_ANALOGCONFIG_MASK 0x6000U - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -static bool gRfalDpoIsEnabled = false; -static uint8_t* gRfalCurrentDpo; -static uint8_t gRfalDpoTableEntries; -static uint8_t gRfalDpo[RFAL_DPO_TABLE_SIZE_MAX]; -static uint8_t gRfalDpoTableEntry; -static rfalDpoMeasureFunc gRfalDpoMeasureCallback = NULL; - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ -void rfalDpoInitialize(void) { - /* Use the default Dynamic Power values */ - gRfalCurrentDpo = (uint8_t*)rfalDpoDefaultSettings; - gRfalDpoTableEntries = (sizeof(rfalDpoDefaultSettings) / RFAL_DPO_TABLE_PARAMETER); - - ST_MEMCPY(gRfalDpo, gRfalCurrentDpo, sizeof(rfalDpoDefaultSettings)); - - /* by default use amplitude measurement */ - gRfalDpoMeasureCallback = rfalChipMeasureAmplitude; - - /* by default DPO is disabled */ - gRfalDpoIsEnabled = false; - - gRfalDpoTableEntry = 0; -} - -void rfalDpoSetMeasureCallback(rfalDpoMeasureFunc pMeasureFunc) { - gRfalDpoMeasureCallback = pMeasureFunc; -} - -/*******************************************************************************/ -ReturnCode rfalDpoTableWrite(rfalDpoEntry* powerTbl, uint8_t powerTblEntries) { - uint8_t entry = 0; - - /* check if the table size parameter is too big */ - if((powerTblEntries * RFAL_DPO_TABLE_PARAMETER) > RFAL_DPO_TABLE_SIZE_MAX) { - return ERR_NOMEM; - } - - /* check if the first increase entry is 0xFF */ - if((powerTblEntries == 0) || (powerTbl == NULL)) { - return ERR_PARAM; - } - - /* check if the entries of the dynamic power table are valid */ - for(entry = 0; entry < powerTblEntries; entry++) { - if(powerTbl[entry].inc < powerTbl[entry].dec) { - return ERR_PARAM; - } - } - - /* copy the data set */ - ST_MEMCPY(gRfalDpo, powerTbl, (powerTblEntries * RFAL_DPO_TABLE_PARAMETER)); - gRfalCurrentDpo = gRfalDpo; - gRfalDpoTableEntries = powerTblEntries; - - if(gRfalDpoTableEntry > powerTblEntries) { - /* is always greater then zero, otherwise we already returned ERR_PARAM */ - gRfalDpoTableEntry = (powerTblEntries - 1); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalDpoTableRead(rfalDpoEntry* tblBuf, uint8_t tblBufEntries, uint8_t* tableEntries) { - /* wrong request */ - if((tblBuf == NULL) || (tblBufEntries < gRfalDpoTableEntries) || (tableEntries == NULL)) { - return ERR_PARAM; - } - - /* Copy the whole Table to the given buffer */ - ST_MEMCPY(tblBuf, gRfalCurrentDpo, (tblBufEntries * RFAL_DPO_TABLE_PARAMETER)); - *tableEntries = gRfalDpoTableEntries; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalDpoAdjust(void) { - uint8_t refValue = 0; - uint16_t modeID; - rfalBitRate br; - rfalDpoEntry* dpoTable = (rfalDpoEntry*)gRfalCurrentDpo; - - /* Check if the Power Adjustment is disabled and * - * if the callback to the measurement method is properly set */ - if((gRfalCurrentDpo == NULL) || (!gRfalDpoIsEnabled) || (gRfalDpoMeasureCallback == NULL)) { - return ERR_PARAM; - } - - /* Ensure that the current mode is Passive Poller */ - if(!rfalIsModePassivePoll(rfalGetMode())) { - return ERR_WRONG_STATE; - } - - /* Ensure a proper measure reference value */ - if(ERR_NONE != gRfalDpoMeasureCallback(&refValue)) { - return ERR_IO; - } - - if(refValue >= dpoTable[gRfalDpoTableEntry].inc) { /* Increase the output power */ - /* the top of the table represents the highest amplitude value*/ - if(gRfalDpoTableEntry == 0) { - /* maximum driver value has been reached */ - } else { - /* go up in the table to decrease the driver resistance */ - gRfalDpoTableEntry--; - } - } else if(refValue <= dpoTable[gRfalDpoTableEntry].dec) { /* decrease the output power */ - /* The bottom is the highest possible value */ - if((gRfalDpoTableEntry + 1) >= gRfalDpoTableEntries) { - /* minimum driver value has been reached */ - } else { - /* go down in the table to increase the driver resistance */ - gRfalDpoTableEntry++; - } - } else { - /* Fall through to always write dpo and its associated analog configs */ - } - - /* Get the new value for RFO resistance form the table and apply the new RFO resistance setting */ - rfalChipSetRFO(dpoTable[gRfalDpoTableEntry].rfoRes); - - /* Apply the DPO Analog Config according to this treshold */ - /* Technology field is being extended for DPO: 2msb are used for treshold step (only 4 allowed) */ - rfalGetBitRate(&br, NULL); /* Obtain current Tx bitrate */ - modeID = rfalAnalogConfigGenModeID( - rfalGetMode(), br, RFAL_ANALOG_CONFIG_DPO); /* Generate Analog Config mode ID */ - modeID |= - ((gRfalDpoTableEntry << RFAL_DPO_ANALOGCONFIG_SHIFT) & - RFAL_DPO_ANALOGCONFIG_MASK); /* Add DPO treshold step|level */ - rfalSetAnalogConfig(modeID); /* Apply DPO Analog Config */ - - return ERR_NONE; -} - -/*******************************************************************************/ -rfalDpoEntry* rfalDpoGetCurrentTableEntry(void) { - rfalDpoEntry* dpoTable = (rfalDpoEntry*)gRfalCurrentDpo; - return &dpoTable[gRfalDpoTableEntry]; -} - -/*******************************************************************************/ -void rfalDpoSetEnabled(bool enable) { - gRfalDpoIsEnabled = enable; -} - -/*******************************************************************************/ -bool rfalDpoIsEnabled(void) { - return gRfalDpoIsEnabled; -} - -#endif /* RFAL_FEATURE_DPO */ diff --git a/lib/ST25RFAL002/source/rfal_iso15693_2.c b/lib/ST25RFAL002/source/rfal_iso15693_2.c deleted file mode 100644 index 34ecd41113..0000000000 --- a/lib/ST25RFAL002/source/rfal_iso15693_2.c +++ /dev/null @@ -1,514 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_iso15693_2.c - * - * \author Ulrich Herrmann - * - * \brief Implementation of ISO-15693-2 - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "rfal_iso15693_2.h" -#include "rfal_crc.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCV -#define RFAL_FEATURE_NFCV false /* NFC-V module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCV - -/* -****************************************************************************** -* LOCAL MACROS -****************************************************************************** -*/ - -#define ISO_15693_DEBUG(...) /*!< Macro for the log method */ - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ -#define ISO15693_DAT_SOF_1_4 0x21 /* LSB constants */ -#define ISO15693_DAT_EOF_1_4 0x04 -#define ISO15693_DAT_00_1_4 0x02 -#define ISO15693_DAT_01_1_4 0x08 -#define ISO15693_DAT_10_1_4 0x20 -#define ISO15693_DAT_11_1_4 0x80 - -#define ISO15693_DAT_SOF_1_256 0x81 -#define ISO15693_DAT_EOF_1_256 0x04 -#define ISO15693_DAT_SLOT0_1_256 0x02 -#define ISO15693_DAT_SLOT1_1_256 0x08 -#define ISO15693_DAT_SLOT2_1_256 0x20 -#define ISO15693_DAT_SLOT3_1_256 0x80 - -#define ISO15693_PHY_DAT_MANCHESTER_1 0xaaaa - -#define ISO15693_PHY_BIT_BUFFER_SIZE \ - 1000 /*!< size of the receiving buffer. Might be adjusted if longer datastreams are expected. */ - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ -static iso15693PhyConfig_t iso15693PhyConfig; /*!< current phy configuration */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode iso15693PhyVCDCode1Of4( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen); -static ReturnCode iso15693PhyVCDCode1Of256( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen); - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ -ReturnCode iso15693PhyConfigure( - const iso15693PhyConfig_t* config, - const struct iso15693StreamConfig** needed_stream_config) { - static struct iso15693StreamConfig stream_config = { - /* MISRA 8.9 */ - .useBPSK = 0, /* 0: subcarrier, 1:BPSK */ - .din = 5, /* 2^5*fc = 423750 Hz: divider for the in subcarrier frequency */ - .dout = 7, /*!< 2^7*fc = 105937 : divider for the in subcarrier frequency */ - .report_period_length = 3, /*!< 8=2^3 the length of the reporting period */ - }; - - /* make a copy of the configuration */ - ST_MEMCPY((uint8_t*)&iso15693PhyConfig, (const uint8_t*)config, sizeof(iso15693PhyConfig_t)); - - if(config->speedMode <= 3U) { /* If valid speed mode adjust report period accordingly */ - stream_config.report_period_length = (3U - (uint8_t)config->speedMode); - } else { /* If invalid default to normal (high) speed */ - stream_config.report_period_length = 3; - } - - *needed_stream_config = &stream_config; - - return ERR_NONE; -} - -ReturnCode iso15693PhyGetConfiguration(iso15693PhyConfig_t* config) { - ST_MEMCPY(config, &iso15693PhyConfig, sizeof(iso15693PhyConfig_t)); - - return ERR_NONE; -} - -ReturnCode iso15693VCDCode( - uint8_t* buffer, - uint16_t length, - bool sendCrc, - bool sendFlags, - bool picopassMode, - uint16_t* subbit_total_length, - uint16_t* offset, - uint8_t* outbuf, - uint16_t outBufSize, - uint16_t* actOutBufSize) { - ReturnCode err = ERR_NONE; - uint8_t eof, sof; - uint8_t transbuf[2]; - uint16_t crc = 0; - ReturnCode (*txFunc)( - const uint8_t data, uint8_t* outbuffer, uint16_t maxOutBufLen, uint16_t* outBufLen); - uint8_t crc_len; - uint8_t* outputBuf; - uint16_t outputBufSize; - - crc_len = (uint8_t)((sendCrc) ? 2 : 0); - - *actOutBufSize = 0; - - if(ISO15693_VCD_CODING_1_4 == iso15693PhyConfig.coding) { - sof = ISO15693_DAT_SOF_1_4; - eof = ISO15693_DAT_EOF_1_4; - txFunc = iso15693PhyVCDCode1Of4; - *subbit_total_length = - ((1U /* SOF */ - + ((length + (uint16_t)crc_len) * 4U) + 1U) /* EOF */ - ); - if(outBufSize < 5U) { /* 5 should be safe: enough for sof + 1byte data in 1of4 */ - return ERR_NOMEM; - } - } else { - sof = ISO15693_DAT_SOF_1_256; - eof = ISO15693_DAT_EOF_1_256; - txFunc = iso15693PhyVCDCode1Of256; - *subbit_total_length = - ((1U /* SOF */ - + ((length + (uint16_t)crc_len) * 64U) + 1U) /* EOF */ - ); - - if(*offset != 0U) { - if(outBufSize < 64U) { /* 64 should be safe: enough a single byte data in 1of256 */ - return ERR_NOMEM; - } - } else { - if(outBufSize < - 65U) { /* At beginning of a frame we need at least 65 bytes to start: enough for sof + 1byte data in 1of256 */ - return ERR_NOMEM; - } - } - } - - if(length == 0U) { - *subbit_total_length = 1; - } - - if((length != 0U) && (0U == *offset) && sendFlags && !picopassMode) { - /* set high datarate flag */ - buffer[0] |= (uint8_t)ISO15693_REQ_FLAG_HIGH_DATARATE; - /* clear sub-carrier flag - we only support single sub-carrier */ - buffer[0] = (uint8_t)(buffer[0] & ~ISO15693_REQ_FLAG_TWO_SUBCARRIERS); /* MISRA 10.3 */ - } - - outputBuf = outbuf; /* MISRA 17.8: Use intermediate variable */ - outputBufSize = outBufSize; /* MISRA 17.8: Use intermediate variable */ - - /* Send SOF if at 0 offset */ - if((length != 0U) && (0U == *offset)) { - *outputBuf = sof; - (*actOutBufSize)++; - outputBufSize--; - outputBuf++; - } - - while((*offset < length) && (err == ERR_NONE)) { - uint16_t filled_size; - /* send data */ - err = txFunc(buffer[*offset], outputBuf, outputBufSize, &filled_size); - (*actOutBufSize) += filled_size; - outputBuf = &outputBuf[filled_size]; /* MISRA 18.4: Avoid pointer arithmetic */ - outputBufSize -= filled_size; - if(err == ERR_NONE) { - (*offset)++; - } - } - if(err != ERR_NONE) { - return ERR_AGAIN; - } - - while((err == ERR_NONE) && sendCrc && (*offset < (length + 2U))) { - uint16_t filled_size; - if(0U == crc) { - crc = rfalCrcCalculateCcitt( - (uint16_t)((picopassMode) ? 0xE012U : 0xFFFFU), /* In PicoPass Mode a different Preset Value is used */ - ((picopassMode) ? - (buffer + 1U) : - buffer), /* CMD byte is not taken into account in PicoPass mode */ - ((picopassMode) ? - (length - 1U) : - length)); /* CMD byte is not taken into account in PicoPass mode */ - - crc = (uint16_t)((picopassMode) ? crc : ~crc); - } - /* send crc */ - transbuf[0] = (uint8_t)(crc & 0xffU); - transbuf[1] = (uint8_t)((crc >> 8) & 0xffU); - err = txFunc(transbuf[*offset - length], outputBuf, outputBufSize, &filled_size); - (*actOutBufSize) += filled_size; - outputBuf = &outputBuf[filled_size]; /* MISRA 18.4: Avoid pointer arithmetic */ - outputBufSize -= filled_size; - if(err == ERR_NONE) { - (*offset)++; - } - } - if(err != ERR_NONE) { - return ERR_AGAIN; - } - - if((!sendCrc && (*offset == length)) || (sendCrc && (*offset == (length + 2U)))) { - *outputBuf = eof; - (*actOutBufSize)++; - outputBufSize--; - outputBuf++; - } else { - return ERR_AGAIN; - } - - return err; -} - -ReturnCode iso15693VICCDecode( - const uint8_t* inBuf, - uint16_t inBufLen, - uint8_t* outBuf, - uint16_t outBufLen, - uint16_t* outBufPos, - uint16_t* bitsBeforeCol, - uint16_t ignoreBits, - bool picopassMode) { - ReturnCode err = ERR_NONE; - uint16_t crc; - uint16_t mp; /* Current bit position in manchester bit inBuf*/ - uint16_t bp; /* Current bit position in outBuf */ - - *bitsBeforeCol = 0; - *outBufPos = 0; - - /* first check for valid SOF. Since it starts with 3 unmodulated pulses it is 0x17. */ - if((inBuf[0] & 0x1fU) != 0x17U) { - ISO_15693_DEBUG("0x%x\n", iso15693PhyBitBuffer[0]); - return ERR_FRAMING; - } - ISO_15693_DEBUG("SOF\n"); - - if(outBufLen == 0U) { - return ERR_NONE; - } - - mp = 5; /* 5 bits were SOF, now manchester starts: 2 bits per payload bit */ - bp = 0; - - ST_MEMSET(outBuf, 0, outBufLen); - - if(inBufLen == 0U) { - return ERR_CRC; - } - - for(; mp < ((inBufLen * 8U) - 2U); mp += 2U) { - bool isEOF = false; - - uint8_t man; - man = (inBuf[mp / 8U] >> (mp % 8U)) & 0x1U; - man |= ((inBuf[(mp + 1U) / 8U] >> ((mp + 1U) % 8U)) & 0x1U) << 1; - if(1U == man) { - bp++; - } - if(2U == man) { - outBuf[bp / 8U] = (uint8_t)(outBuf[bp / 8U] | (1U << (bp % 8U))); /* MISRA 10.3 */ - bp++; - } - if((bp % 8U) == 0U) { /* Check for EOF */ - ISO_15693_DEBUG("ceof %hhx %hhx\n", inBuf[mp / 8U], inBuf[mp / 8 + 1]); - if(((inBuf[mp / 8U] & 0xe0U) == 0xa0U) && - (inBuf[(mp / 8U) + 1U] == 0x03U)) { /* Now we know that it was 10111000 = EOF */ - ISO_15693_DEBUG("EOF\n"); - isEOF = true; - } - } - if(((0U == man) || (3U == man)) && !isEOF) { - if(bp >= ignoreBits) { - err = ERR_RF_COLLISION; - } else { - /* ignored collision: leave as 0 */ - bp++; - } - } - if((bp >= (outBufLen * 8U)) || (err == ERR_RF_COLLISION) || - isEOF) { /* Don't write beyond the end */ - break; - } - } - - *outBufPos = (bp / 8U); - *bitsBeforeCol = bp; - - if(err != ERR_NONE) { - return err; - } - - if((bp % 8U) != 0U) { - return ERR_CRC; - } - - if(*outBufPos > 2U) { - /* finally, check crc */ - ISO_15693_DEBUG("Calculate CRC, val: 0x%x, outBufLen: ", *outBuf); - ISO_15693_DEBUG("0x%x ", *outBufPos - 2); - - crc = rfalCrcCalculateCcitt(((picopassMode) ? 0xE012U : 0xFFFFU), outBuf, *outBufPos - 2U); - crc = (uint16_t)((picopassMode) ? crc : ~crc); - - if(((crc & 0xffU) == outBuf[*outBufPos - 2U]) && - (((crc >> 8U) & 0xffU) == outBuf[*outBufPos - 1U])) { - err = ERR_NONE; - ISO_15693_DEBUG("OK\n"); - } else { - ISO_15693_DEBUG("error! Expected: 0x%x, got ", crc); - ISO_15693_DEBUG("0x%hhx 0x%hhx\n", outBuf[*outBufPos - 2], outBuf[*outBufPos - 1]); - err = ERR_CRC; - } - } else { - err = ERR_CRC; - } - - return err; -} - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief Perform 1 of 4 coding and send coded data - * - * This function takes \a length bytes from \a buffer, perform 1 of 4 coding - * (see ISO15693-2 specification) and sends the data using stream mode. - * - * \param[in] sendSof : send SOF prior to data. - * \param[in] buffer : data to send. - * \param[in] length : number of bytes to send. - * - * \return ERR_IO : Error during communication. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -static ReturnCode iso15693PhyVCDCode1Of4( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen) { - uint8_t tmp; - ReturnCode err = ERR_NONE; - uint16_t a; - uint8_t* outbuf = outbuffer; - - *outBufLen = 0; - - if(maxOutBufLen < 4U) { - return ERR_NOMEM; - } - - tmp = data; - for(a = 0; a < 4U; a++) { - switch(tmp & 0x3U) { - case 0: - *outbuf = ISO15693_DAT_00_1_4; - break; - case 1: - *outbuf = ISO15693_DAT_01_1_4; - break; - case 2: - *outbuf = ISO15693_DAT_10_1_4; - break; - case 3: - *outbuf = ISO15693_DAT_11_1_4; - break; - default: - /* MISRA 16.4: mandatory default statement */ - break; - } - outbuf++; - (*outBufLen)++; - tmp >>= 2; - } - return err; -} - -/*! - ***************************************************************************** - * \brief Perform 1 of 256 coding and send coded data - * - * This function takes \a length bytes from \a buffer, perform 1 of 256 coding - * (see ISO15693-2 specification) and sends the data using stream mode. - * \note This function sends SOF prior to the data. - * - * \param[in] sendSof : send SOF prior to data. - * \param[in] buffer : data to send. - * \param[in] length : number of bytes to send. - * - * \return ERR_IO : Error during communication. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -static ReturnCode iso15693PhyVCDCode1Of256( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen) { - uint8_t tmp; - ReturnCode err = ERR_NONE; - uint16_t a; - uint8_t* outbuf = outbuffer; - - *outBufLen = 0; - - if(maxOutBufLen < 64U) { - return ERR_NOMEM; - } - - tmp = data; - for(a = 0; a < 64U; a++) { - switch(tmp) { - case 0: - *outbuf = ISO15693_DAT_SLOT0_1_256; - break; - case 1: - *outbuf = ISO15693_DAT_SLOT1_1_256; - break; - case 2: - *outbuf = ISO15693_DAT_SLOT2_1_256; - break; - case 3: - *outbuf = ISO15693_DAT_SLOT3_1_256; - break; - default: - *outbuf = 0; - break; - } - outbuf++; - (*outBufLen)++; - tmp -= 4U; - } - - return err; -} - -#endif /* RFAL_FEATURE_NFCV */ diff --git a/lib/ST25RFAL002/source/rfal_isoDep.c b/lib/ST25RFAL002/source/rfal_isoDep.c deleted file mode 100644 index 13674788e8..0000000000 --- a/lib/ST25RFAL002/source/rfal_isoDep.c +++ /dev/null @@ -1,3032 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: NFCC firmware - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_isoDep.c - * - * \author Gustavo Patricio - * - * \brief Implementation of ISO-DEP protocol - * - * This implementation was based on the following specs: - * - ISO/IEC 14443-4 2nd Edition 2008-07-15 - * - NFC Forum Digital Protocol 1.1 2014-01-14 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ - -#include "rfal_isoDep.h" -#include "rfal_rf.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#if RFAL_FEATURE_ISO_DEP - -#if(!RFAL_FEATURE_ISO_DEP_POLL && !RFAL_FEATURE_ISO_DEP_LISTEN) -#error \ - " RFAL: Invalid ISO-DEP Configuration. Please select at least one mode: Poller and/or Listener. " -#endif - -/* Check for valid I-Block length [RFAL_ISODEP_FSX_16 ; RFAL_ISODEP_FSX_4096]*/ -#if((RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN > 4096) || (RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN < 16)) -#error \ - " RFAL: Invalid ISO-DEP IBlock Max length. Please change RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN. " -#endif - -/* Check for valid APDU length. */ -#if((RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN < RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN)) -#error " RFAL: Invalid ISO-DEP APDU Max length. Please change RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN. " -#endif - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define ISODEP_CRC_LEN RFAL_CRC_LEN /*!< ISO1443 CRC Length */ - -#define ISODEP_PCB_POS (0U) /*!< PCB position on message header*/ -#define ISODEP_SWTX_INF_POS (1U) /*!< INF position in a S-WTX */ - -#define ISODEP_DID_POS (1U) /*!< DID position on message header*/ -#define ISODEP_SWTX_PARAM_LEN (1U) /*!< SWTX parameter length */ - -#define ISODEP_DSL_MAX_LEN \ - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN) /*!< Deselect Req/Res length */ - -#define ISODEP_PCB_xBLOCK_MASK (0xC0U) /*!< Bit mask for Block type */ -#define ISODEP_PCB_IBLOCK (0x00U) /*!< Bit mask indicating a I-Block */ -#define ISODEP_PCB_RBLOCK (0x80U) /*!< Bit mask indicating a R-Block */ -#define ISODEP_PCB_SBLOCK (0xC0U) /*!< Bit mask indicating a S-Block */ -#define ISODEP_PCB_INVALID (0x40U) /*!< Bit mask of an Invalid PCB */ - -#define ISODEP_HDR_MAX_LEN \ - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN + \ - RFAL_ISODEP_NAD_LEN) /*!< Max header length (PCB + DID + NAD) */ - -#define ISODEP_PCB_IB_VALID_MASK \ - (ISODEP_PCB_B6_BIT | ISODEP_PCB_B2_BIT) /*!< Bit mask for the MUST bits on I-Block */ -#define ISODEP_PCB_IB_VALID_VAL \ - (ISODEP_PCB_B2_BIT) /*!< Value for the MUST bits on I-Block */ -#define ISODEP_PCB_RB_VALID_MASK \ - (ISODEP_PCB_B6_BIT | ISODEP_PCB_B3_BIT | \ - ISODEP_PCB_B2_BIT) /*!< Bit mask for the MUST bits on R-Block */ -#define ISODEP_PCB_RB_VALID_VAL \ - (ISODEP_PCB_B6_BIT | ISODEP_PCB_B2_BIT) /*!< Value for the MUST bits on R-Block */ -#define ISODEP_PCB_SB_VALID_MASK \ - (ISODEP_PCB_B3_BIT | ISODEP_PCB_B2_BIT | \ - ISODEP_PCB_B1_BIT) /*!< Bit mask for the MUST bits on I-Block */ -#define ISODEP_PCB_SB_VALID_VAL \ - (ISODEP_PCB_B2_BIT) /*!< Value for the MUST bits on I-Block */ - -#define ISODEP_PCB_B1_BIT \ - (0x01U) /*!< Bit mask for the RFU S Blocks */ -#define ISODEP_PCB_B2_BIT \ - (0x02U) /*!< Bit mask for the RFU bit2 in I,S,R Blocks */ -#define ISODEP_PCB_B3_BIT \ - (0x04U) /*!< Bit mask for the RFU bit3 in R Blocks */ -#define ISODEP_PCB_B6_BIT \ - (0x20U) /*!< Bit mask for the RFU bit2 in R Blocks */ -#define ISODEP_PCB_CHAINING_BIT \ - (0x10U) /*!< Bit mask for the chaining bit of an ISO DEP I-Block in PCB. */ -#define ISODEP_PCB_DID_BIT \ - (0x08U) /*!< Bit mask for the DID presence bit of an ISO DEP I,S,R Blocks PCB. */ -#define ISODEP_PCB_NAD_BIT \ - (0x04U) /*!< Bit mask for the NAD presence bit of an ISO DEP I,S,R Blocks in PCB */ -#define ISODEP_PCB_BN_MASK \ - (0x01U) /*!< Bit mask for the block number of an ISO DEP I,R Block in PCB */ - -#define ISODEP_SWTX_PL_MASK \ - (0xC0U) /*!< Bit mask for the Power Level bits of the inf byte of an WTX request or response */ -#define ISODEP_SWTX_WTXM_MASK \ - (0x3FU) /*!< Bit mask for the WTXM bits of the inf byte of an WTX request or response */ - -#define ISODEP_RBLOCK_INF_LEN (0U) /*!< INF length of R-Block Digital 1.1 15.1.3 */ -#define ISODEP_SDSL_INF_LEN (0U) /*!< INF length of S(DSL) Digital 1.1 15.1.3 */ -#define ISODEP_SWTX_INF_LEN (1U) /*!< INF length of S(WTX) Digital 1.1 15.2.2 */ - -#define ISODEP_WTXM_MIN (1U) /*!< Minimum allowed value for the WTXM, Digital 1.0 13.2.2 */ -#define ISODEP_WTXM_MAX (59U) /*!< Maximum allowed value for the WTXM, Digital 1.0 13.2.2 */ - -#define ISODEP_PCB_Sxx_MASK (0x30U) /*!< Bit mask for the S-Block type */ -#define ISODEP_PCB_DESELECT (0x00U) /*!< Bit mask for S-Block indicating Deselect */ -#define ISODEP_PCB_WTX (0x30U) /*!< Bit mask for S-Block indicating Waiting Time eXtension */ - -#define ISODEP_PCB_Rx_MASK (0x10U) /*!< Bit mask for the R-Block type */ -#define ISODEP_PCB_ACK (0x00U) /*!< Bit mask for R-Block indicating ACK */ -#define ISODEP_PCB_NAK (0x10U) /*!< Bit mask for R-Block indicating NAK */ - -/*! Maximum length of control message (no INF) */ -#define ISODEP_CONTROLMSG_BUF_LEN \ - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN + RFAL_ISODEP_NAD_LEN + ISODEP_SWTX_PARAM_LEN) - -#define ISODEP_FWT_DEACTIVATION \ - (71680U) /*!< FWT used for DESELECT Digital 2.2 B10 ISO1444-4 7.2 & 8.1 */ -#define ISODEP_MAX_RERUNS (0x0FFFFFFFU) /*!< Maximum rerun retrys for a blocking protocol run*/ - -#define ISODEP_PCBSBLOCK \ - (0x00U | ISODEP_PCB_SBLOCK | ISODEP_PCB_B2_BIT) /*!< PCB Value of a S-Block */ -#define ISODEP_PCB_SDSL \ - (ISODEP_PCBSBLOCK | ISODEP_PCB_DESELECT) /*!< PCB Value of a S-Block with DESELECT */ -#define ISODEP_PCB_SWTX \ - (ISODEP_PCBSBLOCK | ISODEP_PCB_WTX) /*!< PCB Value of a S-Block with WTX */ -#define ISODEP_PCB_SPARAMETERS \ - (ISODEP_PCB_SBLOCK | ISODEP_PCB_WTX) /*!< PCB Value of a S-Block with PARAMETERS */ - -#define ISODEP_FWI_LIS_MAX_NFC \ - 8U /*!< FWT Listener Max FWIT4ATmax FWIBmax Digital 1.1 A6 & A3 */ -#define ISODEP_FWI_LIS_MAX_EMVCO \ - 7U /*!< FWT Listener Max FWIMAX EMVCo 2.6 A.5 */ -#define ISODEP_FWI_LIS_MAX \ - (uint8_t)( \ - (gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) ? \ - ISODEP_FWI_LIS_MAX_EMVCO : \ - ISODEP_FWI_LIS_MAX_NFC) /*!< FWI Listener Max as NFC / EMVCo */ -#define ISODEP_FWT_LIS_MAX \ - rfalIsoDepFWI2FWT(ISODEP_FWI_LIS_MAX) /*!< FWT Listener Max */ - -#define ISODEP_FWI_MIN_10 (1U) /*!< Minimum value for FWI Digital 1.0 11.6.2.17 */ -#define ISODEP_FWI_MIN_11 (0U) /*!< Default value for FWI Digital 1.1 13.6.2 */ -#define ISODEP_FWI_MAX (14U) /*!< Maximum value for FWI Digital 1.0 11.6.2.17 */ -#define ISODEP_SFGI_MIN (0U) /*!< Default value for FWI Digital 1.1 13.6.2.22 */ -#define ISODEP_SFGI_MAX (14U) /*!< Maximum value for FWI Digital 1.1 13.6.2.22 */ - -#define RFAL_ISODEP_SPARAM_TVL_HDR_LEN (2U) /*!< S(PARAMETERS) TVL header length: Tag + Len */ -#define RFAL_ISODEP_SPARAM_HDR_LEN \ - (RFAL_ISODEP_PCB_LEN + \ - RFAL_ISODEP_SPARAM_TVL_HDR_LEN) /*!< S(PARAMETERS) header length: PCB + Tag + Len */ - -/**********************************************************************************************************************/ -/**********************************************************************************************************************/ -#define RFAL_ISODEP_NO_PARAM (0U) /*!< No parameter flag for isoDepHandleControlMsg() */ - -#define RFAL_ISODEP_CMD_RATS (0xE0U) /*!< RATS command Digital 1.1 13.6.1 */ - -#define RFAL_ISODEP_ATS_MIN_LEN (1U) /*!< Minimum ATS length Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_HDR_LEN (5U) /*!< ATS headerlength Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_MAX_LEN \ - (RFAL_ISODEP_ATS_HDR_LEN + \ - RFAL_ISODEP_ATS_HB_MAX_LEN) /*!< Maximum ATS length Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_T0_FSCI_MASK (0x0FU) /*!< ATS T0's FSCI mask Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_TB_FWI_SHIFT (4U) /*!< ATS TB's FWI shift Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_FWI_MASK (0x0FU) /*!< ATS TB's FWI shift Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_TL_POS (0x00U) /*!< ATS TL's position Digital 1.1 13.6.2 */ - -#define RFAL_ISODEP_PPS_SB (0xD0U) /*!< PPS REQ PPSS's SB value (no CID) ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_MASK (0xF0U) /*!< PPS REQ PPSS's SB mask ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_SB_DID_MASK \ - (0x0FU) /*!< PPS REQ PPSS's DID|CID mask ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_PPS0_PPS1_PRESENT \ - (0x11U) /*!< PPS REQ PPS0 indicating that PPS1 is present */ -#define RFAL_ISODEP_PPS_PPS1 (0x00U) /*!< PPS REQ PPS1 fixed value ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_PPS1_DSI_SHIFT \ - (2U) /*!< PPS REQ PPS1 fixed value ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_PPS1_DXI_MASK \ - (0x0FU) /*!< PPS REQ PPS1 fixed value ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_RES_LEN (1U) /*!< PPS Response length ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS_STARTBYTE_POS \ - (0U) /*!< PPS REQ PPSS's byte position ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS_PPS0_POS (1U) /*!< PPS REQ PPS0's byte position ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS_PPS1_POS (2U) /*!< PPS REQ PPS1's byte position ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS0_VALID_MASK \ - (0xEFU) /*!< PPS REQ PPS0 valid coding mask ISO14443-4 5.4 */ - -#define RFAL_ISODEP_CMD_ATTRIB (0x1DU) /*!< ATTRIB command Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_DSI_SHIFT \ - (6U) /*!< ATTRIB PARAM2 DSI shift Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_DRI_SHIFT \ - (4U) /*!< ATTRIB PARAM2 DRI shift Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_DXI_MASK \ - (0xF0U) /*!< ATTRIB PARAM2 DxI mask Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_FSDI_MASK \ - (0x0FU) /*!< ATTRIB PARAM2 FSDI mask Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM4_DID_MASK \ - (0x0FU) /*!< ATTRIB PARAM4 DID mask Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_HDR_LEN (9U) /*!< ATTRIB REQ header length Digital 1.1 14.6.1 */ - -#define RFAL_ISODEP_ATTRIB_RES_HDR_LEN \ - (1U) /*!< ATTRIB RES header length Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_MBLIDID_POS \ - (0U) /*!< ATTRIB RES MBLI|DID position Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_DID_MASK \ - (0x0FU) /*!< ATTRIB RES DID mask Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_MBLI_MASK \ - (0x0FU) /*!< ATTRIB RES MBLI mask Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_MBLI_SHIFT \ - (4U) /*!< ATTRIB RES MBLI shift Digital 1.1 14.6.2 */ - -#define RFAL_ISODEP_DID_MASK (0x0FU) /*!< ISODEP's DID mask */ -#define RFAL_ISODEP_DID_00 (0U) /*!< ISODEP's DID value 0 */ - -#define RFAL_ISODEP_FSDI_MAX_NFC (8U) /*!< Max FSDI value Digital 2.0 14.6.1.9 & B7 & B8 */ -#define RFAL_ISODEP_FSDI_MAX_NFC_21 \ - (0x0CU) /*!< Max FSDI value Digital 2.1 14.6.1.9 & Table 72 */ -#define RFAL_ISODEP_FSDI_MAX_EMV (0x0CU) /*!< Max FSDI value EMVCo 3.0 5.7.2.5 */ - -#define RFAL_ISODEP_RATS_PARAM_FSDI_MASK \ - (0xF0U) /*!< Mask bits for FSDI in RATS */ -#define RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT \ - (4U) /*!< Shift for FSDI in RATS */ -#define RFAL_ISODEP_RATS_PARAM_DID_MASK \ - (0x0FU) /*!< Mask bits for DID in RATS */ - -#define RFAL_ISODEP_ATS_TL_OFFSET \ - (0x00U) /*!< Offset of TL on ATS */ -#define RFAL_ISODEP_ATS_TA_OFFSET \ - (0x02U) /*!< Offset of TA if it is present on ATS */ -#define RFAL_ISODEP_ATS_TB_OFFSET \ - (0x03U) /*!< Offset of TB if both TA and TB is present on ATS */ -#define RFAL_ISODEP_ATS_TC_OFFSET \ - (0x04U) /*!< Offset of TC if both TA,TB & TC are present on ATS */ -#define RFAL_ISODEP_ATS_HIST_OFFSET \ - (0x05U) /*!< Offset of Historical Bytes if TA, TB & TC are present on ATS */ -#define RFAL_ISODEP_ATS_TC_ADV_FEAT \ - (0x10U) /*!< Bit mask indicating support for Advanced protocol features: DID & NAD */ -#define RFAL_ISODEP_ATS_TC_DID (0x02U) /*!< Bit mask indicating support for DID */ -#define RFAL_ISODEP_ATS_TC_NAD (0x01U) /*!< Bit mask indicating support for NAD */ - -#define RFAL_ISODEP_PPS0_PPS1_PRESENT \ - (0x11U) /*!< PPS0 byte indicating that PPS1 is present */ -#define RFAL_ISODEP_PPS0_PPS1_NOT_PRESENT \ - (0x01U) /*!< PPS0 byte indicating that PPS1 is NOT present */ -#define RFAL_ISODEP_PPS1_DRI_MASK \ - (0x03U) /*!< PPS1 byte DRI mask bits */ -#define RFAL_ISODEP_PPS1_DSI_MASK \ - (0x0CU) /*!< PPS1 byte DSI mask bits */ -#define RFAL_ISODEP_PPS1_DSI_SHIFT \ - (2U) /*!< PPS1 byte DSI shift */ -#define RFAL_ISODEP_PPS1_DxI_MASK \ - (0x03U) /*!< PPS1 byte DSI/DRS mask bits */ - -/*! Delta Time for polling during Activation (ATS) : 20ms Digital 1.0 11.7.1.1 & A.7 */ -#define RFAL_ISODEP_T4T_DTIME_POLL_10 rfalConvMsTo1fc(20) - -/*! Delta Time for polling during Activation (ATS) : 16.4ms Digital 1.1 13.8.1.1 & A.6 - * Use 16 ms as testcase T4AT_BI_10_03 sends a frame exactly at the border */ -#define RFAL_ISODEP_T4T_DTIME_POLL_11 216960U - -/*! Activation frame waiting time FWT(act) = 71680/fc (~5286us) Digital 1.1 13.8.1.1 & A.6 */ -#define RFAL_ISODEP_T4T_FWT_ACTIVATION (71680U + RFAL_ISODEP_T4T_DTIME_POLL_11) - -/*! Delta frame waiting time = 16/fc Digital 1.0 11.7.1.3 & A.7*/ -#define RFAL_ISODEP_DFWT_10 16U - -/*! Delta frame waiting time = 16/fc Digital 2.0 14.8.1.3 & B.7*/ -#define RFAL_ISODEP_DFWT_20 49152U - -/* - ****************************************************************************** - * MACROS - ****************************************************************************** - */ - -#define isoDep_PCBisIBlock(pcb) \ - (((pcb) & (ISODEP_PCB_xBLOCK_MASK | ISODEP_PCB_IB_VALID_MASK)) == \ - (ISODEP_PCB_IBLOCK | ISODEP_PCB_IB_VALID_VAL)) /*!< Checks if pcb is a I-Block */ -#define isoDep_PCBisRBlock(pcb) \ - (((pcb) & (ISODEP_PCB_xBLOCK_MASK | ISODEP_PCB_RB_VALID_MASK)) == \ - (ISODEP_PCB_RBLOCK | ISODEP_PCB_RB_VALID_VAL)) /*!< Checks if pcb is a R-Block */ -#define isoDep_PCBisSBlock(pcb) \ - (((pcb) & (ISODEP_PCB_xBLOCK_MASK | ISODEP_PCB_SB_VALID_MASK)) == \ - (ISODEP_PCB_SBLOCK | ISODEP_PCB_SB_VALID_VAL)) /*!< Checks if pcb is a S-Block */ - -#define isoDep_PCBisChaining(pcb) \ - (((pcb)&ISODEP_PCB_CHAINING_BIT) == \ - ISODEP_PCB_CHAINING_BIT) /*!< Checks if pcb is indicating chaining */ - -#define isoDep_PCBisDeselect(pcb) \ - (((pcb)&ISODEP_PCB_Sxx_MASK) == \ - ISODEP_PCB_DESELECT) /*!< Checks if pcb is indicating DESELECT */ -#define isoDep_PCBisWTX(pcb) \ - (((pcb)&ISODEP_PCB_Sxx_MASK) == ISODEP_PCB_WTX) /*!< Checks if pcb is indicating WTX */ - -#define isoDep_PCBisACK(pcb) \ - (((pcb)&ISODEP_PCB_Rx_MASK) == ISODEP_PCB_ACK) /*!< Checks if pcb is indicating ACK */ -#define isoDep_PCBisNAK(pcb) \ - (((pcb)&ISODEP_PCB_Rx_MASK) == ISODEP_PCB_NAK) /*!< Checks if pcb is indicating ACK */ - -#define isoDep_PCBhasDID(pcb) \ - (((pcb)&ISODEP_PCB_DID_BIT) == ISODEP_PCB_DID_BIT) /*!< Checks if pcb is indicating DID */ -#define isoDep_PCBhasNAD(pcb) \ - (((pcb)&ISODEP_PCB_NAD_BIT) == ISODEP_PCB_NAD_BIT) /*!< Checks if pcb is indicating NAD */ - -#define isoDep_PCBisIChaining(pcb) \ - (isoDep_PCBisIBlock(pcb) && \ - isoDep_PCBisChaining(pcb)) /*!< Checks if pcb is I-Block indicating chaining*/ - -#define isoDep_PCBisSDeselect(pcb) \ - (isoDep_PCBisSBlock(pcb) && \ - isoDep_PCBisDeselect(pcb)) /*!< Checks if pcb is S-Block indicating DESELECT*/ -#define isoDep_PCBisSWTX(pcb) \ - (isoDep_PCBisSBlock(pcb) && \ - isoDep_PCBisWTX(pcb)) /*!< Checks if pcb is S-Block indicating WTX */ - -#define isoDep_PCBisRACK(pcb) \ - (isoDep_PCBisRBlock(pcb) && \ - isoDep_PCBisACK(pcb)) /*!< Checks if pcb is R-Block indicating ACK */ -#define isoDep_PCBisRNAK(pcb) \ - (isoDep_PCBisRBlock(pcb) && \ - isoDep_PCBisNAK(pcb)) /*!< Checks if pcb is R-Block indicating NAK */ - -#define isoDep_PCBIBlock(bn) \ - ((uint8_t)(0x00U | ISODEP_PCB_IBLOCK | ISODEP_PCB_B2_BIT | ((bn)&ISODEP_PCB_BN_MASK))) /*!< Returns an I-Block with the given block number (bn) */ -#define isoDep_PCBIBlockChaining(bn) \ - ((uint8_t)(isoDep_PCBIBlock(bn) | ISODEP_PCB_CHAINING_BIT)) /*!< Returns an I-Block with the given block number (bn) indicating chaining */ - -#define isoDep_PCBRBlock(bn) \ - ((uint8_t)(0x00U | ISODEP_PCB_RBLOCK | ISODEP_PCB_B6_BIT | ISODEP_PCB_B2_BIT | ((bn)&ISODEP_PCB_BN_MASK))) /*!< Returns an R-Block with the given block number (bn) */ -#define isoDep_PCBRACK(bn) \ - ((uint8_t)(isoDep_PCBRBlock(bn) | ISODEP_PCB_ACK)) /*!< Returns an R-Block with the given block number (bn) indicating ACK */ -#define isoDep_PCBRNAK(bn) \ - ((uint8_t)(isoDep_PCBRBlock(bn) | ISODEP_PCB_NAK)) /*!< Returns an R-Block with the given block number (bn) indicating NAK */ - -#define isoDep_GetBN(pcb) \ - ((uint8_t)((pcb)&ISODEP_PCB_BN_MASK)) /*!< Returns the block number (bn) from the given pcb */ -#define isoDep_GetWTXM(inf) \ - ((uint8_t)(( \ - inf)&ISODEP_SWTX_WTXM_MASK)) /*!< Returns the WTX value from the given inf byte */ -#define isoDep_isWTXMValid(wtxm) \ - (((wtxm) >= ISODEP_WTXM_MIN) && \ - ((wtxm) <= ISODEP_WTXM_MAX)) /*!< Checks if the given wtxm is valid */ - -#define isoDep_WTXMListenerMax(fwt) \ - (MIN( \ - (uint8_t)(ISODEP_FWT_LIS_MAX / (fwt)), \ - ISODEP_WTXM_MAX)) /*!< Calculates the Max WTXM value for the given fwt as a Listener */ - -#define isoDepCalcdSGFT(s) \ - (384U * ((uint32_t)1U \ - << (s))) /*!< Calculates the dSFGT with given SFGI Digital 1.1 13.8.2.1 & A.6*/ -#define isoDepCalcSGFT(s) \ - (4096U * ((uint32_t)1U \ - << (s))) /*!< Calculates the SFGT with given SFGI Digital 1.1 13.8.2 */ - -#define isoDep_PCBNextBN(bn) \ - (((uint8_t)(bn) ^ 0x01U) & \ - ISODEP_PCB_BN_MASK) /*!< Returns the value of the next block number based on bn */ -#define isoDep_PCBPrevBN(bn) \ - isoDep_PCBNextBN(bn) /*!< Returns the value of the previous block number based on bn */ -#define isoDep_ToggleBN(bn) \ - ((bn) = \ - (((bn) ^ 0x01U) & \ - ISODEP_PCB_BN_MASK)) /*!< Toggles the block number value of the given bn */ - -#define isoDep_WTXAdjust(v) \ - ((v) - ((v) >> 3)) /*!< Adjust WTX timer value to a percentage of the total, current 88% */ - -/*! ISO 14443-4 7.5.6.2 & Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ -#define isoDepReEnableRx(rxB, rxBL, rxL) \ - rfalTransceiveBlockingTx(NULL, 0, rxB, rxBL, rxL, RFAL_TXRX_FLAGS_DEFAULT, RFAL_FWT_NONE) - -/*! Macro used for the blocking methods */ -#define rfalIsoDepRunBlocking(e, fn) \ - do { \ - (e) = (fn); \ - rfalWorker(); \ - } while((e) == ERR_BUSY) - -#define isoDepTimerStart(timer, time_ms) \ - do { \ - platformTimerDestroy(timer); \ - (timer) = platformTimerCreate((uint16_t)(time_ms)); \ - } while(0) /*!< Configures and starts the WTX timer */ -#define isoDepTimerisExpired(timer) \ - platformTimerIsExpired(timer) /*!< Checks WTX timer has expired */ -#define isoDepTimerDestroy(timer) \ - platformTimerDestroy(timer) /*!< Destroys WTX timer */ - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -/*! Internal structure to be used in handling of S(PARAMETERS) only */ -typedef struct { - uint8_t pcb; /*!< PCB byte */ - rfalIsoDepSParameter sParam; /*!< S(PARAMETERS) */ -} rfalIsoDepControlMsgSParam; - -/*! Enumeration of the possible control message types */ -typedef enum { - ISODEP_R_ACK, /*!< R-ACK Acknowledge */ - ISODEP_R_NAK, /*!< R-NACK Negative acknowledge */ - ISODEP_S_WTX, /*!< S-WTX Waiting Time Extension */ - ISODEP_S_DSL /*!< S-DSL Deselect */ -} rfalIsoDepControlMsg; - -/*! Enumeration of the IsoDep roles */ -typedef enum { - ISODEP_ROLE_PCD, /*!< Perform as Reader/PCD */ - ISODEP_ROLE_PICC /*!< Perform as Card/PICC */ -} rfalIsoDepRole; - -/*! ISO-DEP layer states */ -typedef enum { - ISODEP_ST_IDLE, /*!< Idle State */ - ISODEP_ST_PCD_TX, /*!< PCD Transmission State */ - ISODEP_ST_PCD_RX, /*!< PCD Reception State */ - ISODEP_ST_PCD_WAIT_DSL, /*!< PCD Wait for DSL response */ - - ISODEP_ST_PICC_ACT_ATS, /*!< PICC has replied to RATS (ATS) */ - ISODEP_ST_PICC_ACT_ATTRIB, /*!< PICC has replied to ATTRIB */ - ISODEP_ST_PICC_RX, /*!< PICC Reception State */ - ISODEP_ST_PICC_SWTX, /*!< PICC Waiting Time eXtension */ - ISODEP_ST_PICC_SDSL, /*!< PICC S(DSL) response ongoing */ - ISODEP_ST_PICC_TX, /*!< PICC Transmission State */ - - ISODEP_ST_PCD_ACT_RATS, /*!< PCD activation (RATS) */ - ISODEP_ST_PCD_ACT_PPS, /*!< PCD activation (PPS) */ - -} rfalIsoDepState; - -/*! Holds all ISO-DEP data(counters, buffers, ID, timeouts, frame size) */ -typedef struct { - rfalIsoDepState state; /*!< ISO-DEP module state */ - rfalIsoDepRole role; /*!< Current ISO-DEP role */ - - uint8_t blockNumber; /*!< Current block number */ - uint8_t did; /*!< Current DID */ - uint8_t nad; /*!< Current DID */ - uint8_t cntIRetrys; /*!< I-Block retry counter */ - uint8_t cntRRetrys; /*!< R-Block retry counter */ - uint8_t cntSDslRetrys; /*!< S(DESELECT) retry counter */ - uint8_t cntSWtxRetrys; /*!< Overall S(WTX) retry counter */ - uint8_t cntSWtxNack; /*!< R(NACK) answered with S(WTX) counter */ - uint32_t fwt; /*!< Current FWT (Frame Waiting Time) */ - uint32_t dFwt; /*!< Current delta FWT */ - uint16_t fsx; /*!< Current FSx FSC or FSD (max Frame size) */ - bool isTxChaining; /*!< Flag for chaining on Tx */ - bool isRxChaining; /*!< Flag for chaining on Rx */ - uint8_t* txBuf; /*!< Tx buffer pointer */ - uint8_t* rxBuf; /*!< Rx buffer pointer */ - uint16_t txBufLen; /*!< Tx buffer length */ - uint16_t rxBufLen; /*!< Rx buffer length */ - uint8_t txBufInfPos; /*!< Start of payload in txBuf */ - uint8_t rxBufInfPos; /*!< Start of payload in rxBuf */ - - uint16_t ourFsx; /*!< Our current FSx FSC or FSD (Frame size) */ - uint8_t lastPCB; /*!< Last PCB sent */ - uint8_t lastWTXM; /*!< Last WTXM sent */ - uint8_t atsTA; /*!< TA on ATS */ - uint8_t hdrLen; /*!< Current ISO-DEP length */ - rfalBitRate txBR; /*!< Current Tx Bit Rate */ - rfalBitRate rxBR; /*!< Current Rx Bit Rate */ - uint16_t* rxLen; /*!< Output parameter ptr to Rx length */ - bool* rxChaining; /*!< Output parameter ptr to Rx chaining flag */ - uint32_t WTXTimer; /*!< Timer used for WTX */ - bool lastDID00; /*!< Last PCD block had DID flag (for DID = 0) */ - - bool isTxPending; /*!< Flag pending Block while waiting WTX Ack */ - bool isWait4WTX; /*!< Flag for waiting WTX Ack */ - - uint8_t maxRetriesI; /*!< Number of retries for a I-Block */ - uint8_t maxRetriesR; /*!< Number of retries for a R-Block */ - uint8_t maxRetriesSDSL; /*!< Number of retries for S(DESELECT) errors */ - uint8_t maxRetriesSWTX; /*!< Number of retries for S(WTX) errors */ - uint8_t maxRetriesSnWTX; /*!< Number of retries S(WTX) replied w NACK */ - uint8_t maxRetriesRATS; /*!< Number of retries for RATS */ - - rfalComplianceMode compMode; /*!< Compliance mode */ - - uint8_t ctrlBuf[ISODEP_CONTROLMSG_BUF_LEN]; /*!< Control msg buf */ - uint16_t ctrlRxLen; /*!< Control msg rcvd len */ - - union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one frame at a time */ -#if RFAL_FEATURE_NFCA - rfalIsoDepRats ratsReq; - rfalIsoDepPpsReq ppsReq; -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB - rfalIsoDepAttribCmd attribReq; -#endif /* RFAL_FEATURE_NFCB */ - } actv; /*!< Activation buffer */ - - uint8_t* rxLen8; /*!< Receive length (8-bit) */ - rfalIsoDepDevice* actvDev; /*!< Activation Device Info */ - rfalIsoDepListenActvParam actvParam; /*!< Listen Activation context */ - - rfalIsoDepApduTxRxParam APDUParam; /*!< APDU TxRx params */ - uint16_t APDUTxPos; /*!< APDU Tx position */ - uint16_t APDURxPos; /*!< APDU Rx position */ - bool isAPDURxChaining; /*!< APDU Transceive chaining flag */ - -} rfalIsoDep; - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -static rfalIsoDep gIsoDep; /*!< ISO-DEP Module instance */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ -static void isoDepClearCounters(void); -static ReturnCode - isoDepTx(uint8_t pcb, const uint8_t* txBuf, uint8_t* infBuf, uint16_t infLen, uint32_t fwt); -static ReturnCode isoDepHandleControlMsg(rfalIsoDepControlMsg controlMsg, uint8_t param); -static void rfalIsoDepApdu2IBLockParam( - rfalIsoDepApduTxRxParam apduParam, - rfalIsoDepTxRxParam* iBlockParam, - uint16_t txPos, - uint16_t rxPos); - -#if RFAL_FEATURE_ISO_DEP_POLL -static ReturnCode isoDepDataExchangePCD(uint16_t* outActRxLen, bool* outIsChaining); -static void rfalIsoDepCalcBitRate( - rfalBitRate maxAllowedBR, - uint8_t piccBRCapability, - rfalBitRate* dsi, - rfalBitRate* dri); -static uint32_t rfalIsoDepSFGI2SFGT(uint8_t sfgi); - -#if RFAL_FEATURE_NFCA -static ReturnCode - rfalIsoDepStartRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen); -static ReturnCode rfalIsoDepGetRATSStatus(void); -static ReturnCode - rfalIsoDepStartPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes); -static ReturnCode rfalIsoDepGetPPSSTatus(void); -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB -static ReturnCode rfalIsoDepStartATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen); -static ReturnCode rfalIsoDepGetATTRIBStatus(void); -#endif /* RFAL_FEATURE_NFCB */ - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - -#if RFAL_FEATURE_ISO_DEP_LISTEN -static ReturnCode isoDepDataExchangePICC(void); -static ReturnCode isoDepReSendControlMsg(void); -#endif - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -static void isoDepClearCounters(void) { - gIsoDep.cntIRetrys = 0; - gIsoDep.cntRRetrys = 0; - gIsoDep.cntSDslRetrys = 0; - gIsoDep.cntSWtxRetrys = 0; - gIsoDep.cntSWtxNack = 0; -} - -/*******************************************************************************/ -static ReturnCode - isoDepTx(uint8_t pcb, const uint8_t* txBuf, uint8_t* infBuf, uint16_t infLen, uint32_t fwt) { - uint8_t* txBlock; - uint16_t txBufLen; - uint8_t computedPcb; - rfalTransceiveContext ctx; - - txBlock = infBuf; /* Point to beginning of the INF, and go backwards */ - gIsoDep.lastPCB = pcb; /* Store the last PCB sent */ - - if(infLen > 0U) { - if(((uint32_t)infBuf - (uint32_t)txBuf) < - gIsoDep.hdrLen) /* Check that we can fit the header in the given space */ - { - return ERR_NOMEM; - } - } - - /*******************************************************************************/ - /* Compute optional PCB bits */ - computedPcb = pcb; - if((gIsoDep.did != RFAL_ISODEP_NO_DID) || - ((gIsoDep.did == RFAL_ISODEP_DID_00) && gIsoDep.lastDID00)) { - computedPcb |= ISODEP_PCB_DID_BIT; - } - if(gIsoDep.nad != RFAL_ISODEP_NO_NAD) { - computedPcb |= ISODEP_PCB_NAD_BIT; - } - if((gIsoDep.isTxChaining) && (isoDep_PCBisIBlock(computedPcb))) { - computedPcb |= ISODEP_PCB_CHAINING_BIT; - } - - /*******************************************************************************/ - /* Compute Payload on the given txBuf, start by the PCB | DID | NAD | before INF */ - - if(gIsoDep.nad != RFAL_ISODEP_NO_NAD) { - *(--txBlock) = gIsoDep.nad; /* NAD is optional */ - } - - if((gIsoDep.did != RFAL_ISODEP_NO_DID) || - ((gIsoDep.did == RFAL_ISODEP_DID_00) && gIsoDep.lastDID00)) { - *(--txBlock) = gIsoDep.did; /* DID is optional */ - } - - *(--txBlock) = computedPcb; /* PCB always present */ - - txBufLen = - (infLen + - (uint16_t)((uint32_t)infBuf - (uint32_t)txBlock)); /* Calculate overall buffer size */ - - if(txBufLen > (gIsoDep.fsx - - ISODEP_CRC_LEN)) /* Check if msg length violates the maximum frame size FSC */ - { - return ERR_NOTSUPP; - } - - rfalCreateByteFlagsTxRxContext( - ctx, - txBlock, - txBufLen, - gIsoDep.rxBuf, - gIsoDep.rxBufLen, - gIsoDep.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - ((gIsoDep.role == ISODEP_ROLE_PICC) ? RFAL_FWT_NONE : fwt)); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode isoDepHandleControlMsg(rfalIsoDepControlMsg controlMsg, uint8_t param) { - uint8_t pcb; - uint8_t infLen; - uint32_t fwtTemp; - - infLen = 0; - fwtTemp = (gIsoDep.fwt + gIsoDep.dFwt); - ST_MEMSET(gIsoDep.ctrlBuf, 0x00, ISODEP_CONTROLMSG_BUF_LEN); - - switch(controlMsg) { - /*******************************************************************************/ - case ISODEP_R_ACK: - - if(gIsoDep.cntRRetrys++ > gIsoDep.maxRetriesR) { - return ERR_TIMEOUT; /* NFC Forum mandates timeout or transmission error depending on previous errors */ - } - - pcb = isoDep_PCBRACK(gIsoDep.blockNumber); - break; - - /*******************************************************************************/ - case ISODEP_R_NAK: - - if((gIsoDep.cntRRetrys++ > gIsoDep.maxRetriesR) || /* Max R Block retries reached */ - (gIsoDep.cntSWtxNack >= - gIsoDep - .maxRetriesSnWTX)) /* Max number PICC is allowed to respond with S(WTX) to R(NAK) */ - { - return ERR_TIMEOUT; - } - - pcb = isoDep_PCBRNAK(gIsoDep.blockNumber); - break; - - /*******************************************************************************/ - case ISODEP_S_WTX: - - if((gIsoDep.cntSWtxRetrys++ > gIsoDep.maxRetriesSWTX) && - (gIsoDep.maxRetriesSWTX != RFAL_ISODEP_MAX_WTX_RETRYS_ULTD)) { - return ERR_PROTO; - } - - /* Check if WTXM is valid */ - if(!isoDep_isWTXMValid(param)) { - return ERR_PROTO; - } - - if(gIsoDep.role == ISODEP_ROLE_PCD) { - /* Calculate temp Wait Time eXtension */ - fwtTemp = (gIsoDep.fwt * param); - fwtTemp = MIN(RFAL_ISODEP_MAX_FWT, fwtTemp); - fwtTemp += gIsoDep.dFwt; - } - - pcb = ISODEP_PCB_SWTX; - gIsoDep.ctrlBuf[RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN + infLen++] = param; - break; - - /*******************************************************************************/ - case ISODEP_S_DSL: - - if(gIsoDep.cntSDslRetrys++ > gIsoDep.maxRetriesSDSL) { - return ERR_TIMEOUT; /* NFC Forum mandates timeout or transmission error depending on previous errors */ - } - - if(gIsoDep.role == ISODEP_ROLE_PCD) { - /* Digital 1.0 - 13.2.7.3 Poller must wait fwtDEACTIVATION */ - fwtTemp = ISODEP_FWT_DEACTIVATION; - gIsoDep.state = ISODEP_ST_PCD_WAIT_DSL; - } - pcb = ISODEP_PCB_SDSL; - break; - - /*******************************************************************************/ - default: - return ERR_INTERNAL; - } - - return isoDepTx( - pcb, - gIsoDep.ctrlBuf, - &gIsoDep.ctrlBuf[RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN], - infLen, - fwtTemp); -} - -#if RFAL_FEATURE_ISO_DEP_LISTEN -/*******************************************************************************/ -static ReturnCode isoDepReSendControlMsg(void) { - if(isoDep_PCBisRACK(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM); - } - - if(isoDep_PCBisRNAK(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_R_NAK, RFAL_ISODEP_NO_PARAM); - } - - if(isoDep_PCBisSDeselect(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM); - } - - if(isoDep_PCBisSWTX(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_S_WTX, gIsoDep.lastWTXM); - } - return ERR_WRONG_STATE; -} -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -void rfalIsoDepInitialize(void) { - gIsoDep.state = ISODEP_ST_IDLE; - gIsoDep.role = ISODEP_ROLE_PCD; - gIsoDep.did = RFAL_ISODEP_NO_DID; - gIsoDep.nad = RFAL_ISODEP_NO_NAD; - gIsoDep.blockNumber = 0; - gIsoDep.isTxChaining = false; - gIsoDep.isRxChaining = false; - gIsoDep.lastDID00 = false; - gIsoDep.lastPCB = ISODEP_PCB_INVALID; - gIsoDep.fsx = (uint16_t)RFAL_ISODEP_FSX_16; - gIsoDep.ourFsx = (uint16_t)RFAL_ISODEP_FSX_16; - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - - gIsoDep.rxLen = NULL; - gIsoDep.rxBuf = NULL; - gIsoDep.rxBufInfPos = 0U; - gIsoDep.txBufInfPos = 0U; - - gIsoDep.isTxPending = false; - gIsoDep.isWait4WTX = false; - - gIsoDep.compMode = RFAL_COMPLIANCE_MODE_NFC; - gIsoDep.maxRetriesR = RFAL_ISODEP_MAX_R_RETRYS; - gIsoDep.maxRetriesI = RFAL_ISODEP_MAX_I_RETRYS; - gIsoDep.maxRetriesSDSL = RFAL_ISODEP_MAX_DSL_RETRYS; - gIsoDep.maxRetriesSWTX = RFAL_ISODEP_MAX_WTX_RETRYS; - gIsoDep.maxRetriesSnWTX = RFAL_ISODEP_MAX_WTX_NACK_RETRYS; - gIsoDep.maxRetriesRATS = RFAL_ISODEP_RATS_RETRIES; - - gIsoDep.APDURxPos = 0; - gIsoDep.APDUTxPos = 0; - gIsoDep.APDUParam.rxLen = NULL; - gIsoDep.APDUParam.rxBuf = NULL; - gIsoDep.APDUParam.txBuf = NULL; - - isoDepClearCounters(); - - /* Destroy any ongoing WTX timer */ - isoDepTimerDestroy(gIsoDep.WTXTimer); - gIsoDep.WTXTimer = 0U; -} - -/*******************************************************************************/ -void rfalIsoDepInitializeWithParams( - rfalComplianceMode compMode, - uint8_t maxRetriesR, - uint8_t maxRetriesSnWTX, - uint8_t maxRetriesSWTX, - uint8_t maxRetriesSDSL, - uint8_t maxRetriesI, - uint8_t maxRetriesRATS) { - rfalIsoDepInitialize(); - - gIsoDep.compMode = compMode; - gIsoDep.maxRetriesR = maxRetriesR; - gIsoDep.maxRetriesSnWTX = maxRetriesSnWTX; - gIsoDep.maxRetriesSWTX = maxRetriesSWTX; - gIsoDep.maxRetriesSDSL = maxRetriesSDSL; - gIsoDep.maxRetriesI = maxRetriesI; - gIsoDep.maxRetriesRATS = maxRetriesRATS; -} - -#if RFAL_FEATURE_ISO_DEP_POLL -/*******************************************************************************/ -static ReturnCode isoDepDataExchangePCD(uint16_t* outActRxLen, bool* outIsChaining) { - ReturnCode ret; - uint8_t rxPCB; - - /* Check out parameters */ - if((outActRxLen == NULL) || (outIsChaining == NULL)) { - return ERR_PARAM; - } - - *outIsChaining = false; - - /* Calculate header required and check if the buffers InfPositions are suitable */ - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - if(gIsoDep.did != RFAL_ISODEP_NO_DID) { - gIsoDep.hdrLen += RFAL_ISODEP_DID_LEN; - } - if(gIsoDep.nad != RFAL_ISODEP_NO_NAD) { - gIsoDep.hdrLen += RFAL_ISODEP_NAD_LEN; - } - - /* Check if there is enough space before the infPos to append ISO-DEP headers on rx and tx */ - if((gIsoDep.rxBufInfPos < gIsoDep.hdrLen) || (gIsoDep.txBufInfPos < gIsoDep.hdrLen)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - switch(gIsoDep.state) { - /*******************************************************************************/ - case ISODEP_ST_IDLE: - return ERR_NONE; - - /*******************************************************************************/ - case ISODEP_ST_PCD_TX: - ret = isoDepTx( - isoDep_PCBIBlock(gIsoDep.blockNumber), - gIsoDep.txBuf, - &gIsoDep.txBuf[gIsoDep.txBufInfPos], - gIsoDep.txBufLen, - (gIsoDep.fwt + gIsoDep.dFwt)); - switch(ret) { - case ERR_NONE: - gIsoDep.state = ISODEP_ST_PCD_RX; - break; - - default: - return ret; - } - /* fall through */ - - /*******************************************************************************/ - case ISODEP_ST_PCD_WAIT_DSL: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - case ISODEP_ST_PCD_RX: - - ret = rfalGetTransceiveStatus(); - switch(ret) { - /* Data rcvd with error or timeout -> Send R-NAK */ - case ERR_TIMEOUT: - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: /* added to handle test cases scenario TC_POL_NFCB_T4AT_BI_82_x_y & TC_POL_NFCB_T4BT_BI_82_x_y */ - case ERR_INCOMPLETE_BYTE: /* added to handle test cases scenario TC_POL_NFCB_T4AT_BI_82_x_y & TC_POL_NFCB_T4BT_BI_82_x_y */ - - if(gIsoDep - .isRxChaining) { /* Rule 5 - In PICC chaining when a invalid/timeout occurs -> R-ACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - } else if( - gIsoDep.state == - ISODEP_ST_PCD_WAIT_DSL) { /* Rule 8 - If s-Deselect response fails MAY retransmit */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM)); - } else { /* Rule 4 - When a invalid block or timeout occurs -> R-NACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_NAK, RFAL_ISODEP_NO_PARAM)); - } - return ERR_BUSY; - - case ERR_NONE: - break; - - case ERR_BUSY: - return ERR_BUSY; /* Debug purposes */ - - default: - return ret; - } - - /*******************************************************************************/ - /* No error, process incoming msg */ - /*******************************************************************************/ - - (*outActRxLen) = rfalConvBitsToBytes(*outActRxLen); - - /* Check rcvd msg length, cannot be less then the expected header */ - if(((*outActRxLen) < gIsoDep.hdrLen) || ((*outActRxLen) >= gIsoDep.ourFsx)) { - return ERR_PROTO; - } - - /* Grab rcvd PCB */ - rxPCB = gIsoDep.rxBuf[ISODEP_PCB_POS]; - - /* EMVCo doesn't allow usage of for CID or NAD EMVCo 2.6 TAble 10.2 */ - if((gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) && - (isoDep_PCBhasDID(rxPCB) || isoDep_PCBhasNAD(rxPCB))) { - return ERR_PROTO; - } - - /* If we are expecting DID, check if PCB signals its presence and if device ID match*/ - if((gIsoDep.did != RFAL_ISODEP_NO_DID) && - (!isoDep_PCBhasDID(rxPCB) || (gIsoDep.did != gIsoDep.rxBuf[ISODEP_DID_POS]))) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Process S-Block */ - /*******************************************************************************/ - if(isoDep_PCBisSBlock(rxPCB)) { - /* Check if is a Wait Time eXtension */ - if(isoDep_PCBisSWTX(rxPCB)) { - /* Check if PICC has requested S(WTX) as response to R(NAK) EMVCo 3.0 10.3.5.5 / Digital 2.0 16.2.6.5 */ - if(isoDep_PCBisRNAK(gIsoDep.lastPCB)) { - gIsoDep.cntSWtxNack++; /* Count S(WTX) upon R(NAK) */ - gIsoDep.cntRRetrys = 0; /* Reset R-Block counter has PICC has responded */ - } else { - gIsoDep.cntSWtxNack = 0; /* Reset R(NACK)->S(WTX) counter */ - } - - /* Rule 3 - respond to S-block: get 1st INF byte S(STW): Power + WTXM */ - EXIT_ON_ERR( - ret, - isoDepHandleControlMsg( - ISODEP_S_WTX, isoDep_GetWTXM(gIsoDep.rxBuf[gIsoDep.hdrLen]))); - return ERR_BUSY; - } - - /* Check if is a deselect response */ - if(isoDep_PCBisSDeselect(rxPCB)) { - if(gIsoDep.state == ISODEP_ST_PCD_WAIT_DSL) { - rfalIsoDepInitialize(); /* Session finished reInit vars */ - return ERR_NONE; - } - - /* Deselect response not expected */ - /* fall through to PROTO error */ - } - /* Unexpected S-Block */ - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Process R-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisRBlock(rxPCB)) { - if(isoDep_PCBisRACK(rxPCB)) /* Check if is a R-ACK */ - { - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) /* Expected block number */ - { - /* Rule B - ACK with expected bn -> Increment block number */ - gIsoDep.blockNumber = isoDep_PCBNextBN(gIsoDep.blockNumber); - - /* R-ACK only allowed when PCD chaining */ - if(!gIsoDep.isTxChaining) { - return ERR_PROTO; - } - - /* Rule 7 - Chaining transaction done, continue chaining */ - isoDepClearCounters(); - return ERR_NONE; /* This block has been transmitted */ - } else { - /* Rule 6 - R-ACK with wrong block number retransmit */ - /* Digital 2.0 16.2.5.4 - Retransmit maximum two times */ - /* EMVCo 3.0 10.3.4.3 - PCD may re-transmit the last I-Block or report error */ - if(gIsoDep.cntIRetrys++ < gIsoDep.maxRetriesI) { - gIsoDep.cntRRetrys = 0; /* Clear R counter only */ - gIsoDep.state = ISODEP_ST_PCD_TX; - return ERR_BUSY; - } - return ERR_TIMEOUT; /* NFC Forum mandates timeout or transmission error depending on previous errors */ - } - } else /* Unexpected R-Block */ - { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Process I-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisIBlock(rxPCB)) { - /*******************************************************************************/ - /* is PICC performing chaining */ - if(isoDep_PCBisChaining(rxPCB)) { - gIsoDep.isRxChaining = true; - *outIsChaining = true; - - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) { - /* Rule B - ACK with correct block number -> Increase Block number */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - isoDepClearCounters(); /* Clear counters in case R counter is already at max */ - - /* Rule 2 - Send ACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - - /* Received I-Block with chaining, send current data to DH */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *outActRxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *outActRxLen); - } - - isoDepClearCounters(); - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived */ - } else { - /* Rule 5 - PICC chaining invalid I-Block -> R-ACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - } - return ERR_BUSY; - } - - gIsoDep.isRxChaining = false; /* clear PICC chaining flag */ - - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) { - /* Rule B - I-Block with correct block number -> Increase Block number */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /* I-Block transaction done successfully */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *outActRxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *outActRxLen); - } - - gIsoDep.state = ISODEP_ST_IDLE; - isoDepClearCounters(); - return ERR_NONE; - } else { - if((gIsoDep.compMode != RFAL_COMPLIANCE_MODE_ISO)) { - /* Invalid Block (not chaining) -> Raise error Digital 1.1 15.2.6.4 EMVCo 2.6 10.3.5.4 */ - return ERR_PROTO; - } - - /* Rule 4 - Invalid Block -> R-NAK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_NAK, RFAL_ISODEP_NO_PARAM)); - return ERR_BUSY; - } - } else /* not S/R/I - Block */ - { - return ERR_PROTO; - } - /* fall through */ - - /*******************************************************************************/ - default: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - /* MISRA 16.4: no empty default (comment will suffice) */ - break; - } - - return ERR_INTERNAL; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepDeselect(void) { - ReturnCode ret; - uint32_t cntRerun; - bool dummyB; - - /*******************************************************************************/ - /* Using local static vars and static config to cope with a Deselect after * - * RATS\ATTRIB without any I-Block exchanged */ - gIsoDep.rxLen = &gIsoDep.ctrlRxLen; - gIsoDep.rxBuf = gIsoDep.ctrlBuf; - gIsoDep.rxBufLen = ISODEP_CONTROLMSG_BUF_LEN - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN); - gIsoDep.rxBufInfPos = (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN); - gIsoDep.txBufInfPos = (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN); - - /*******************************************************************************/ - /* The Deselect process is being done blocking, Digital 1.0 - 13.2.7.1 MUST wait response and retry*/ - /* Set the maximum reruns while we will wait for a response */ - cntRerun = ISODEP_MAX_RERUNS; - - /* Send DSL request and run protocol until get a response, error or "timeout" */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM)); - do { - ret = isoDepDataExchangePCD(gIsoDep.rxLen, &dummyB); - rfalWorker(); - } while(((cntRerun--) != 0U) && (ret == ERR_BUSY)); - - rfalIsoDepInitialize(); - return ((cntRerun == 0U) ? ERR_TIMEOUT : ret); -} - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - -/*******************************************************************************/ -uint32_t rfalIsoDepFWI2FWT(uint8_t fwi) { - uint32_t result; - uint8_t tmpFWI; - - tmpFWI = fwi; - - /* RFU values -> take the default value - * Digital 1.0 11.6.2.17 FWI[1,14] - * Digital 1.1 7.6.2.22 FWI[0,14] - * EMVCo 2.6 Table A.5 FWI[0,14] */ - if(tmpFWI > ISODEP_FWI_MAX) { - tmpFWI = RFAL_ISODEP_FWI_DEFAULT; - } - - /* FWT = (256 x 16/fC) x 2^FWI => 2^(FWI+12) Digital 1.1 13.8.1 & 7.9.1 */ - - result = ((uint32_t)1U << (tmpFWI + 12U)); - result = MIN(RFAL_ISODEP_MAX_FWT, result); /* Maximum Frame Waiting Time must be fulfilled */ - - return result; -} - -/*******************************************************************************/ -uint16_t rfalIsoDepFSxI2FSx(uint8_t FSxI) { - uint16_t fsx; - uint8_t fsi; - - /* Enforce maximum FSxI/FSx allowed - NFC Forum and EMVCo differ */ - fsi = - ((gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) ? MIN(FSxI, RFAL_ISODEP_FSDI_MAX_EMV) : - MIN(FSxI, RFAL_ISODEP_FSDI_MAX_NFC)); - - switch(fsi) { - case(uint8_t)RFAL_ISODEP_FSXI_16: - fsx = (uint16_t)RFAL_ISODEP_FSX_16; - break; - case(uint8_t)RFAL_ISODEP_FSXI_24: - fsx = (uint16_t)RFAL_ISODEP_FSX_24; - break; - case(uint8_t)RFAL_ISODEP_FSXI_32: - fsx = (uint16_t)RFAL_ISODEP_FSX_32; - break; - case(uint8_t)RFAL_ISODEP_FSXI_40: - fsx = (uint16_t)RFAL_ISODEP_FSX_40; - break; - case(uint8_t)RFAL_ISODEP_FSXI_48: - fsx = (uint16_t)RFAL_ISODEP_FSX_48; - break; - case(uint8_t)RFAL_ISODEP_FSXI_64: - fsx = (uint16_t)RFAL_ISODEP_FSX_64; - break; - case(uint8_t)RFAL_ISODEP_FSXI_96: - fsx = (uint16_t)RFAL_ISODEP_FSX_96; - break; - case(uint8_t)RFAL_ISODEP_FSXI_128: - fsx = (uint16_t)RFAL_ISODEP_FSX_128; - break; - case(uint8_t)RFAL_ISODEP_FSXI_256: - fsx = (uint16_t)RFAL_ISODEP_FSX_256; - break; - case(uint8_t)RFAL_ISODEP_FSXI_512: - fsx = (uint16_t)RFAL_ISODEP_FSX_512; - break; - case(uint8_t)RFAL_ISODEP_FSXI_1024: - fsx = (uint16_t)RFAL_ISODEP_FSX_1024; - break; - case(uint8_t)RFAL_ISODEP_FSXI_2048: - fsx = (uint16_t)RFAL_ISODEP_FSX_2048; - break; - case(uint8_t)RFAL_ISODEP_FSXI_4096: - fsx = (uint16_t)RFAL_ISODEP_FSX_4096; - break; - default: - fsx = (uint16_t)RFAL_ISODEP_FSX_256; - break; - } - return fsx; -} - -#if RFAL_FEATURE_ISO_DEP_LISTEN - -/*******************************************************************************/ -bool rfalIsoDepIsRats(const uint8_t* buf, uint8_t bufLen) { - if(buf != NULL) { - if((RFAL_ISODEP_CMD_RATS == (uint8_t)*buf) && (sizeof(rfalIsoDepRats) == bufLen)) { - return true; - } - } - return false; -} - -/*******************************************************************************/ -bool rfalIsoDepIsAttrib(const uint8_t* buf, uint8_t bufLen) { - if(buf != NULL) { - if((RFAL_ISODEP_CMD_ATTRIB == (uint8_t)*buf) && - (RFAL_ISODEP_ATTRIB_REQ_MIN_LEN <= bufLen) && - ((RFAL_ISODEP_ATTRIB_REQ_MIN_LEN + RFAL_ISODEP_ATTRIB_HLINFO_LEN) >= bufLen)) { - return true; - } - } - return false; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepListenStartActivation( - rfalIsoDepAtsParam* atsParam, - const rfalIsoDepAttribResParam* attribResParam, - const uint8_t* buf, - uint16_t bufLen, - rfalIsoDepListenActvParam actParam) { - uint8_t* txBuf; - uint8_t bufIt; - const uint8_t* buffer = buf; - - /*******************************************************************************/ - bufIt = 0; - txBuf = - (uint8_t*)actParam - .rxBuf; /* Use the rxBuf as TxBuf as well, the struct enforces a size enough MAX( NFCA_ATS_MAX_LEN, NFCB_ATTRIB_RES_MAX_LEN ) */ - gIsoDep.txBR = RFAL_BR_106; - gIsoDep.rxBR = RFAL_BR_106; - - /* Check for a valid buffer pointer */ - if(buffer == NULL) { - return ERR_PARAM; - } - - /*******************************************************************************/ - if(*buffer == RFAL_ISODEP_CMD_RATS) { - /* Check ATS parameters */ - if(atsParam == NULL) { - return ERR_PARAM; - } - - /* If requested copy RATS to device info */ - if(actParam.isoDepDev != NULL) { - ST_MEMCPY( - (uint8_t*)&actParam.isoDepDev->activation.A.Poller.RATS, - buffer, - sizeof(rfalIsoDepRats)); /* Copy RATS' CMD + PARAM */ - } - - /*******************************************************************************/ - /* Process RATS */ - buffer++; - gIsoDep.fsx = rfalIsoDepFSxI2FSx( - (((*buffer) & RFAL_ISODEP_RATS_PARAM_FSDI_MASK) >> RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT)); - gIsoDep.did = (*buffer & RFAL_ISODEP_DID_MASK); - - /*******************************************************************************/ - /* Digital 1.1 13.6.1.8 - DID as to between 0 and 14 */ - if(gIsoDep.did > RFAL_ISODEP_DID_MAX) { - return ERR_PROTO; - } - - /* Check if we are configured to support DID */ - if((gIsoDep.did != RFAL_ISODEP_DID_00) && (!atsParam->didSupport)) { - return ERR_NOTSUPP; - } - - /*******************************************************************************/ - /* Check RFAL supported bit rates */ - if((!(RFAL_SUPPORT_BR_CE_A_212) && - (((atsParam->ta & RFAL_ISODEP_ATS_TA_DPL_212) != 0U) || - ((atsParam->ta & RFAL_ISODEP_ATS_TA_DLP_212) != 0U))) || - (!(RFAL_SUPPORT_BR_CE_A_424) && - (((atsParam->ta & RFAL_ISODEP_ATS_TA_DPL_424) != 0U) || - ((atsParam->ta & RFAL_ISODEP_ATS_TA_DLP_424) != 0U))) || - (!(RFAL_SUPPORT_BR_CE_A_848) && - (((atsParam->ta & RFAL_ISODEP_ATS_TA_DPL_848) != 0U) || - ((atsParam->ta & RFAL_ISODEP_ATS_TA_DLP_848) != 0U)))) { - return ERR_NOTSUPP; - } - - /* Enforce proper FWI configuration */ - if(atsParam->fwi > ISODEP_FWI_LIS_MAX) { - atsParam->fwi = ISODEP_FWI_LIS_MAX; - } - - gIsoDep.atsTA = atsParam->ta; - gIsoDep.fwt = rfalIsoDepFWI2FWT(atsParam->fwi); - gIsoDep.ourFsx = rfalIsoDepFSxI2FSx(atsParam->fsci); - - /* Ensure proper/maximum Historical Bytes length */ - atsParam->hbLen = MIN(RFAL_ISODEP_ATS_HB_MAX_LEN, atsParam->hbLen); - - /*******************************************************************************/ - /* Compute ATS */ - - txBuf[bufIt++] = (RFAL_ISODEP_ATS_HIST_OFFSET + atsParam->hbLen); /* TL */ - txBuf[bufIt++] = - ((RFAL_ISODEP_ATS_T0_TA_PRESENCE_MASK | RFAL_ISODEP_ATS_T0_TB_PRESENCE_MASK | - RFAL_ISODEP_ATS_T0_TC_PRESENCE_MASK) | - atsParam->fsci); /* T0 */ - txBuf[bufIt++] = atsParam->ta; /* TA */ - txBuf[bufIt++] = - ((atsParam->fwi << RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT) | - (atsParam->sfgi & RFAL_ISODEP_RATS_PARAM_FSDI_MASK)); /* TB */ - txBuf[bufIt++] = (uint8_t)((atsParam->didSupport) ? RFAL_ISODEP_ATS_TC_DID : 0U); /* TC */ - - if(atsParam->hbLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(&txBuf[bufIt], atsParam->hb, atsParam->hbLen); /* T1-Tk */ - bufIt += atsParam->hbLen; - } - - gIsoDep.state = ISODEP_ST_PICC_ACT_ATS; - - } - /*******************************************************************************/ - else if(*buffer == RFAL_ISODEP_CMD_ATTRIB) { - /* Check ATTRIB parameters */ - if(attribResParam == NULL) { - return ERR_PARAM; - } - - /* REMARK: ATTRIB handling */ - NO_WARNING(attribResParam); - NO_WARNING(bufLen); - return ERR_NOT_IMPLEMENTED; - } else { - return ERR_PARAM; - } - - gIsoDep.actvParam = actParam; - - /*******************************************************************************/ - /* If requested copy to ISO-DEP device info */ - if(actParam.isoDepDev != NULL) { - actParam.isoDepDev->info.DID = gIsoDep.did; - actParam.isoDepDev->info.FSx = gIsoDep.fsx; - actParam.isoDepDev->info.FWT = gIsoDep.fwt; - actParam.isoDepDev->info.dFWT = 0; - actParam.isoDepDev->info.DSI = gIsoDep.txBR; - actParam.isoDepDev->info.DRI = gIsoDep.rxBR; - } - - return rfalTransceiveBlockingTx( - txBuf, - bufIt, - (uint8_t*)actParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - actParam.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FWT_NONE); -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepListenGetActivationStatus(void) { - ReturnCode err; - uint8_t* txBuf; - uint8_t bufIt; - - rfalBitRate dsi; - rfalBitRate dri; - - /* Check if Activation is running */ - if(gIsoDep.state < ISODEP_ST_PICC_ACT_ATS) { - return ERR_WRONG_STATE; - } - - /* Check if Activation has finished already */ - if(gIsoDep.state >= ISODEP_ST_PICC_RX) { - return ERR_NONE; - } - - /*******************************************************************************/ - /* Check for incoming msg */ - err = rfalGetTransceiveStatus(); - switch(err) { - /*******************************************************************************/ - case ERR_NONE: - break; - - /*******************************************************************************/ - case ERR_LINK_LOSS: - case ERR_BUSY: - return err; - - /*******************************************************************************/ - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - - /* ISO14443 4 5.6.2.2 2 If ATS has been replied upon a invalid block, PICC disables the PPS responses */ - if(gIsoDep.state == ISODEP_ST_PICC_ACT_ATS) { - gIsoDep.state = ISODEP_ST_PICC_RX; - break; - } - /* fall through */ - - /*******************************************************************************/ - default: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - /* ReEnable the receiver and wait for another frame */ - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - - return ERR_BUSY; - } - - txBuf = - (uint8_t*)gIsoDep.actvParam - .rxBuf; /* Use the rxBuf as TxBuf as well, the struct enforces a size enough MAX(NFCA_PPS_RES_LEN, ISODEP_DSL_MAX_LEN) */ - dri = RFAL_BR_KEEP; /* The RFAL_BR_KEEP is used to check if PPS with BR change was requested */ - dsi = RFAL_BR_KEEP; /* MISRA 9.1 */ - bufIt = 0; - - /*******************************************************************************/ - gIsoDep.role = ISODEP_ROLE_PICC; - - /*******************************************************************************/ - if(gIsoDep.state == ISODEP_ST_PICC_ACT_ATS) { - /* Check for a PPS ISO 14443-4 5.3 */ - if((((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_STARTBYTE_POS] & - RFAL_ISODEP_PPS_MASK) == RFAL_ISODEP_PPS_SB) { - /* ISO 14443-4 5.3.1 Check if the we are the addressed DID/CID */ - /* ISO 14443-4 5.3.2 Check for a valid PPS0 */ - if(((((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_STARTBYTE_POS] & - RFAL_ISODEP_DID_MASK) != gIsoDep.did) || - ((((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS0_POS] & - RFAL_ISODEP_PPS0_VALID_MASK) != RFAL_ISODEP_PPS0_PPS1_NOT_PRESENT)) { - /* Invalid DID on PPS request or Invalid PPS0, reEnable the receiver and wait another frame */ - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - - return ERR_BUSY; - } - - /*******************************************************************************/ - /* Check PPS1 presence */ - if(((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS0_POS] == - RFAL_ISODEP_PPS0_PPS1_PRESENT) { - uint8_t newdri = ((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS1_POS] & - RFAL_ISODEP_PPS1_DxI_MASK; /* MISRA 10.8 */ - uint8_t newdsi = (((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS1_POS] >> - RFAL_ISODEP_PPS1_DSI_SHIFT) & - RFAL_ISODEP_PPS1_DxI_MASK; /* MISRA 10.8 */ - /* PRQA S 4342 2 # MISRA 10.5 - Layout of enum rfalBitRate and above masks guarantee no invalid enum values to be created */ - dri = (rfalBitRate)(newdri); - dsi = (rfalBitRate)(newdsi); - - if((!(RFAL_SUPPORT_BR_CE_A_106) && - ((dsi == RFAL_BR_106) || (dri == RFAL_BR_106))) || - (!(RFAL_SUPPORT_BR_CE_A_212) && - ((dsi == RFAL_BR_212) || (dri == RFAL_BR_212))) || - (!(RFAL_SUPPORT_BR_CE_A_424) && - ((dsi == RFAL_BR_424) || (dri == RFAL_BR_424))) || - (!(RFAL_SUPPORT_BR_CE_A_848) && - ((dsi == RFAL_BR_848) || (dri == RFAL_BR_848)))) { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Compute and send PPS RES / Ack */ - txBuf[bufIt++] = ((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_STARTBYTE_POS]; - - rfalTransceiveBlockingTx( - txBuf, - bufIt, - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FWT_NONE); - - /*******************************************************************************/ - /* Exchange the bit rates if requested */ - if(dri != RFAL_BR_KEEP) { - rfalSetBitRate( - dsi, - dri); /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset above (RFAL_SUPPORT_BR_CE_A_xxx) */ - - gIsoDep.txBR = dsi; /* DSI codes the divisor from PICC to PCD */ - gIsoDep.rxBR = dri; /* DRI codes the divisor from PCD to PICC */ - - if(gIsoDep.actvParam.isoDepDev != NULL) { - gIsoDep.actvParam.isoDepDev->info.DSI = dsi; - gIsoDep.actvParam.isoDepDev->info.DRI = dri; - } - } - } - /* Check for a S-Deselect is done on Data Exchange Activity */ - } - - /*******************************************************************************/ - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - gIsoDep.hdrLen += - RFAL_ISODEP_DID_LEN; /* Always assume DID to be aligned with Digital 1.1 15.1.2 and ISO14443 4 5.6.3 #454 */ - gIsoDep.hdrLen += (uint8_t)((gIsoDep.nad != RFAL_ISODEP_NO_NAD) ? RFAL_ISODEP_NAD_LEN : 0U); - - /*******************************************************************************/ - /* Rule C - The PICC block number shall be initialized to 1 at activation */ - gIsoDep.blockNumber = 1; - - /* Activation done, keep the rcvd data in, reMap the activation buffer to the global to be retrieved by the DEP method */ - gIsoDep.rxBuf = (uint8_t*)gIsoDep.actvParam.rxBuf; - gIsoDep.rxBufLen = sizeof(rfalIsoDepBufFormat); - gIsoDep.rxBufInfPos = - (uint8_t)((uint32_t)gIsoDep.actvParam.rxBuf->inf - (uint32_t)gIsoDep.actvParam.rxBuf->prologue); - gIsoDep.rxLen = gIsoDep.actvParam.rxLen; - gIsoDep.rxChaining = gIsoDep.actvParam.isRxChaining; - - gIsoDep.state = ISODEP_ST_PICC_RX; - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -/*******************************************************************************/ -uint16_t rfalIsoDepGetMaxInfLen(void) { - /* Check whether all parameters are valid, otherwise return minimum default value */ - if((gIsoDep.fsx < (uint16_t)RFAL_ISODEP_FSX_16) || - (gIsoDep.fsx > (uint16_t)RFAL_ISODEP_FSX_4096) || (gIsoDep.hdrLen > ISODEP_HDR_MAX_LEN)) { - uint16_t isodepFsx16 = (uint16_t)RFAL_ISODEP_FSX_16; /* MISRA 10.1 */ - return (isodepFsx16 - RFAL_ISODEP_PCB_LEN - ISODEP_CRC_LEN); - } - - return (gIsoDep.fsx - gIsoDep.hdrLen - ISODEP_CRC_LEN); -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepStartTransceive(rfalIsoDepTxRxParam param) { - gIsoDep.txBuf = param.txBuf->prologue; - gIsoDep.txBufInfPos = (uint8_t)((uint32_t)param.txBuf->inf - (uint32_t)param.txBuf->prologue); - gIsoDep.txBufLen = param.txBufLen; - gIsoDep.isTxChaining = param.isTxChaining; - - gIsoDep.rxBuf = param.rxBuf->prologue; - gIsoDep.rxBufInfPos = (uint8_t)((uint32_t)param.rxBuf->inf - (uint32_t)param.rxBuf->prologue); - gIsoDep.rxBufLen = sizeof(rfalIsoDepBufFormat); - - gIsoDep.rxLen = param.rxLen; - gIsoDep.rxChaining = param.isRxChaining; - - gIsoDep.fwt = param.FWT; - gIsoDep.dFwt = param.dFWT; - gIsoDep.fsx = param.FSx; - gIsoDep.did = param.DID; - - /* Only change the FSx from activation if no to Keep */ - gIsoDep.ourFsx = ((param.ourFSx != RFAL_ISODEP_FSX_KEEP) ? param.ourFSx : gIsoDep.ourFsx); - - /* Clear inner control params for next dataExchange */ - gIsoDep.isRxChaining = false; - isoDepClearCounters(); - - if(gIsoDep.role == ISODEP_ROLE_PICC) { - if(gIsoDep.txBufLen > 0U) { - /* Ensure that an RTOX Ack is not being expected at moment */ - if(!gIsoDep.isWait4WTX) { - gIsoDep.state = ISODEP_ST_PICC_TX; - return ERR_NONE; - } else { - /* If RTOX Ack is expected, signal a pending Tx to be transmitted right after */ - gIsoDep.isTxPending = true; - } - } - - /* Digital 1.1 15.2.5.1 The first block SHALL be sent by the Reader/Writer */ - gIsoDep.state = ISODEP_ST_PICC_RX; - return ERR_NONE; - } - - gIsoDep.state = ISODEP_ST_PCD_TX; - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepGetTransceiveStatus(void) { - if(gIsoDep.role == ISODEP_ROLE_PICC) { -#if RFAL_FEATURE_ISO_DEP_LISTEN - return isoDepDataExchangePICC(); -#else - return ERR_NOTSUPP; -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - } else { -#if RFAL_FEATURE_ISO_DEP_POLL - return isoDepDataExchangePCD(gIsoDep.rxLen, gIsoDep.rxChaining); -#else - return ERR_NOTSUPP; -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - } -} - -#if RFAL_FEATURE_ISO_DEP_LISTEN - -/*******************************************************************************/ -static ReturnCode isoDepDataExchangePICC(void) { - uint8_t rxPCB; - ReturnCode ret; - - switch(gIsoDep.state) { - /*******************************************************************************/ - case ISODEP_ST_IDLE: - return ERR_NONE; - - /*******************************************************************************/ - case ISODEP_ST_PICC_TX: - - ret = isoDepTx( - isoDep_PCBIBlock(gIsoDep.blockNumber), - gIsoDep.txBuf, - &gIsoDep.txBuf[gIsoDep.txBufInfPos], - gIsoDep.txBufLen, - RFAL_FWT_NONE); - - /* Clear pending Tx flag */ - gIsoDep.isTxPending = false; - - switch(ret) { - case ERR_NONE: - gIsoDep.state = ISODEP_ST_PICC_RX; - return ERR_BUSY; - - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - return ret; - - /*******************************************************************************/ - case ISODEP_ST_PICC_RX: - - ret = rfalGetTransceiveStatus(); - switch(ret) { - /*******************************************************************************/ - /* Data rcvd with error or timeout -> mute */ - case ERR_TIMEOUT: - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - - /* Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ - isoDepReEnableRx((uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - - return ERR_BUSY; - - /*******************************************************************************/ - case ERR_LINK_LOSS: - return ret; /* Debug purposes */ - - case ERR_BUSY: - return ret; /* Debug purposes */ - - /*******************************************************************************/ - case ERR_NONE: - *gIsoDep.rxLen = rfalConvBitsToBytes(*gIsoDep.rxLen); - break; - - /*******************************************************************************/ - default: - return ret; - } - break; - - /*******************************************************************************/ - case ISODEP_ST_PICC_SWTX: - - if(!isoDepTimerisExpired(gIsoDep.WTXTimer)) /* Do nothing until WTX timer has expired */ - { - return ERR_BUSY; - } - - /* Set waiting for WTX Ack Flag */ - gIsoDep.isWait4WTX = true; - - /* Digital 1.1 15.2.2.9 - Calculate the WTXM such that FWTtemp <= FWTmax */ - gIsoDep.lastWTXM = (uint8_t)isoDep_WTXMListenerMax(gIsoDep.fwt); - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_WTX, gIsoDep.lastWTXM)); - - gIsoDep.state = ISODEP_ST_PICC_RX; /* Go back to Rx to process WTX ack */ - return ERR_BUSY; - - /*******************************************************************************/ - case ISODEP_ST_PICC_SDSL: - - if(rfalIsTransceiveInRx()) /* Wait until DSL response has been sent */ - { - rfalIsoDepInitialize(); /* Session finished reInit vars */ - return ERR_SLEEP_REQ; /* Notify Deselect request */ - } - return ERR_BUSY; - - /*******************************************************************************/ - default: - return ERR_INTERNAL; - } - - /* ISO 14443-4 7.5.6.2 CE SHALL NOT attempt error recovery -> clear counters */ - isoDepClearCounters(); - - /*******************************************************************************/ - /* No error, process incoming msg */ - /*******************************************************************************/ - - /* Grab rcvd PCB */ - rxPCB = gIsoDep.rxBuf[ISODEP_PCB_POS]; - - /*******************************************************************************/ - /* When DID=0 PCD may or may not use DID, therefore check whether current PCD request - * has DID present to be reflected on max INF length #454 */ - - /* ReCalculate Header Length */ - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - gIsoDep.hdrLen += (uint8_t)((isoDep_PCBhasDID(rxPCB)) ? RFAL_ISODEP_DID_LEN : 0U); - gIsoDep.hdrLen += (uint8_t)((isoDep_PCBhasNAD(rxPCB)) ? RFAL_ISODEP_NAD_LEN : 0U); - - /* Store whether last PCD block had DID. for PICC special handling of DID = 0 */ - if(gIsoDep.did == RFAL_ISODEP_DID_00) { - gIsoDep.lastDID00 = ((isoDep_PCBhasDID(rxPCB)) ? true : false); - } - - /*******************************************************************************/ - /* Check rcvd msg length, cannot be less then the expected header OR * - * if the rcvd msg exceeds our announced frame size (FSD) */ - if(((*gIsoDep.rxLen) < gIsoDep.hdrLen) || - ((*gIsoDep.rxLen) > (gIsoDep.ourFsx - ISODEP_CRC_LEN))) { - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - return ERR_BUSY; /* ERR_PROTO Ignore this protocol request */ - } - - /* If we are expecting DID, check if PCB signals its presence and if device ID match OR - * If our DID=0 and DID is sent but with an incorrect value */ - if(((gIsoDep.did != RFAL_ISODEP_DID_00) && - (!isoDep_PCBhasDID(rxPCB) || (gIsoDep.did != gIsoDep.rxBuf[ISODEP_DID_POS]))) || - ((gIsoDep.did == RFAL_ISODEP_DID_00) && isoDep_PCBhasDID(rxPCB) && - (RFAL_ISODEP_DID_00 != gIsoDep.rxBuf[ISODEP_DID_POS]))) { - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - return ERR_BUSY; /* Ignore a wrong DID request */ - } - - /* If we aren't expecting NAD and it's received */ - if((gIsoDep.nad == RFAL_ISODEP_NO_NAD) && isoDep_PCBhasNAD(rxPCB)) { - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - return ERR_BUSY; /* Ignore a unexpected NAD request */ - } - - /*******************************************************************************/ - /* Process S-Block */ - /*******************************************************************************/ - if(isoDep_PCBisSBlock(rxPCB)) { - /* Check if is a Wait Time eXtension */ - if(isoDep_PCBisSWTX(rxPCB)) { - /* Check if we're expecting a S-WTX */ - if(isoDep_PCBisWTX(gIsoDep.lastPCB)) { - /* Digital 1.1 15.2.2.11 S(WTX) Ack with different WTXM -> Protocol Error * - * Power level indication also should be set to 0 */ - if((gIsoDep.rxBuf[gIsoDep.hdrLen] == gIsoDep.lastWTXM) && - ((*gIsoDep.rxLen - gIsoDep.hdrLen) == ISODEP_SWTX_INF_LEN)) { - /* Clear waiting for RTOX Ack Flag */ - gIsoDep.isWait4WTX = false; - - /* Check if a Tx is already pending */ - if(gIsoDep.isTxPending) { - /* Has a pending Tx, go immediately to TX */ - gIsoDep.state = ISODEP_ST_PICC_TX; - return ERR_BUSY; - } - - /* Set WTX timer */ - isoDepTimerStart( - gIsoDep.WTXTimer, - isoDep_WTXAdjust((gIsoDep.lastWTXM * rfalConv1fcToMs(gIsoDep.fwt)))); - - gIsoDep.state = ISODEP_ST_PICC_SWTX; - return ERR_BUSY; - } - } - /* Unexpected/Incorrect S-WTX, fall into reRenable */ - } - - /* Check if is a Deselect request */ - if(isoDep_PCBisSDeselect(rxPCB) && - ((*gIsoDep.rxLen - gIsoDep.hdrLen) == ISODEP_SDSL_INF_LEN)) { - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM)); - - /* S-DSL transmission ongoing, wait until complete */ - gIsoDep.state = ISODEP_ST_PICC_SDSL; - return ERR_BUSY; - } - - /* Unexpected S-Block, fall into reRenable */ - } - - /*******************************************************************************/ - /* Process R-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisRBlock(rxPCB) && ((*gIsoDep.rxLen - gIsoDep.hdrLen) == ISODEP_RBLOCK_INF_LEN)) { - if(isoDep_PCBisRACK(rxPCB)) /* Check if is a R-ACK */ - { - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) /* Check block number */ - { - /* Rule 11 - R(ACK) with current bn -> re-transmit */ - if(!isoDep_PCBisIBlock(gIsoDep.lastPCB)) { - isoDepReSendControlMsg(); - } else { - gIsoDep.state = ISODEP_ST_PICC_TX; - } - - return ERR_BUSY; - } else { - if(!gIsoDep.isTxChaining) { - /* Rule 13 violation R(ACK) without performing chaining */ - isoDepReEnableRx( - (uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - return ERR_BUSY; - } - - /* Rule E - R(ACK) with not current bn -> toggle bn */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /* This block has been transmitted and acknowledged, perform WTX until next data is provided */ - - /* Rule 9 - PICC is allowed to send an S(WTX) instead of an I-block or an R(ACK) */ - isoDepTimerStart(gIsoDep.WTXTimer, isoDep_WTXAdjust(rfalConv1fcToMs(gIsoDep.fwt))); - gIsoDep.state = ISODEP_ST_PICC_SWTX; - - /* Rule 13 - R(ACK) with not current bn -> continue chaining */ - return ERR_NONE; /* This block has been transmitted */ - } - } else if(isoDep_PCBisRNAK(rxPCB)) /* Check if is a R-NACK */ - { - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) /* Check block number */ - { - /* Rule 11 - R(NAK) with current bn -> re-transmit last x-Block */ - if(!isoDep_PCBisIBlock(gIsoDep.lastPCB)) { - isoDepReSendControlMsg(); - } else { - gIsoDep.state = ISODEP_ST_PICC_TX; - } - - return ERR_BUSY; - } else { - /* Rule 12 - R(NAK) with not current bn -> R(ACK) */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - - return ERR_BUSY; - } - } else { - /* MISRA 15.7 - Empty else */ - } - - /* Unexpected R-Block, fall into reRenable */ - } - - /*******************************************************************************/ - /* Process I-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisIBlock(rxPCB)) { - /* Rule D - When an I-block is received, the PICC shall toggle its block number before sending a block */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /*******************************************************************************/ - /* Check if the block number is the one expected */ - /* Check if PCD sent an I-Block instead ACK/NACK when we are chaining */ - if((isoDep_GetBN(rxPCB) != gIsoDep.blockNumber) || (gIsoDep.isTxChaining)) { - /* Remain in the same Block Number */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /* ISO 14443-4 7.5.6.2 & Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ - isoDepReEnableRx((uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - return ERR_BUSY; - } - - /*******************************************************************************/ - /* is PCD performing chaining ? */ - if(isoDep_PCBisChaining(rxPCB)) { - gIsoDep.isRxChaining = true; - *gIsoDep.rxChaining = true; /* Output Parameter*/ - - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - - /* Received I-Block with chaining, send current data to DH */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *gIsoDep.rxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*gIsoDep.rxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *gIsoDep.rxLen); - } - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived*/ - } - - /*******************************************************************************/ - /* PCD is not performing chaining */ - gIsoDep.isRxChaining = false; /* clear PCD chaining flag */ - *gIsoDep.rxChaining = false; /* Output Parameter */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *gIsoDep.rxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*gIsoDep.rxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *gIsoDep.rxLen); - } - - /*******************************************************************************/ - /* Reception done, send data back and start WTX timer */ - isoDepTimerStart(gIsoDep.WTXTimer, isoDep_WTXAdjust(rfalConv1fcToMs(gIsoDep.fwt))); - - gIsoDep.state = ISODEP_ST_PICC_SWTX; - return ERR_NONE; - } else { - /* MISRA 15.7 - Empty else */ - } - - /* Unexpected/Unknown Block */ - /* ISO 14443-4 7.5.6.2 & Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ - isoDepReEnableRx((uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - - return ERR_BUSY; -} -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -#if RFAL_FEATURE_ISO_DEP_POLL - -#if RFAL_FEATURE_NFCA - -/*******************************************************************************/ -static ReturnCode - rfalIsoDepStartRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen) { - rfalTransceiveContext ctx; - - if(ats == NULL) { - return ERR_PARAM; - } - - gIsoDep.rxBuf = (uint8_t*)ats; - gIsoDep.rxLen8 = atsLen; - gIsoDep.did = DID; - - /*******************************************************************************/ - /* Compose RATS */ - gIsoDep.actv.ratsReq.CMD = RFAL_ISODEP_CMD_RATS; - gIsoDep.actv.ratsReq.PARAM = - (((uint8_t)FSDI << RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT) & RFAL_ISODEP_RATS_PARAM_FSDI_MASK) | - (DID & RFAL_ISODEP_RATS_PARAM_DID_MASK); - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)&gIsoDep.actv.ratsReq, - sizeof(rfalIsoDepRats), - (uint8_t*)ats, - sizeof(rfalIsoDepAts), - &gIsoDep.rxBufLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ISODEP_T4T_FWT_ACTIVATION); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode rfalIsoDepGetRATSStatus(void) { - ReturnCode ret; - - ret = rfalGetTransceiveStatus(); - if(ret == ERR_NONE) { - gIsoDep.rxBufLen = rfalConvBitsToBytes(gIsoDep.rxBufLen); - - /* Check for valid ATS length Digital 1.1 13.6.2.1 & 13.6.2.3 */ - if((gIsoDep.rxBufLen < RFAL_ISODEP_ATS_MIN_LEN) || - (gIsoDep.rxBufLen > RFAL_ISODEP_ATS_MAX_LEN) || - (gIsoDep.rxBuf[RFAL_ISODEP_ATS_TL_POS] != gIsoDep.rxBufLen)) { - return ERR_PROTO; - } - - /* Assign our FSx, in case the a Deselect is send without Transceive */ - gIsoDep.ourFsx = rfalIsoDepFSxI2FSx( - (uint8_t)(gIsoDep.actv.ratsReq.PARAM >> RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT)); - - /* Check and assign if ATS length was requested (length also available on TL) */ - if(gIsoDep.rxLen8 != NULL) { - *gIsoDep.rxLen8 = (uint8_t)gIsoDep.rxBufLen; - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalIsoDepStartRATS(FSDI, DID, ats, atsLen)); - rfalIsoDepRunBlocking(ret, rfalIsoDepGetRATSStatus()); - - return ret; -} - -/*******************************************************************************/ -static ReturnCode - rfalIsoDepStartPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes) { - rfalTransceiveContext ctx; - - if((ppsRes == NULL) || (DSI > RFAL_BR_848) || (DRI > RFAL_BR_848) || - (DID > RFAL_ISODEP_DID_MAX)) { - return ERR_PARAM; - } - - gIsoDep.rxBuf = (uint8_t*)ppsRes; - - /*******************************************************************************/ - /* Compose PPS Request */ - gIsoDep.actv.ppsReq.PPSS = (RFAL_ISODEP_PPS_SB | (DID & RFAL_ISODEP_PPS_SB_DID_MASK)); - gIsoDep.actv.ppsReq.PPS0 = RFAL_ISODEP_PPS_PPS0_PPS1_PRESENT; - gIsoDep.actv.ppsReq.PPS1 = - (RFAL_ISODEP_PPS_PPS1 | - ((((uint8_t)DSI << RFAL_ISODEP_PPS_PPS1_DSI_SHIFT) | (uint8_t)DRI) & - RFAL_ISODEP_PPS_PPS1_DXI_MASK)); - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)&gIsoDep.actv.ppsReq, - sizeof(rfalIsoDepPpsReq), - (uint8_t*)ppsRes, - sizeof(rfalIsoDepPpsRes), - &gIsoDep.rxBufLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ISODEP_T4T_FWT_ACTIVATION); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode rfalIsoDepGetPPSSTatus(void) { - ReturnCode ret; - - ret = rfalGetTransceiveStatus(); - if(ret == ERR_NONE) { - gIsoDep.rxBufLen = rfalConvBitsToBytes(gIsoDep.rxBufLen); - - /* Check for valid PPS Response */ - if((gIsoDep.rxBufLen != RFAL_ISODEP_PPS_RES_LEN) || - (*gIsoDep.rxBuf != gIsoDep.actv.ppsReq.PPSS)) { - return ERR_PROTO; - } - } - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalIsoDepStartPPS(DID, DSI, DRI, ppsRes)); - rfalIsoDepRunBlocking(ret, rfalIsoDepGetPPSSTatus()); - - return ret; -} - -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB - -static ReturnCode rfalIsoDepStartATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen) { - rfalTransceiveContext ctx; - - if((attribRes == NULL) || (attribResLen == NULL) || (DSI > RFAL_BR_848) || - (DRI > RFAL_BR_848) || (DID > RFAL_ISODEP_DID_MAX)) { - return ERR_NONE; - } - - gIsoDep.rxBuf = (uint8_t*)attribRes; - gIsoDep.rxLen8 = attribResLen; - gIsoDep.did = DID; - - /*******************************************************************************/ - /* Compose ATTRIB command */ - gIsoDep.actv.attribReq.cmd = RFAL_ISODEP_CMD_ATTRIB; - gIsoDep.actv.attribReq.Param.PARAM1 = PARAM1; - gIsoDep.actv.attribReq.Param.PARAM2 = - (((((uint8_t)DSI << RFAL_ISODEP_ATTRIB_PARAM2_DSI_SHIFT) | - ((uint8_t)DRI << RFAL_ISODEP_ATTRIB_PARAM2_DRI_SHIFT)) & - RFAL_ISODEP_ATTRIB_PARAM2_DXI_MASK) | - ((uint8_t)FSDI & RFAL_ISODEP_ATTRIB_PARAM2_FSDI_MASK)); - gIsoDep.actv.attribReq.Param.PARAM3 = PARAM3; - gIsoDep.actv.attribReq.Param.PARAM4 = (DID & RFAL_ISODEP_ATTRIB_PARAM4_DID_MASK); - ST_MEMCPY(gIsoDep.actv.attribReq.nfcid0, nfcid0, RFAL_NFCB_NFCID0_LEN); - - /* Append the Higher layer Info if provided */ - if((HLInfo != NULL) && (HLInfoLen > 0U)) { - ST_MEMCPY( - gIsoDep.actv.attribReq.HLInfo, HLInfo, MIN(HLInfoLen, RFAL_ISODEP_ATTRIB_HLINFO_LEN)); - } - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)&gIsoDep.actv.attribReq, - (uint16_t)(RFAL_ISODEP_ATTRIB_HDR_LEN + MIN((uint16_t)HLInfoLen, RFAL_ISODEP_ATTRIB_HLINFO_LEN)), - (uint8_t*)gIsoDep.rxBuf, - sizeof(rfalIsoDepAttribRes), - &gIsoDep.rxBufLen, - RFAL_TXRX_FLAGS_DEFAULT, - fwt); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode rfalIsoDepGetATTRIBStatus(void) { - ReturnCode ret; - - ret = rfalGetTransceiveStatus(); - if(ret == ERR_NONE) { - gIsoDep.rxBufLen = rfalConvBitsToBytes(gIsoDep.rxBufLen); - - /* Check a for valid ATTRIB Response Digital 1.1 15.6.2.1 */ - if((gIsoDep.rxBufLen < RFAL_ISODEP_ATTRIB_RES_HDR_LEN) || - ((gIsoDep.rxBuf[RFAL_ISODEP_ATTRIB_RES_MBLIDID_POS] & - RFAL_ISODEP_ATTRIB_RES_DID_MASK) != gIsoDep.did)) { - return ERR_PROTO; - } - - if(gIsoDep.rxLen8 != NULL) { - *gIsoDep.rxLen8 = (uint8_t)gIsoDep.rxBufLen; - } - - gIsoDep.ourFsx = rfalIsoDepFSxI2FSx( - (uint8_t)(gIsoDep.actv.attribReq.Param.PARAM2 & RFAL_ISODEP_ATTRIB_PARAM2_FSDI_MASK)); - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalIsoDepStartATTRIB( - nfcid0, - PARAM1, - DSI, - DRI, - FSDI, - PARAM3, - DID, - HLInfo, - HLInfoLen, - fwt, - attribRes, - attribResLen)); - rfalIsoDepRunBlocking(ret, rfalIsoDepGetATTRIBStatus()); - - return ret; -} - -#endif /* RFAL_FEATURE_NFCB */ - -#if RFAL_FEATURE_NFCA - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollAHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalIsoDepPollAStartActivation(FSDI, DID, maxBR, isoDepDev)); - rfalIsoDepRunBlocking(ret, rfalIsoDepPollAGetActivationStatus()); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollAStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - if(isoDepDev == NULL) { - return ERR_PARAM; - } - - /* Enable EMD handling according Digital 1.1 4.1.1.1 ; EMVCo 2.6 4.9.2 */ - rfalSetErrorHandling(RFAL_ERRORHANDLING_EMVCO); - - /* Start RATS Transceive */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartRATS( - FSDI, - DID, - &isoDepDev->activation.A.Listener.ATS, - &isoDepDev->activation.A.Listener.ATSLen)); - - isoDepDev->info.DSI = maxBR; - gIsoDep.actvDev = isoDepDev; - gIsoDep.cntRRetrys = gIsoDep.maxRetriesRATS; - gIsoDep.state = ISODEP_ST_PCD_ACT_RATS; - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollAGetActivationStatus(void) { - ReturnCode ret; - uint8_t msgIt; - rfalBitRate maxBR; - - switch(gIsoDep.state) { - /*******************************************************************************/ - case ISODEP_ST_PCD_ACT_RATS: - - ret = rfalIsoDepGetRATSStatus(); - if(ret != ERR_BUSY) { - if(ret != ERR_NONE) { - /* EMVCo 2.6 9.6.1.1 & 9.6.1.2 If a timeout error is detected retransmit, on transmission error abort */ - if((gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) && (ret != ERR_TIMEOUT)) { - break; - } - - if(gIsoDep.cntRRetrys != 0U) { - /* Ensure FDT before retransmission (reuse RFAL GT timer) */ - rfalSetGT(rfalGetFDTPoll()); - rfalFieldOnAndStartGT(); - - /* Send RATS retransmission */ /* PRQA S 4342 1 # MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed within 4bit range */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartRATS( - (rfalIsoDepFSxI)(uint8_t)(gIsoDep.actv.ratsReq.PARAM >> RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT), - gIsoDep.did, - &gIsoDep.actvDev->activation.A.Listener.ATS, - &gIsoDep.actvDev->activation.A.Listener.ATSLen)); - gIsoDep.cntRRetrys--; - ret = ERR_BUSY; - } - /* Switch between NFC Forum and ISO14443-4 behaviour #595 - * ISO14443-4 5.6.1 If RATS fails, a Deactivation sequence should be performed as defined on clause 8 - * Activity 1.1 9.6 Device Deactivation Activity is to be only performed when there's an active device */ - else if(gIsoDep.compMode == RFAL_COMPLIANCE_MODE_ISO) { - rfalIsoDepDeselect(); - } else { - /* MISRA 15.7 - Empty else */ - } - } else /* ATS received */ - { - maxBR = gIsoDep.actvDev->info.DSI; /* Retrieve requested max bitrate */ - - /*******************************************************************************/ - /* Process ATS Response */ - gIsoDep.actvDev->info.FWI = - RFAL_ISODEP_FWI_DEFAULT; /* Default value EMVCo 2.6 5.7.2.6 */ - gIsoDep.actvDev->info.SFGI = 0U; - gIsoDep.actvDev->info.MBL = 0U; - gIsoDep.actvDev->info.DSI = RFAL_BR_106; - gIsoDep.actvDev->info.DRI = RFAL_BR_106; - gIsoDep.actvDev->info.FSxI = (uint8_t) - RFAL_ISODEP_FSXI_32; /* FSC default value is 32 bytes ISO14443-A 5.2.3 */ - - /*******************************************************************************/ - /* Check for ATS optional fields */ - if(gIsoDep.actvDev->activation.A.Listener.ATS.TL > RFAL_ISODEP_ATS_MIN_LEN) { - msgIt = RFAL_ISODEP_ATS_MIN_LEN; - - /* Format byte T0 is optional, if present assign FSDI */ - gIsoDep.actvDev->info.FSxI = - (gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_FSCI_MASK); - - /* T0 has already been processed, always the same position */ - msgIt++; - - /* Check if TA is present */ - if((gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_TA_PRESENCE_MASK) != 0U) { - rfalIsoDepCalcBitRate( - maxBR, - ((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt++], - &gIsoDep.actvDev->info.DSI, - &gIsoDep.actvDev->info.DRI); - } - - /* Check if TB is present */ - if((gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_TB_PRESENCE_MASK) != 0U) { - gIsoDep.actvDev->info.SFGI = - ((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt++]; - gIsoDep.actvDev->info.FWI = - (uint8_t)((gIsoDep.actvDev->info.SFGI >> RFAL_ISODEP_ATS_TB_FWI_SHIFT) & RFAL_ISODEP_ATS_FWI_MASK); - gIsoDep.actvDev->info.SFGI &= RFAL_ISODEP_ATS_TB_SFGI_MASK; - } - - /* Check if TC is present */ - if((gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_TC_PRESENCE_MASK) != 0U) { - /* Check for Protocol features support */ - /* Advanced protocol features defined on Digital 1.0 Table 69, removed after */ - gIsoDep.actvDev->info.supAdFt = - (((((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt] & - RFAL_ISODEP_ATS_TC_ADV_FEAT) != 0U) ? - true : - false); - gIsoDep.actvDev->info.supDID = - (((((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt] & - RFAL_ISODEP_ATS_TC_DID) != 0U) ? - true : - false); - gIsoDep.actvDev->info.supNAD = - (((((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt++] & - RFAL_ISODEP_ATS_TC_NAD) != 0U) ? - true : - false); - } - } - - gIsoDep.actvDev->info.FSx = rfalIsoDepFSxI2FSx(gIsoDep.actvDev->info.FSxI); - gIsoDep.fsx = gIsoDep.actvDev->info.FSx; - - gIsoDep.actvDev->info.SFGT = - rfalIsoDepSFGI2SFGT((uint8_t)gIsoDep.actvDev->info.SFGI); - - /* Ensure SFGT before following frame (reuse RFAL GT timer) */ - rfalSetGT(rfalConvMsTo1fc(gIsoDep.actvDev->info.SFGT)); - rfalFieldOnAndStartGT(); - - gIsoDep.actvDev->info.FWT = rfalIsoDepFWI2FWT(gIsoDep.actvDev->info.FWI); - gIsoDep.actvDev->info.dFWT = RFAL_ISODEP_DFWT_20; - - gIsoDep.actvDev->info.DID = - ((gIsoDep.actvDev->info.supDID) ? gIsoDep.did : RFAL_ISODEP_NO_DID); - gIsoDep.actvDev->info.NAD = RFAL_ISODEP_NO_NAD; - - /*******************************************************************************/ - /* If higher bit rates are supported by both devices, send PPS */ - if((gIsoDep.actvDev->info.DSI != RFAL_BR_106) || - (gIsoDep.actvDev->info.DRI != RFAL_BR_106)) { - /* Send PPS */ /* PRQA S 0310 1 # MISRA 11.3 - Intentional safe cast to avoiding buffer duplication */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartPPS( - gIsoDep.actvDev->info.DID, - gIsoDep.actvDev->info.DSI, - gIsoDep.actvDev->info.DRI, - (rfalIsoDepPpsRes*)&gIsoDep.ctrlBuf)); - - gIsoDep.state = ISODEP_ST_PCD_ACT_PPS; - return ERR_BUSY; - } - - return ERR_NONE; - } - } - break; - - /*******************************************************************************/ - case ISODEP_ST_PCD_ACT_PPS: - ret = rfalIsoDepGetPPSSTatus(); - if(ret != ERR_BUSY) { - /* Check whether PPS has been acknowledge */ - if(ret == ERR_NONE) { - /* DSI code the divisor from PICC to PCD */ - /* DRI code the divisor from PCD to PICC */ - rfalSetBitRate(gIsoDep.actvDev->info.DRI, gIsoDep.actvDev->info.DSI); - } else { - /* If PPS has faled keep activation bit rate */ - gIsoDep.actvDev->info.DSI = RFAL_BR_106; - gIsoDep.actvDev->info.DRI = RFAL_BR_106; - } - } - break; - - /*******************************************************************************/ - default: - ret = ERR_WRONG_STATE; - break; - } - - return ret; -} -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollBHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalIsoDepPollBStartActivation( - FSDI, DID, maxBR, PARAM1, nfcbDev, HLInfo, HLInfoLen, isoDepDev)); - rfalIsoDepRunBlocking(ret, rfalIsoDepPollBGetActivationStatus()); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollBStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - /***************************************************************************/ - /* Initialize ISO-DEP Device with info from SENSB_RES */ - isoDepDev->info.FWI = - ((nfcbDev->sensbRes.protInfo.FwiAdcFo >> RFAL_NFCB_SENSB_RES_FWI_SHIFT) & - RFAL_NFCB_SENSB_RES_FWI_MASK); - isoDepDev->info.FWT = rfalIsoDepFWI2FWT(isoDepDev->info.FWI); - isoDepDev->info.dFWT = RFAL_NFCB_DFWT; - isoDepDev->info.SFGI = - (((uint32_t)nfcbDev->sensbRes.protInfo.SFGI >> RFAL_NFCB_SENSB_RES_SFGI_SHIFT) & - RFAL_NFCB_SENSB_RES_SFGI_MASK); - isoDepDev->info.SFGT = rfalIsoDepSFGI2SFGT((uint8_t)isoDepDev->info.SFGI); - isoDepDev->info.FSxI = - ((nfcbDev->sensbRes.protInfo.FsciProType >> RFAL_NFCB_SENSB_RES_FSCI_SHIFT) & - RFAL_NFCB_SENSB_RES_FSCI_MASK); - isoDepDev->info.FSx = rfalIsoDepFSxI2FSx(isoDepDev->info.FSxI); - isoDepDev->info.DID = DID; - isoDepDev->info.supDID = - (((nfcbDev->sensbRes.protInfo.FwiAdcFo & RFAL_NFCB_SENSB_RES_FO_DID_MASK) != 0U) ? true : - false); - isoDepDev->info.supNAD = - (((nfcbDev->sensbRes.protInfo.FwiAdcFo & RFAL_NFCB_SENSB_RES_FO_NAD_MASK) != 0U) ? true : - false); - - /* Check if DID requested is supported by PICC */ - if((DID != RFAL_ISODEP_NO_DID) && (!isoDepDev->info.supDID)) { - return ERR_PARAM; - } - - /* Enable EMD handling according Digital 2.1 4.1.1.1 ; EMVCo 3.0 4.9.2 */ - rfalSetErrorHandling(RFAL_ERRORHANDLING_EMVCO); - - /***************************************************************************/ - /* Set FDT Poll to be used on upcoming communications */ - if(gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) { - /* Disregard Minimum TR2 returned by PICC, always use FDTb MIN EMVCo 3.0 6.3.2.10 */ - rfalSetFDTPoll(RFAL_FDT_POLL_NFCB_POLLER); - } else { - /* Apply minimum TR2 from SENSB_RES Digital 2.1 7.6.2.23 */ - rfalSetFDTPoll(rfalNfcbTR2ToFDT( - ((nfcbDev->sensbRes.protInfo.FsciProType >> RFAL_NFCB_SENSB_RES_PROTO_TR2_SHIFT) & - RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK))); - } - - /* Calculate max Bit Rate */ - rfalIsoDepCalcBitRate( - maxBR, nfcbDev->sensbRes.protInfo.BRC, &isoDepDev->info.DSI, &isoDepDev->info.DRI); - - /***************************************************************************/ - /* Send ATTRIB Command */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartATTRIB( - (const uint8_t*)&nfcbDev->sensbRes.nfcid0, - (((nfcbDev->sensbRes.protInfo.FwiAdcFo & RFAL_NFCB_SENSB_RES_ADC_ADV_FEATURE_MASK) != - 0U) ? - PARAM1 : - RFAL_ISODEP_ATTRIB_REQ_PARAM1_DEFAULT), - isoDepDev->info.DSI, - isoDepDev->info.DRI, - FSDI, - (gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) ? - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK : - (nfcbDev->sensbRes.protInfo.FsciProType & - ((RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK << RFAL_NFCB_SENSB_RES_PROTO_TR2_SHIFT) | - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK)), /* EMVCo 2.6 6.4.1.9 */ - DID, - HLInfo, - HLInfoLen, - (isoDepDev->info.FWT + isoDepDev->info.dFWT), - &isoDepDev->activation.B.Listener.ATTRIB_RES, - &isoDepDev->activation.B.Listener.ATTRIB_RESLen)); - - gIsoDep.actvDev = isoDepDev; - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollBGetActivationStatus(void) { - ReturnCode ret; - uint8_t mbli; - - /***************************************************************************/ - /* Process ATTRIB Response */ - ret = rfalIsoDepGetATTRIBStatus(); - if(ret != ERR_BUSY) { - if(ret == ERR_NONE) { - /* Digital 1.1 14.6.2.3 - Check if received DID match */ - if((gIsoDep.actvDev->activation.B.Listener.ATTRIB_RES.mbliDid & - RFAL_ISODEP_ATTRIB_RES_DID_MASK) != gIsoDep.did) { - return ERR_PROTO; - } - - /* Retrieve MBLI and calculate new FDS/MBL (Maximum Buffer Length) */ - mbli = - ((gIsoDep.actvDev->activation.B.Listener.ATTRIB_RES.mbliDid >> - RFAL_ISODEP_ATTRIB_RES_MBLI_SHIFT) & - RFAL_ISODEP_ATTRIB_RES_MBLI_MASK); - if(mbli > 0U) { - /* Digital 1.1 14.6.2 Calculate Maximum Buffer Length MBL = FSC x 2^(MBLI-1) */ - gIsoDep.actvDev->info.MBL = - (gIsoDep.actvDev->info.FSx * ((uint32_t)1U << (mbli - 1U))); - } - - /* DSI code the divisor from PICC to PCD */ - /* DRI code the divisor from PCD to PICC */ - rfalSetBitRate(gIsoDep.actvDev->info.DRI, gIsoDep.actvDev->info.DSI); - - /* REMARK: SoF EoF TR0 and TR1 are not passed on to RF layer */ - - /* Start the SFGT timer (reuse RFAL GT timer) */ - rfalSetGT(rfalConvMsTo1fc(gIsoDep.actvDev->info.SFGT)); - rfalFieldOnAndStartGT(); - } else { - gIsoDep.actvDev->info.DSI = RFAL_BR_106; - gIsoDep.actvDev->info.DRI = RFAL_BR_106; - } - - /*******************************************************************************/ - /* Store already FS info, rfalIsoDepGetMaxInfLen() may be called before setting TxRx params */ - gIsoDep.fsx = gIsoDep.actvDev->info.FSx; - } - - return ret; -} - -#endif /* RFAL_FEATURE_NFCB */ - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollHandleSParameters( - rfalIsoDepDevice* isoDepDev, - rfalBitRate maxTxBR, - rfalBitRate maxRxBR) { - uint8_t it; - uint8_t supPCD2PICC; - uint8_t supPICC2PCD; - uint8_t currenttxBR; - uint8_t currentrxBR; - rfalBitRate txBR; - rfalBitRate rxBR; - uint16_t rcvLen; - ReturnCode ret; - rfalIsoDepControlMsgSParam sParam; - - if((isoDepDev == NULL) || (maxTxBR > RFAL_BR_13560) || (maxRxBR > RFAL_BR_13560)) { - return ERR_PARAM; - } - - it = 0; - supPICC2PCD = 0x00; - supPCD2PICC = 0x00; - txBR = RFAL_BR_106; - rxBR = RFAL_BR_106; - sParam.pcb = ISODEP_PCB_SPARAMETERS; - - /*******************************************************************************/ - /* Send S(PARAMETERS) - Block Info */ - sParam.sParam.tag = RFAL_ISODEP_SPARAM_TAG_BLOCKINFO; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_BRREQ; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_BRREQ_LEN; - sParam.sParam.length = it; - - /* Send S(PARAMETERS). Use a fixed FWI of 4 ISO14443-4 2016 7.2 */ - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&sParam, - (RFAL_ISODEP_SPARAM_HDR_LEN + (uint16_t)it), - (uint8_t*)&sParam, - sizeof(rfalIsoDepControlMsgSParam), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - ISODEP_FWT_DEACTIVATION)); - - it = 0; - - /*******************************************************************************/ - /* Check S(PARAMETERS) response */ - if((sParam.pcb != ISODEP_PCB_SPARAMETERS) || - (sParam.sParam.tag != RFAL_ISODEP_SPARAM_TAG_BLOCKINFO) || - (sParam.sParam.value[it] != RFAL_ISODEP_SPARAM_TAG_BRIND) || - (rcvLen < RFAL_ISODEP_SPARAM_HDR_LEN) || - (rcvLen != ((uint16_t)sParam.sParam.length + RFAL_ISODEP_SPARAM_HDR_LEN))) { - return ERR_PROTO; - } - - /* Retrieve PICC's bit rate PICC capabilities */ - for(it = 0; it < (rcvLen - (uint16_t)RFAL_ISODEP_SPARAM_TAG_LEN); it++) { - if((sParam.sParam.value[it] == RFAL_ISODEP_SPARAM_TAG_SUP_PCD2PICC) && - (sParam.sParam.value[it + (uint16_t)RFAL_ISODEP_SPARAM_TAG_LEN] == - RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN)) { - supPCD2PICC = sParam.sParam.value[it + RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN]; - } - - if((sParam.sParam.value[it] == RFAL_ISODEP_SPARAM_TAG_SUP_PICC2PCD) && - (sParam.sParam.value[it + (uint16_t)RFAL_ISODEP_SPARAM_TAG_LEN] == - RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN)) { - supPICC2PCD = sParam.sParam.value[it + RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN]; - } - } - - /*******************************************************************************/ - /* Check if requested bit rates are supported by PICC */ - if((supPICC2PCD == 0x00U) || (supPCD2PICC == 0x00U)) { - return ERR_PROTO; - } - - for(it = 0; it <= (uint8_t)maxTxBR; it++) { - if((supPCD2PICC & (0x01U << it)) != 0U) { - txBR = (rfalBitRate) - it; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created */ - } - } - for(it = 0; it <= (uint8_t)maxRxBR; it++) { - if((supPICC2PCD & (0x01U << it)) != 0U) { - rxBR = (rfalBitRate) - it; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created */ - } - } - - it = 0; - currenttxBR = (uint8_t)txBR; - currentrxBR = (uint8_t)rxBR; - - /*******************************************************************************/ - /* Send S(PARAMETERS) - Bit rates Activation */ - sParam.sParam.tag = RFAL_ISODEP_SPARAM_TAG_BLOCKINFO; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_BRACT; - sParam.sParam.value[it++] = - (RFAL_ISODEP_SPARAM_TVL_HDR_LEN + RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN + - RFAL_ISODEP_SPARAM_TVL_HDR_LEN + RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN); - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_SEL_PCD2PICC; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN; - sParam.sParam.value[it++] = ((uint8_t)0x01U << currenttxBR); - sParam.sParam.value[it++] = 0x00U; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_SEL_PICC2PCD; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN; - sParam.sParam.value[it++] = ((uint8_t)0x01U << currentrxBR); - sParam.sParam.value[it++] = 0x00U; - sParam.sParam.length = it; - - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&sParam, - (RFAL_ISODEP_SPARAM_HDR_LEN + (uint16_t)it), - (uint8_t*)&sParam, - sizeof(rfalIsoDepControlMsgSParam), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - (isoDepDev->info.FWT + isoDepDev->info.dFWT))); - - it = 0; - - /*******************************************************************************/ - /* Check S(PARAMETERS) Acknowledge */ - if((sParam.pcb != ISODEP_PCB_SPARAMETERS) || - (sParam.sParam.tag != RFAL_ISODEP_SPARAM_TAG_BLOCKINFO) || - (sParam.sParam.value[it] != RFAL_ISODEP_SPARAM_TAG_BRACK) || - (rcvLen < RFAL_ISODEP_SPARAM_HDR_LEN)) { - return ERR_PROTO; - } - - EXIT_ON_ERR(ret, rfalSetBitRate(txBR, rxBR)); - - isoDepDev->info.DRI = txBR; - isoDepDev->info.DSI = rxBR; - - return ERR_NONE; -} - -/*******************************************************************************/ -static void rfalIsoDepCalcBitRate( - rfalBitRate maxAllowedBR, - uint8_t piccBRCapability, - rfalBitRate* dsi, - rfalBitRate* dri) { - uint8_t driMask; - uint8_t dsiMask; - int8_t i; - bool bitrateFound; - rfalBitRate curMaxBR; - - curMaxBR = maxAllowedBR; - - do { - bitrateFound = true; - - (*dsi) = RFAL_BR_106; - (*dri) = RFAL_BR_106; - - /* Digital 1.0 5.6.2.5 & 11.6.2.14: A received RFU value of b4 = 1b MUST be interpreted as if b7 to b1 ? 0000000b (only 106 kbits/s in both direction) */ - if(((RFAL_ISODEP_BITRATE_RFU_MASK & piccBRCapability) != 0U) || (curMaxBR > RFAL_BR_848) || - (curMaxBR == RFAL_BR_KEEP)) { - return; - } - - /***************************************************************************/ - /* Determine Listen->Poll bit rate */ - dsiMask = (piccBRCapability & RFAL_ISODEP_BSI_MASK); - for(i = 2; i >= 0; i--) // Check supported bit rate from the highest - { - if(((dsiMask & (0x10U << (uint8_t)i)) != 0U) && - (((uint8_t)i + 1U) <= (uint8_t)curMaxBR)) { - uint8_t newdsi = ((uint8_t)i) + 1U; - (*dsi) = (rfalBitRate) - newdsi; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created */ - break; - } - } - - /***************************************************************************/ - /* Determine Poll->Listen bit rate */ - driMask = (piccBRCapability & RFAL_ISODEP_BRI_MASK); - for(i = 2; i >= 0; i--) /* Check supported bit rate from the highest */ - { - if(((driMask & (0x01U << (uint8_t)i)) != 0U) && - (((uint8_t)i + 1U) <= (uint8_t)curMaxBR)) { - uint8_t newdri = ((uint8_t)i) + 1U; - (*dri) = (rfalBitRate) - newdri; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created */ - break; - } - } - - /***************************************************************************/ - /* Check if different bit rate is supported */ - - /* Digital 1.0 Table 67: if b8=1b, then only the same bit rate divisor for both directions is supported */ - if((piccBRCapability & RFAL_ISODEP_SAME_BITRATE_MASK) != 0U) { - (*dsi) = MIN((*dsi), (*dri)); - (*dri) = (*dsi); - /* Check that the baudrate is supported */ - if((RFAL_BR_106 != (*dsi)) && - (!(((dsiMask & (0x10U << ((uint8_t)(*dsi) - 1U))) != 0U) && - ((driMask & (0x01U << ((uint8_t)(*dri) - 1U))) != 0U)))) { - bitrateFound = false; - curMaxBR = - (*dsi); /* set allowed bitrate to be lowest and determine bit rate again */ - } - } - } while(!(bitrateFound)); -} - -/*******************************************************************************/ -static uint32_t rfalIsoDepSFGI2SFGT(uint8_t sfgi) { - uint32_t sfgt; - uint8_t tmpSFGI; - - tmpSFGI = sfgi; - - if(tmpSFGI > ISODEP_SFGI_MAX) { - tmpSFGI = ISODEP_SFGI_MIN; - } - - if(tmpSFGI != ISODEP_SFGI_MIN) { - /* If sfgi != 0 wait SFGT + dSFGT Digital 1.1 13.8.2.1 */ - sfgt = isoDepCalcSGFT(sfgi) + isoDepCalcdSGFT(sfgi); - } - /* Otherwise use FDTPoll min Digital 1.1 13.8.2.3*/ - else { - sfgt = RFAL_FDT_POLL_NFCA_POLLER; - } - - /* Convert carrier cycles to milli seconds */ - return (rfalConv1fcToMs(sfgt) + 1U); -} - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - -/*******************************************************************************/ -static void rfalIsoDepApdu2IBLockParam( - rfalIsoDepApduTxRxParam apduParam, - rfalIsoDepTxRxParam* iBlockParam, - uint16_t txPos, - uint16_t rxPos) { - NO_WARNING(rxPos); /* Keep this param for future use */ - - iBlockParam->DID = apduParam.DID; - iBlockParam->FSx = apduParam.FSx; - iBlockParam->ourFSx = apduParam.ourFSx; - iBlockParam->FWT = apduParam.FWT; - iBlockParam->dFWT = apduParam.dFWT; - - if((apduParam.txBufLen - txPos) > rfalIsoDepGetMaxInfLen()) { - iBlockParam->isTxChaining = true; - iBlockParam->txBufLen = rfalIsoDepGetMaxInfLen(); - } else { - iBlockParam->isTxChaining = false; - iBlockParam->txBufLen = (apduParam.txBufLen - txPos); - } - - /* TxBuf is moved to the beginning for every I-Block */ - iBlockParam->txBuf = - (rfalIsoDepBufFormat*)apduParam - .txBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - iBlockParam->rxBuf = - apduParam - .tmpBuf; /* Simply using the apdu buffer is not possible because of current ACK handling */ - iBlockParam->isRxChaining = &gIsoDep.isAPDURxChaining; - iBlockParam->rxLen = apduParam.rxLen; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepStartApduTransceive(rfalIsoDepApduTxRxParam param) { - rfalIsoDepTxRxParam txRxParam; - - /* Initialize and store APDU context */ - gIsoDep.APDUParam = param; - gIsoDep.APDUTxPos = 0; - gIsoDep.APDURxPos = 0; - - /* Assign current FSx to calculate INF length (only change the FSx from activation if no to Keep) */ - gIsoDep.ourFsx = ((param.ourFSx != RFAL_ISODEP_FSX_KEEP) ? param.ourFSx : gIsoDep.ourFsx); - gIsoDep.fsx = param.FSx; - - /* Convert APDU TxRxParams to I-Block TxRxParams */ - rfalIsoDepApdu2IBLockParam( - gIsoDep.APDUParam, &txRxParam, gIsoDep.APDUTxPos, gIsoDep.APDURxPos); - - return rfalIsoDepStartTransceive(txRxParam); -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepGetApduTransceiveStatus(void) { - ReturnCode ret; - rfalIsoDepTxRxParam txRxParam; - - ret = rfalIsoDepGetTransceiveStatus(); - switch(ret) { - /*******************************************************************************/ - case ERR_NONE: - - /* Check if we are still doing chaining on Tx */ - if(gIsoDep.isTxChaining) { - /* Add already Tx bytes */ - gIsoDep.APDUTxPos += gIsoDep.txBufLen; - - /* Convert APDU TxRxParams to I-Block TxRxParams */ - rfalIsoDepApdu2IBLockParam( - gIsoDep.APDUParam, &txRxParam, gIsoDep.APDUTxPos, gIsoDep.APDURxPos); - - if(txRxParam.txBufLen > 0U) /* MISRA 21.18 */ - { - /* Move next I-Block to beginning of APDU Tx buffer */ - ST_MEMCPY( - gIsoDep.APDUParam.txBuf->apdu, - &gIsoDep.APDUParam.txBuf->apdu[gIsoDep.APDUTxPos], - txRxParam.txBufLen); - } - - EXIT_ON_ERR(ret, rfalIsoDepStartTransceive(txRxParam)); - return ERR_BUSY; - } - - /* APDU TxRx is done */ - /* fall through */ - - /*******************************************************************************/ - case ERR_AGAIN: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Check if no APDU transceive has been started before (data from rfalIsoDepListenStartActivation) */ - if(gIsoDep.APDUParam.rxLen == NULL) { - if(ret == ERR_AGAIN) { - /* In Listen mode first chained packet cannot be retrieved via APDU interface */ - return ERR_NOTSUPP; - } - - /* TxRx is complete and full data is already available */ - return ERR_NONE; - } - - if(*gIsoDep.APDUParam.rxLen > 0U) /* MISRA 21.18 */ - { - /* Ensure that data in tmpBuf still fits into APDU buffer */ - if((gIsoDep.APDURxPos + (*gIsoDep.APDUParam.rxLen)) > - (uint16_t)RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN) { - return ERR_NOMEM; - } - - /* Copy chained packet from tmp buffer to APDU buffer */ - ST_MEMCPY( - &gIsoDep.APDUParam.rxBuf->apdu[gIsoDep.APDURxPos], - gIsoDep.APDUParam.tmpBuf->inf, - *gIsoDep.APDUParam.rxLen); - gIsoDep.APDURxPos += *gIsoDep.APDUParam.rxLen; - } - - /* Update output param rxLen */ - *gIsoDep.APDUParam.rxLen = gIsoDep.APDURxPos * 8; - - /* Wait for following I-Block or APDU TxRx has finished */ - return ((ret == ERR_AGAIN) ? ERR_BUSY : ERR_NONE); - - /*******************************************************************************/ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - return ret; -} - -#endif /* RFAL_FEATURE_ISO_DEP */ diff --git a/lib/ST25RFAL002/source/rfal_nfc.c b/lib/ST25RFAL002/source/rfal_nfc.c deleted file mode 100644 index 57ff2e2358..0000000000 --- a/lib/ST25RFAL002/source/rfal_nfc.c +++ /dev/null @@ -1,2118 +0,0 @@ -/** - ****************************************************************************** - * - * COPYRIGHT(c) 2020 STMicroelectronics - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of STMicroelectronics nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - ****************************************************************************** - */ - -/*! \file rfal_nfc.c - * - * \author Gustavo Patricio - * - * \brief RFAL NFC device - * - * This module provides the required features to behave as an NFC Poller - * or Listener device. It grants an easy to use interface for the following - * activities: Technology Detection, Collision Resollution, Activation, - * Data Exchange, and Deactivation - * - * This layer is influenced by (but not fully aligned with) the NFC Forum - * specifications, in particular: Activity 2.0 and NCI 2.0 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfc.h" -#include "utils.h" -#include "rfal_analogConfig.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define RFAL_NFC_MAX_DEVICES 5U /* Max number of devices supported */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -#define rfalNfcNfcNotify(st) \ - if(gNfcDev.disc.notifyCb != NULL) gNfcDev.disc.notifyCb(st) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Buffer union, only one interface is used at a time */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one interface at a time */ - rfalIsoDepBufFormat isoDepBuf; /*!< ISO-DEP buffer format (with header/prologue) */ - rfalNfcDepBufFormat nfcDepBuf; /*!< NFC-DEP buffer format (with header/prologue) */ -} rfalNfcTmpBuffer; - -typedef struct { - rfalNfcState state; /* Main state */ - uint16_t techsFound; /* Technologies found bitmask */ - uint16_t techs2do; /* Technologies still to be performed */ - rfalBitRate ap2pBR; /* Bit rate to poll for AP2P */ - uint8_t selDevIdx; /* Selected device index */ - rfalNfcDevice* activeDev; /* Active device pointer */ - rfalNfcDiscoverParam disc; /* Discovery parameters */ - rfalNfcDevice devList[RFAL_NFC_MAX_DEVICES]; /*!< Location of device list */ - uint8_t devCnt; /* Devices found counter */ - uint32_t discTmr; /* Discovery Total duration timer */ - ReturnCode dataExErr; /* Last Data Exchange error */ - bool discRestart; /* Restart discover after deactivation flag */ - bool isRxChaining; /* Flag indicating Other device is chaining */ - uint32_t lmMask; /* Listen Mode mask */ - bool isTechInit; /* Flag indicating technology has been set */ - bool isOperOngoing; /* Flag indicating operation is ongoing */ - - rfalNfcBuffer txBuf; /* Tx buffer for Data Exchange */ - rfalNfcBuffer rxBuf; /* Rx buffer for Data Exchange */ - uint16_t rxLen; /* Length of received data on Data Exchange */ - -#if RFAL_FEATURE_NFC_DEP || RFAL_FEATURE_ISO_DEP - rfalNfcTmpBuffer tmpBuf; /* Tmp buffer for Data Exchange */ -#endif /* RFAL_FEATURE_NFC_DEP || RFAL_FEATURE_ISO_DEP */ - -} rfalNfc; - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ -#ifdef RFAL_TEST_MODE -rfalNfc gNfcDev; -#else /* RFAL_TEST_MODE */ -static rfalNfc gNfcDev; -#endif /* RFAL_TEST_MODE */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode rfalNfcPollTechDetetection(void); -static ReturnCode rfalNfcPollCollResolution(void); -static ReturnCode rfalNfcPollActivation(uint8_t devIt); -static ReturnCode rfalNfcDeactivation(void); - -#if RFAL_FEATURE_NFC_DEP -static ReturnCode rfalNfcNfcDepActivate( - rfalNfcDevice* device, - rfalNfcDepCommMode commMode, - const uint8_t* atrReq, - uint16_t atrReqLen); -#endif /* RFAL_FEATURE_NFC_DEP */ - -#if RFAL_FEATURE_LISTEN_MODE -static ReturnCode rfalNfcListenActivation(void); -#endif /* RFAL_FEATURE_LISTEN_MODE*/ - -/*******************************************************************************/ -ReturnCode rfalNfcInitialize(void) { - ReturnCode err; - - gNfcDev.state = RFAL_NFC_STATE_NOTINIT; - - rfalAnalogConfigInitialize(); /* Initialize RFAL's Analog Configs */ - EXIT_ON_ERR(err, rfalInitialize()); /* Initialize RFAL */ - - gNfcDev.state = RFAL_NFC_STATE_IDLE; /* Go to initialized */ - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDiscover(const rfalNfcDiscoverParam* disParams) { - /* Check if initialization has been performed */ - if(gNfcDev.state != RFAL_NFC_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Check valid parameters */ - if((disParams == NULL) || (disParams->devLimit > RFAL_NFC_MAX_DEVICES) || - (disParams->devLimit == 0U) || - ((disParams->maxBR > RFAL_BR_1695) && (disParams->maxBR != RFAL_BR_KEEP)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_F) != 0U) && - (disParams->nfcfBR != RFAL_BR_212) && (disParams->nfcfBR != RFAL_BR_424)) || - ((((disParams->techs2Find & RFAL_NFC_POLL_TECH_AP2P) != 0U) && - (disParams->ap2pBR > RFAL_BR_424)) || - (disParams->GBLen > RFAL_NFCDEP_GB_MAX_LEN))) { - return ERR_PARAM; - } - - if((((disParams->techs2Find & RFAL_NFC_POLL_TECH_A) != 0U) && !((bool)RFAL_FEATURE_NFCA)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_B) != 0U) && !((bool)RFAL_FEATURE_NFCB)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_F) != 0U) && !((bool)RFAL_FEATURE_NFCF)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_V) != 0U) && !((bool)RFAL_FEATURE_NFCV)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_ST25TB) != 0U) && - !((bool)RFAL_FEATURE_ST25TB)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_AP2P) != 0U) && - !((bool)RFAL_FEATURE_NFC_DEP)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_A) != 0U) && !((bool)RFAL_FEATURE_NFCA)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_B) != 0U) && !((bool)RFAL_FEATURE_NFCB)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_F) != 0U) && !((bool)RFAL_FEATURE_NFCF)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_AP2P) != 0U) && - !((bool)RFAL_FEATURE_NFC_DEP))) { - return ERR_DISABLED; /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset */ - } - - /* Initialize context for discovery */ - gNfcDev.activeDev = NULL; - gNfcDev.techsFound = RFAL_NFC_TECH_NONE; - gNfcDev.devCnt = 0; - gNfcDev.discRestart = true; - gNfcDev.isTechInit = false; - gNfcDev.disc = *disParams; - - /* Calculate Listen Mask */ - gNfcDev.lmMask = 0U; - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_A) != 0U) ? RFAL_LM_MASK_NFCA : 0U); - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_B) != 0U) ? RFAL_LM_MASK_NFCB : 0U); - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_F) != 0U) ? RFAL_LM_MASK_NFCF : 0U); - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_AP2P) != 0U) ? RFAL_LM_MASK_ACTIVE_P2P : - 0U); - -#if !RFAL_FEATURE_LISTEN_MODE - /* Check if Listen Mode is supported/Enabled */ - if(gNfcDev.lmMask != 0U) { - return ERR_DISABLED; - } -#endif - - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDeactivate(bool discovery) { - /* Check for valid state */ - if(gNfcDev.state <= RFAL_NFC_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Check if discovery is to continue afterwards */ - if((discovery == true) && (gNfcDev.disc.techs2Find != RFAL_NFC_TECH_NONE)) { - /* If so let the state machine continue*/ - gNfcDev.discRestart = discovery; - gNfcDev.state = RFAL_NFC_STATE_DEACTIVATION; - } else { - /* Otherwise deactivate immediately and go to IDLE */ - rfalNfcDeactivation(); - gNfcDev.state = RFAL_NFC_STATE_IDLE; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcSelect(uint8_t devIdx) { - /* Check for valid state */ - if(gNfcDev.state != RFAL_NFC_STATE_POLL_SELECT) { - return ERR_WRONG_STATE; - } - - gNfcDev.selDevIdx = devIdx; - gNfcDev.state = RFAL_NFC_STATE_POLL_ACTIVATION; - - return ERR_NONE; -} - -/*******************************************************************************/ -rfalNfcState rfalNfcGetState(void) { - return gNfcDev.state; -} - -/*******************************************************************************/ -ReturnCode rfalNfcGetDevicesFound(rfalNfcDevice** devList, uint8_t* devCnt) { - /* Check for valid state */ - if(gNfcDev.state < RFAL_NFC_STATE_POLL_SELECT) { - return ERR_WRONG_STATE; - } - - /* Check valid parameters */ - if((devList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = gNfcDev.devCnt; - *devList = gNfcDev.devList; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcGetActiveDevice(rfalNfcDevice** dev) { - /* Check for valid state */ - if(gNfcDev.state < RFAL_NFC_STATE_ACTIVATED) { - return ERR_WRONG_STATE; - } - - /* Check valid parameter */ - if(dev == NULL) { - return ERR_PARAM; - } - - /* Check for valid state */ - if((gNfcDev.devCnt == 0U) || (gNfcDev.activeDev == NULL)) { - return ERR_REQUEST; - } - - *dev = gNfcDev.activeDev; - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalNfcWorker(void) { - ReturnCode err; - - rfalWorker(); /* Execute RFAL process */ - - switch(gNfcDev.state) { - /*******************************************************************************/ - case RFAL_NFC_STATE_NOTINIT: - case RFAL_NFC_STATE_IDLE: - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_START_DISCOVERY: - - /* Initialize context for discovery cycle */ - gNfcDev.devCnt = 0; - gNfcDev.selDevIdx = 0; - gNfcDev.techsFound = RFAL_NFC_TECH_NONE; - gNfcDev.techs2do = gNfcDev.disc.techs2Find; - gNfcDev.state = RFAL_NFC_STATE_POLL_TECHDETECT; - -#if RFAL_FEATURE_WAKEUP_MODE - /* Check if Low power Wake-Up is to be performed */ - if(gNfcDev.disc.wakeupEnabled) { - /* Initialize Low power Wake-up mode and wait */ - err = rfalWakeUpModeStart( - (gNfcDev.disc.wakeupConfigDefault ? NULL : &gNfcDev.disc.wakeupConfig)); - if(err == ERR_NONE) { - gNfcDev.state = RFAL_NFC_STATE_WAKEUP_MODE; - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller that WU was started */ - } - } -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_WAKEUP_MODE: - -#if RFAL_FEATURE_WAKEUP_MODE - /* Check if the Wake-up mode has woke */ - if(rfalWakeUpModeHasWoke()) { - rfalWakeUpModeStop(); /* Disable Wake-up mode */ - gNfcDev.state = RFAL_NFC_STATE_POLL_TECHDETECT; /* Go to Technology detection */ - - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller that WU has woke */ - } -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_POLL_TECHDETECT: - - /* Start total duration timer */ - platformTimerDestroy(gNfcDev.discTmr); - gNfcDev.discTmr = (uint32_t)platformTimerCreate(gNfcDev.disc.totalDuration); - - err = - rfalNfcPollTechDetetection(); /* Perform Technology Detection */ - if(err != ERR_BUSY) /* Wait until all technologies are performed */ - { - if((err != ERR_NONE) || - (gNfcDev.techsFound == - RFAL_NFC_TECH_NONE)) /* Check if any error occurred or no techs were found */ - { - rfalFieldOff(); - gNfcDev.state = - RFAL_NFC_STATE_LISTEN_TECHDETECT; /* Nothing found as poller, go to listener */ - break; - } - - gNfcDev.techs2do = - gNfcDev.techsFound; /* Store the found technologies for collision resolution */ - gNfcDev.state = - RFAL_NFC_STATE_POLL_COLAVOIDANCE; /* One or more devices found, go to Collision Avoidance */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_POLL_COLAVOIDANCE: - - err = - rfalNfcPollCollResolution(); /* Resolve any eventual collision */ - if(err != ERR_BUSY) /* Wait until all technologies are performed */ - { - if((err != ERR_NONE) || - (gNfcDev.devCnt == 0U)) /* Check if any error occurred or no devices were found */ - { - gNfcDev.state = RFAL_NFC_STATE_DEACTIVATION; - break; /* Unable to retrieve any device, restart loop */ - } - - /* Check if more than one device has been found */ - if(gNfcDev.devCnt > 1U) { - /* If more than one device was found inform upper layer to choose which one to activate */ - if(gNfcDev.disc.notifyCb != NULL) { - gNfcDev.state = RFAL_NFC_STATE_POLL_SELECT; - gNfcDev.disc.notifyCb(gNfcDev.state); - break; - } - } - - /* If only one device or no callback has been set, activate the first device found */ - gNfcDev.selDevIdx = 0U; - gNfcDev.state = RFAL_NFC_STATE_POLL_ACTIVATION; - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_POLL_ACTIVATION: - - err = rfalNfcPollActivation(gNfcDev.selDevIdx); - if(err != ERR_BUSY) /* Wait until all Activation is complete */ - { - if(err != ERR_NONE) /* Activation failed selected device */ - { - gNfcDev.state = - RFAL_NFC_STATE_DEACTIVATION; /* If Activation failed, restart loop */ - break; - } - - gNfcDev.state = RFAL_NFC_STATE_ACTIVATED; /* Device has been properly activated */ - rfalNfcNfcNotify( - gNfcDev.state); /* Inform upper layer that a device has been activated */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_DATAEXCHANGE: - - rfalNfcDataExchangeGetStatus(); /* Run the internal state machine */ - - if(gNfcDev.dataExErr != ERR_BUSY) /* If Dataexchange has terminated */ - { - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE; /* Go to done state */ - rfalNfcNfcNotify(gNfcDev.state); /* And notify caller */ - } - if(gNfcDev.dataExErr == ERR_SLEEP_REQ) /* Check if Listen mode has to go to Sleep */ - { - gNfcDev.state = RFAL_NFC_STATE_LISTEN_SLEEP; /* Go to Listen Sleep state */ - rfalNfcNfcNotify(gNfcDev.state); /* And notify caller */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_DEACTIVATION: - - rfalNfcDeactivation(); /* Deactivate current device */ - - gNfcDev.state = - ((gNfcDev.discRestart) ? RFAL_NFC_STATE_START_DISCOVERY : RFAL_NFC_STATE_IDLE); - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_LISTEN_TECHDETECT: - - if(platformTimerIsExpired(gNfcDev.discTmr)) { -#if RFAL_FEATURE_LISTEN_MODE - rfalListenStop(); -#else - rfalFieldOff(); -#endif /* RFAL_FEATURE_LISTEN_MODE */ - - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; /* Restart the discovery loop */ - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - break; - } - -#if RFAL_FEATURE_LISTEN_MODE - - if(gNfcDev.lmMask != 0U) /* Check if configured to perform Listen mode */ - { - err = rfalListenStart( - gNfcDev.lmMask, - &gNfcDev.disc.lmConfigPA, - NULL, - &gNfcDev.disc.lmConfigPF, - (uint8_t*)&gNfcDev.rxBuf.rfBuf, - (uint16_t)rfalConvBytesToBits(sizeof(gNfcDev.rxBuf.rfBuf)), - &gNfcDev.rxLen); - if(err == ERR_NONE) { - gNfcDev.state = - RFAL_NFC_STATE_LISTEN_COLAVOIDANCE; /* Wait for listen mode to be activated */ - } - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_LISTEN_COLAVOIDANCE: - - if(platformTimerIsExpired( - gNfcDev.discTmr)) /* Check if the total duration has been reached */ - { - rfalListenStop(); - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; /* Restart the discovery loop */ - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - break; - } - - /* Check for external field */ - if(rfalListenGetState(NULL, NULL) >= RFAL_LM_STATE_IDLE) { - gNfcDev.state = - RFAL_NFC_STATE_LISTEN_ACTIVATION; /* Wait for listen mode to be activated */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_LISTEN_ACTIVATION: - case RFAL_NFC_STATE_LISTEN_SLEEP: - - err = rfalNfcListenActivation(); - if(err != ERR_BUSY) { - if(err == ERR_NONE) { - gNfcDev.activeDev = - gNfcDev.devList; /* Assign the active device to be used further on */ - gNfcDev.devCnt++; - - gNfcDev.state = RFAL_NFC_STATE_ACTIVATED; /* Device has been properly activated */ - rfalNfcNfcNotify( - gNfcDev.state); /* Inform upper layer that a device has been activated */ - } else { - rfalListenStop(); - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; /* Restart the discovery loop */ - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - } - } -#endif /* RFAL_FEATURE_LISTEN_MODE */ - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_ACTIVATED: - case RFAL_NFC_STATE_POLL_SELECT: - case RFAL_NFC_STATE_DATAEXCHANGE_DONE: - default: - return; - } -} - -/*******************************************************************************/ -ReturnCode rfalNfcDataExchangeStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t flags) { - ReturnCode err; - rfalTransceiveContext ctx; - - /*******************************************************************************/ - /* The Data Exchange is divided in two different moments, the trigger/Start of * - * the transfer followed by the check until its completion */ - if((gNfcDev.state >= RFAL_NFC_STATE_ACTIVATED) && (gNfcDev.activeDev != NULL)) { - /*******************************************************************************/ - /* In Listen mode is the Poller that initiates the communicatation */ - /* Assign output parameters and rfalNfcDataExchangeGetStatus will return */ - /* incoming data from Poller/Initiator */ - if((gNfcDev.state == RFAL_NFC_STATE_ACTIVATED) && - rfalNfcIsRemDevPoller(gNfcDev.activeDev->type)) { - if(txDataLen > 0U) { - return ERR_WRONG_STATE; - } - - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - *rxData = (uint8_t*)( (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_ISODEP) ? gNfcDev.rxBuf.isoDepBuf.apdu : - ((gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_NFCDEP) ? gNfcDev.rxBuf.nfcDepBuf.pdu : gNfcDev.rxBuf.rfBuf)); - if(gNfcDev.disc.activate_after_sak) { - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE; - } - return ERR_NONE; - } - - /*******************************************************************************/ - switch(gNfcDev.activeDev - ->rfInterface) /* Check which RF interface shall be used/has been activated */ - { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)txData, - txDataLen, - gNfcDev.rxBuf.rfBuf, - sizeof(gNfcDev.rxBuf.rfBuf), - &gNfcDev.rxLen, - flags, - fwt); - if(flags == RFAL_TXRX_FLAGS_RAW) { - ctx.txBufLen = txDataLen; - } - *rxData = (uint8_t*)gNfcDev.rxBuf.rfBuf; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - err = rfalStartTransceive(&ctx); - break; - -#if RFAL_FEATURE_ISO_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_ISODEP: { - rfalIsoDepApduTxRxParam isoDepTxRx; - - if(txDataLen > sizeof(gNfcDev.txBuf.isoDepBuf.apdu)) { - return ERR_NOMEM; - } - - if(txDataLen > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.isoDepBuf.apdu, txData, txDataLen); - } - - isoDepTxRx.DID = RFAL_ISODEP_NO_DID; - isoDepTxRx.ourFSx = RFAL_ISODEP_FSX_KEEP; - isoDepTxRx.FSx = gNfcDev.activeDev->proto.isoDep.info.FSx; - isoDepTxRx.dFWT = gNfcDev.activeDev->proto.isoDep.info.dFWT; - isoDepTxRx.FWT = gNfcDev.activeDev->proto.isoDep.info.FWT; - isoDepTxRx.txBuf = &gNfcDev.txBuf.isoDepBuf; - isoDepTxRx.txBufLen = txDataLen; - isoDepTxRx.rxBuf = &gNfcDev.rxBuf.isoDepBuf; - isoDepTxRx.rxLen = &gNfcDev.rxLen; - isoDepTxRx.tmpBuf = &gNfcDev.tmpBuf.isoDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.isoDepBuf.apdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL ISO-DEP Transceive */ - err = rfalIsoDepStartApduTransceive(isoDepTxRx); - break; - } -#endif /* RFAL_FEATURE_ISO_DEP */ - -#if RFAL_FEATURE_NFC_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_NFCDEP: { - rfalNfcDepPduTxRxParam nfcDepTxRx; - - if(txDataLen > sizeof(gNfcDev.txBuf.nfcDepBuf.pdu)) { - return ERR_NOMEM; - } - - if(txDataLen > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.nfcDepBuf.pdu, txData, txDataLen); - } - - nfcDepTxRx.DID = RFAL_NFCDEP_DID_KEEP; - nfcDepTxRx.FSx = - rfalNfcIsRemDevListener(gNfcDev.activeDev->type) ? - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Target.ATR_RES.PPt)) : - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Initiator.ATR_REQ.PPi)); - nfcDepTxRx.dFWT = gNfcDev.activeDev->proto.nfcDep.info.dFWT; - nfcDepTxRx.FWT = gNfcDev.activeDev->proto.nfcDep.info.FWT; - nfcDepTxRx.txBuf = &gNfcDev.txBuf.nfcDepBuf; - nfcDepTxRx.txBufLen = txDataLen; - nfcDepTxRx.rxBuf = &gNfcDev.rxBuf.nfcDepBuf; - nfcDepTxRx.rxLen = &gNfcDev.rxLen; - nfcDepTxRx.tmpBuf = &gNfcDev.tmpBuf.nfcDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.nfcDepBuf.pdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL NFC-DEP Transceive */ - err = rfalNfcDepStartPduTransceive(nfcDepTxRx); - break; - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - default: - err = ERR_PARAM; - break; - } - - /* If a transceive has successfuly started flag Data Exchange as ongoing */ - if(err == ERR_NONE) { - gNfcDev.dataExErr = ERR_BUSY; - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; - } - - return err; - } - - return ERR_WRONG_STATE; -} - -ReturnCode rfalNfcDataExchangeCustomStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t flags) { - ReturnCode err; - rfalTransceiveContext ctx; - - /*******************************************************************************/ - /* The Data Exchange is divided in two different moments, the trigger/Start of * - * the transfer followed by the check until its completion */ - if((gNfcDev.state >= RFAL_NFC_STATE_ACTIVATED) && (gNfcDev.activeDev != NULL)) { - /*******************************************************************************/ - /* In Listen mode is the Poller that initiates the communicatation */ - /* Assign output parameters and rfalNfcDataExchangeGetStatus will return */ - /* incoming data from Poller/Initiator */ - if((gNfcDev.state == RFAL_NFC_STATE_ACTIVATED) && - rfalNfcIsRemDevPoller(gNfcDev.activeDev->type)) { - if(txDataLen > 0U) { - return ERR_WRONG_STATE; - } - - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - *rxData = (uint8_t*)( (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_ISODEP) ? gNfcDev.rxBuf.isoDepBuf.apdu : - ((gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_NFCDEP) ? gNfcDev.rxBuf.nfcDepBuf.pdu : gNfcDev.rxBuf.rfBuf)); - if(gNfcDev.disc.activate_after_sak) { - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE; - } - return ERR_NONE; - } - - /*******************************************************************************/ - switch(gNfcDev.activeDev - ->rfInterface) /* Check which RF interface shall be used/has been activated */ - { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - ctx.rxBuf = gNfcDev.rxBuf.rfBuf; - ctx.rxBufLen = 8 * sizeof(gNfcDev.rxBuf.rfBuf); - ctx.rxRcvdLen = &gNfcDev.rxLen; - ctx.txBuf = txData; - ctx.txBufLen = txDataLen; - ctx.flags = flags; - ctx.fwt = fwt; - *rxData = (uint8_t*)gNfcDev.rxBuf.rfBuf; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - err = rfalStartTransceive(&ctx); - break; - -#if RFAL_FEATURE_ISO_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_ISODEP: { - rfalIsoDepApduTxRxParam isoDepTxRx; - uint16_t tx_bytes = txDataLen / 8; - - if(tx_bytes > sizeof(gNfcDev.txBuf.isoDepBuf.apdu)) { - return ERR_NOMEM; - } - - if(tx_bytes > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.isoDepBuf.apdu, txData, tx_bytes); - } - - isoDepTxRx.DID = RFAL_ISODEP_NO_DID; - isoDepTxRx.ourFSx = RFAL_ISODEP_FSX_KEEP; - isoDepTxRx.FSx = gNfcDev.activeDev->proto.isoDep.info.FSx; - isoDepTxRx.dFWT = gNfcDev.activeDev->proto.isoDep.info.dFWT; - isoDepTxRx.FWT = gNfcDev.activeDev->proto.isoDep.info.FWT; - isoDepTxRx.txBuf = &gNfcDev.txBuf.isoDepBuf; - isoDepTxRx.txBufLen = tx_bytes; - isoDepTxRx.rxBuf = &gNfcDev.rxBuf.isoDepBuf; - isoDepTxRx.rxLen = &gNfcDev.rxLen; - isoDepTxRx.tmpBuf = &gNfcDev.tmpBuf.isoDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.isoDepBuf.apdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL ISO-DEP Transceive */ - err = rfalIsoDepStartApduTransceive(isoDepTxRx); - break; - } -#endif /* RFAL_FEATURE_ISO_DEP */ - -#if RFAL_FEATURE_NFC_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_NFCDEP: { - rfalNfcDepPduTxRxParam nfcDepTxRx; - - if(txDataLen > sizeof(gNfcDev.txBuf.nfcDepBuf.pdu)) { - return ERR_NOMEM; - } - - if(txDataLen > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.nfcDepBuf.pdu, txData, txDataLen); - } - - nfcDepTxRx.DID = RFAL_NFCDEP_DID_KEEP; - nfcDepTxRx.FSx = - rfalNfcIsRemDevListener(gNfcDev.activeDev->type) ? - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Target.ATR_RES.PPt)) : - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Initiator.ATR_REQ.PPi)); - nfcDepTxRx.dFWT = gNfcDev.activeDev->proto.nfcDep.info.dFWT; - nfcDepTxRx.FWT = gNfcDev.activeDev->proto.nfcDep.info.FWT; - nfcDepTxRx.txBuf = &gNfcDev.txBuf.nfcDepBuf; - nfcDepTxRx.txBufLen = txDataLen; - nfcDepTxRx.rxBuf = &gNfcDev.rxBuf.nfcDepBuf; - nfcDepTxRx.rxLen = &gNfcDev.rxLen; - nfcDepTxRx.tmpBuf = &gNfcDev.tmpBuf.nfcDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.nfcDepBuf.pdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL NFC-DEP Transceive */ - err = rfalNfcDepStartPduTransceive(nfcDepTxRx); - break; - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - default: - err = ERR_PARAM; - break; - } - - /* If a transceive has successfuly started flag Data Exchange as ongoing */ - if(err == ERR_NONE) { - gNfcDev.dataExErr = ERR_BUSY; - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; - } - - return err; - } - - return ERR_WRONG_STATE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDataExchangeGetStatus(void) { - /*******************************************************************************/ - /* Check if it's the first frame received in Listen mode */ - if(gNfcDev.state == RFAL_NFC_STATE_ACTIVATED) { - /* Continue data exchange as normal */ - gNfcDev.dataExErr = ERR_BUSY; - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; - - /* Check if we performing in T3T CE */ - if((gNfcDev.activeDev->type == RFAL_NFC_POLL_TYPE_NFCF) && - (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_RF)) { - /* The first frame has been retrieved by rfalListenMode, flag data immediately */ - /* Can only call rfalGetTransceiveStatus() after starting a transceive with rfalStartTransceive */ - gNfcDev.dataExErr = ERR_NONE; - } - } - - /*******************************************************************************/ - /* Check if we are in we have been placed to sleep, and return last error */ - if(gNfcDev.state == RFAL_NFC_STATE_LISTEN_SLEEP) { - return gNfcDev.dataExErr; /* ERR_SLEEP_REQ */ - } - - /*******************************************************************************/ - /* Check if Data exchange has been started */ - if((gNfcDev.state != RFAL_NFC_STATE_DATAEXCHANGE) && - (gNfcDev.state != RFAL_NFC_STATE_DATAEXCHANGE_DONE)) { - return ERR_WRONG_STATE; - } - - /* Check if Data exchange is still ongoing */ - if(gNfcDev.dataExErr == ERR_BUSY) { - switch(gNfcDev.activeDev->rfInterface) { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - gNfcDev.dataExErr = rfalGetTransceiveStatus(); - break; - -#if RFAL_FEATURE_ISO_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_ISODEP: - gNfcDev.dataExErr = rfalIsoDepGetApduTransceiveStatus(); - break; -#endif /* RFAL_FEATURE_ISO_DEP */ - - /*******************************************************************************/ -#if RFAL_FEATURE_NFC_DEP - case RFAL_NFC_INTERFACE_NFCDEP: - gNfcDev.dataExErr = rfalNfcDepGetPduTransceiveStatus(); - break; -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - default: - gNfcDev.dataExErr = ERR_PARAM; - break; - } - -#if RFAL_FEATURE_LISTEN_MODE - /*******************************************************************************/ - /* If a Sleep request has been received (Listen Mode) go to sleep immediately */ - if(gNfcDev.dataExErr == ERR_SLEEP_REQ) { - EXIT_ON_ERR( - gNfcDev.dataExErr, - rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, - gNfcDev.rxBuf.rfBuf, - sizeof(gNfcDev.rxBuf.rfBuf), - &gNfcDev.rxLen)); - - /* If set Sleep was successful keep restore the Sleep request signal */ - gNfcDev.dataExErr = ERR_SLEEP_REQ; - } -#endif /* RFAL_FEATURE_LISTEN_MODE */ - } - - return gNfcDev.dataExErr; -} - -/*! - ****************************************************************************** - * \brief Poller Technology Detection - * - * This method implements the Technology Detection / Poll for different - * device technologies. - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcPollTechDetetection(void) { - ReturnCode err; - - err = ERR_NONE; - - /* Suppress warning when specific RFAL features have been disabled */ - NO_WARNING(err); - - /*******************************************************************************/ - /* AP2P Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_AP2P) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_AP2P) != 0U)) { -#if RFAL_FEATURE_NFC_DEP - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR( - err, - rfalSetMode(RFAL_MODE_POLL_ACTIVE_P2P, gNfcDev.disc.ap2pBR, gNfcDev.disc.ap2pBR)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - rfalSetFDTListen(RFAL_FDT_LISTEN_AP2P_POLLER); - rfalSetFDTPoll(RFAL_TIMING_NONE); - rfalSetGT(RFAL_GT_AP2P_ADJUSTED); - EXIT_ON_ERR(err, rfalFieldOnAndStartGT()); /* Turns the Field On and starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_AP2P; - - err = rfalNfcNfcDepActivate( - gNfcDev.devList, RFAL_NFCDEP_COMM_ACTIVE, NULL, 0); /* Poll for NFC-A devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_AP2P; - - gNfcDev.devList->type = RFAL_NFC_LISTEN_TYPE_AP2P; - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_NFCDEP; - gNfcDev.devCnt++; - - return ERR_NONE; - } - - gNfcDev.isTechInit = false; - rfalFieldOff(); - } - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFC_DEP */ - } - - /*******************************************************************************/ - /* Passive NFC-A Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_A) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_A) != 0U)) { -#if RFAL_FEATURE_NFCA - - rfalNfcaSensRes sensRes; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcaPollerInitialize()); /* Initialize RFAL for NFC-A */ - EXIT_ON_ERR(err, rfalFieldOnAndStartGT()); /* Turns the Field On and starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcaPollerTechnologyDetection( - gNfcDev.disc.compMode, &sensRes); /* Poll for NFC-A devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_A; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_A; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCA */ - } - - /*******************************************************************************/ - /* Passive NFC-B Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_B) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_B) != 0U)) { -#if RFAL_FEATURE_NFCB - - rfalNfcbSensbRes sensbRes; - uint8_t sensbResLen; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcbPollerInitialize()); /* Initialize RFAL for NFC-B */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcbPollerTechnologyDetection( - gNfcDev.disc.compMode, &sensbRes, &sensbResLen); /* Poll for NFC-B devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_B; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_B; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCB */ - } - - /*******************************************************************************/ - /* Passive NFC-F Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_F) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_F) != 0U)) { -#if RFAL_FEATURE_NFCF - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR( - err, - rfalNfcfPollerInitialize(gNfcDev.disc.nfcfBR)); /* Initialize RFAL for NFC-F */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcfPollerCheckPresence(); /* Poll for NFC-F devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_F; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_F; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCF */ - } - - /*******************************************************************************/ - /* Passive NFC-V Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_V) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_V) != 0U)) { -#if RFAL_FEATURE_NFCV - - rfalNfcvInventoryRes invRes; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcvPollerInitialize()); /* Initialize RFAL for NFC-V */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcvPollerCheckPresence(&invRes); /* Poll for NFC-V devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_V; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_V; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCV */ - } - - /*******************************************************************************/ - /* Passive Proprietary Technology ST25TB */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_ST25TB) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_ST25TB) != 0U)) { -#if RFAL_FEATURE_ST25TB - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalSt25tbPollerInitialize()); /* Initialize RFAL for NFC-V */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalSt25tbPollerCheckPresence(NULL); /* Poll for ST25TB devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_ST25TB; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_ST25TB; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_ST25TB */ - } - - return ERR_NONE; -} - -/*! - ****************************************************************************** - * \brief Poller Collision Resolution - * - * This method implements the Collision Resolution on all technologies that - * have been detected before. - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcPollCollResolution(void) { - uint8_t i; - static uint8_t devCnt; - ReturnCode err; - - err = ERR_NONE; - i = 0; - - /* Suppress warning when specific RFAL features have been disabled */ - NO_WARNING(err); - NO_WARNING(devCnt); - NO_WARNING(i); - - /* Check if device limit has been reached */ - if(gNfcDev.devCnt >= gNfcDev.disc.devLimit) { - return ERR_NONE; - } - - /*******************************************************************************/ - /* NFC-A Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCA - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_A) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_A) != - 0U)) /* If a NFC-A device was found/detected, perform Collision Resolution */ - { - static rfalNfcaListenDevice nfcaDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcaPollerInitialize()); /* Initialize RFAL for NFC-A */ - EXIT_ON_ERR(err, rfalFieldOnAndStartGT()); /* Turns the Field On and starts GT timer */ - - gNfcDev.isTechInit = true; /* Technology has been initialized */ - gNfcDev.isOperOngoing = false; /* No operation currently ongoing */ - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - if(!gNfcDev.isOperOngoing) { - EXIT_ON_ERR( - err, - rfalNfcaPollerStartFullCollisionResolution( - gNfcDev.disc.compMode, - (gNfcDev.disc.devLimit - gNfcDev.devCnt), - nfcaDevList, - &devCnt)); - - gNfcDev.isOperOngoing = true; - return ERR_BUSY; - } - - err = rfalNfcaPollerGetFullCollisionResolutionStatus(); - if(err != ERR_BUSY) { - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_A; - - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfca list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCA; - gNfcDev.devList[gNfcDev.devCnt].dev.nfca = nfcaDevList[i]; - gNfcDev.devCnt++; - } - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCA */ - - /*******************************************************************************/ - /* NFC-B Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCB - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_B) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_B) != - 0U)) /* If a NFC-B device was found/detected, perform Collision Resolution */ - { - rfalNfcbListenDevice nfcbDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcbPollerInitialize()); /* Initialize RFAL for NFC-B */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_B; - - err = rfalNfcbPollerCollisionResolution( - gNfcDev.disc.compMode, (gNfcDev.disc.devLimit - gNfcDev.devCnt), nfcbDevList, &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcb list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCB; - gNfcDev.devList[gNfcDev.devCnt].dev.nfcb = nfcbDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCB*/ - - /*******************************************************************************/ - /* NFC-F Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCF - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_F) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_F) != - 0U)) /* If a NFC-F device was found/detected, perform Collision Resolution */ - { - rfalNfcfListenDevice nfcfDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR( - err, - rfalNfcfPollerInitialize(gNfcDev.disc.nfcfBR)); /* Initialize RFAL for NFC-F */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_F; - - err = rfalNfcfPollerCollisionResolution( - gNfcDev.disc.compMode, (gNfcDev.disc.devLimit - gNfcDev.devCnt), nfcfDevList, &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcf list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCF; - gNfcDev.devList[gNfcDev.devCnt].dev.nfcf = nfcfDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCF */ - - /*******************************************************************************/ - /* NFC-V Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCV - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_V) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_V) != - 0U)) /* If a NFC-V device was found/detected, perform Collision Resolution */ - { - rfalNfcvListenDevice nfcvDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcvPollerInitialize()); /* Initialize RFAL for NFC-V */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_V; - - err = rfalNfcvPollerCollisionResolution( - RFAL_COMPLIANCE_MODE_NFC, - (gNfcDev.disc.devLimit - gNfcDev.devCnt), - nfcvDevList, - &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcf list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCV; - gNfcDev.devList[gNfcDev.devCnt].dev.nfcv = nfcvDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* ST25TB Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_ST25TB - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_ST25TB) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_ST25TB) != - 0U)) /* If a ST25TB device was found/detected, perform Collision Resolution */ - { - rfalSt25tbListenDevice st25tbDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalSt25tbPollerInitialize()); /* Initialize RFAL for ST25TB */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_ST25TB; - - err = rfalSt25tbPollerCollisionResolution( - (gNfcDev.disc.devLimit - gNfcDev.devCnt), st25tbDevList, &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcf list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_ST25TB; - gNfcDev.devList[gNfcDev.devCnt].dev.st25tb = st25tbDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_ST25TB */ - - return ERR_NONE; /* All technologies have been performed */ -} - -/*! - ****************************************************************************** - * \brief Poller Activation - * - * This method Activates a given device according to it's type and - * protocols supported - * - * \param[in] devIt : device's position on the list to be activated - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcPollActivation(uint8_t devIt) { - ReturnCode err; - - err = ERR_NONE; - - /* Suppress warning when specific RFAL features have been disabled */ - NO_WARNING(err); - - if(devIt > gNfcDev.devCnt) { - return ERR_WRONG_STATE; - } - - switch(gNfcDev.devList[devIt].type) { - /*******************************************************************************/ - /* AP2P Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFC_DEP - case RFAL_NFC_LISTEN_TYPE_AP2P: - /* Activation has already been performed (ATR_REQ) */ - - gNfcDev.devList[devIt].nfcid = - gNfcDev.devList[devIt].proto.nfcDep.activation.Target.ATR_RES.NFCID3; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - break; -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - /* Passive NFC-A Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCA - case RFAL_NFC_LISTEN_TYPE_NFCA: - - if(!gNfcDev.isTechInit) { - rfalNfcaPollerInitialize(); - gNfcDev.isTechInit = true; - gNfcDev.isOperOngoing = false; - return ERR_BUSY; - } - - if(gNfcDev.devList[devIt].dev.nfca.isSleep) /* Check if desired device is in Sleep */ - { - rfalNfcaSensRes sensRes; - rfalNfcaSelRes selRes; - - if(!gNfcDev.isOperOngoing) { - /* Wake up all cards */ - EXIT_ON_ERR( - err, rfalNfcaPollerCheckPresence(RFAL_14443A_SHORTFRAME_CMD_WUPA, &sensRes)); - gNfcDev.isOperOngoing = true; - } else { - /* Select specific device */ - EXIT_ON_ERR( - err, - rfalNfcaPollerSelect( - gNfcDev.devList[devIt].dev.nfca.nfcId1, - gNfcDev.devList[devIt].dev.nfca.nfcId1Len, - &selRes)); - gNfcDev.devList[devIt].dev.nfca.isSleep = false; - gNfcDev.isOperOngoing = false; - } - return ERR_BUSY; - } - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfca.nfcId1; - gNfcDev.devList[devIt].nfcidLen = gNfcDev.devList[devIt].dev.nfca.nfcId1Len; - - /*******************************************************************************/ - /* Perform protocol specific activation */ - switch(gNfcDev.devList[devIt].dev.nfca.type) { - /*******************************************************************************/ - case RFAL_NFCA_T1T: - - /* No further activation needed for T1T (RID already performed) */ - - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfca.ridRes.uid; - gNfcDev.devList[devIt].nfcidLen = RFAL_T1T_UID_LEN; - - gNfcDev.devList[devIt].rfInterface = RFAL_NFC_INTERFACE_RF; - break; - - case RFAL_NFCA_T2T: - - /* No further activation needed for a T2T */ - - gNfcDev.devList[devIt].rfInterface = RFAL_NFC_INTERFACE_RF; - break; - - /*******************************************************************************/ - case RFAL_NFCA_T4T: /* Device supports ISO-DEP */ - -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_POLL - if(!gNfcDev.isOperOngoing) { - /* Perform ISO-DEP (ISO14443-4) activation: RATS and PPS if supported */ - rfalIsoDepInitialize(); - EXIT_ON_ERR( - err, - rfalIsoDepPollAStartActivation( - (rfalIsoDepFSxI)RFAL_ISODEP_FSDI_DEFAULT, - RFAL_ISODEP_NO_DID, - gNfcDev.disc.maxBR, - &gNfcDev.devList[devIt].proto.isoDep)); - - gNfcDev.isOperOngoing = true; - return ERR_BUSY; - } - - err = rfalIsoDepPollAGetActivationStatus(); - if(err != ERR_NONE) { - return err; - } - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_ISODEP; /* NFC-A T4T device activated */ -#else - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* No ISO-DEP supported activate using RF interface */ -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - break; - - /*******************************************************************************/ - case RFAL_NFCA_T4T_NFCDEP: /* Device supports both T4T and NFC-DEP */ - case RFAL_NFCA_NFCDEP: /* Device supports NFC-DEP */ - -#if RFAL_FEATURE_NFC_DEP - /* Perform NFC-DEP (P2P) activation: ATR and PSL if supported */ - EXIT_ON_ERR( - err, - rfalNfcNfcDepActivate(&gNfcDev.devList[devIt], RFAL_NFCDEP_COMM_PASSIVE, NULL, 0)); - - gNfcDev.devList[devIt].nfcid = - gNfcDev.devList[devIt].proto.nfcDep.activation.Target.ATR_RES.NFCID3; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_NFCDEP; /* NFC-A P2P device activated */ -#else - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* No NFC-DEP supported activate using RF interface */ -#endif /* RFAL_FEATURE_NFC_DEP */ - break; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - break; -#endif /* RFAL_FEATURE_NFCA */ - - /*******************************************************************************/ - /* Passive NFC-B Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCB - case RFAL_NFC_LISTEN_TYPE_NFCB: - - if(!gNfcDev.isTechInit) { - rfalNfcbPollerInitialize(); - gNfcDev.isTechInit = true; - gNfcDev.isOperOngoing = false; - return ERR_BUSY; - } - - if(gNfcDev.devList[devIt].dev.nfcb.isSleep) /* Check if desired device is in Sleep */ - { - rfalNfcbSensbRes sensbRes; - uint8_t sensbResLen; - - /* Wake up all cards. SENSB_RES may return collision but the NFCID0 is available to explicitly select NFC-B card via ATTRIB; so error will be ignored here */ - rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_ALLB_REQ, RFAL_NFCB_SLOT_NUM_1, &sensbRes, &sensbResLen); - } - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfcb.sensbRes.nfcid0; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCB_NFCID0_LEN; - -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_POLL - /* Check if device supports ISO-DEP (ISO14443-4) */ - if((gNfcDev.devList[devIt].dev.nfcb.sensbRes.protInfo.FsciProType & - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK) != 0U) { - if(!gNfcDev.isOperOngoing) { - rfalIsoDepInitialize(); - /* Perform ISO-DEP (ISO14443-4) activation: ATTRIB */ - EXIT_ON_ERR( - err, - rfalIsoDepPollBStartActivation( - (rfalIsoDepFSxI)RFAL_ISODEP_FSDI_DEFAULT, - RFAL_ISODEP_NO_DID, - gNfcDev.disc.maxBR, - 0x00, - &gNfcDev.devList[devIt].dev.nfcb, - NULL, - 0, - &gNfcDev.devList[devIt].proto.isoDep)); - - gNfcDev.isOperOngoing = true; - return ERR_BUSY; - } - - err = rfalIsoDepPollBGetActivationStatus(); - if(err != ERR_NONE) { - return err; - } - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_ISODEP; /* NFC-B T4T device activated */ - break; - } - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* NFC-B device activated */ - break; - -#endif /* RFAL_FEATURE_NFCB */ - - /*******************************************************************************/ - /* Passive NFC-F Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCF - case RFAL_NFC_LISTEN_TYPE_NFCF: - - rfalNfcfPollerInitialize(gNfcDev.disc.nfcfBR); - -#if RFAL_FEATURE_NFC_DEP - if(rfalNfcfIsNfcDepSupported(&gNfcDev.devList[devIt].dev.nfcf)) { - /* Perform NFC-DEP (P2P) activation: ATR and PSL if supported */ - EXIT_ON_ERR( - err, - rfalNfcNfcDepActivate(&gNfcDev.devList[devIt], RFAL_NFCDEP_COMM_PASSIVE, NULL, 0)); - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = - gNfcDev.devList[devIt].proto.nfcDep.activation.Target.ATR_RES.NFCID3; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_NFCDEP; /* NFC-F P2P device activated */ - break; - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfcf.sensfRes.NFCID2; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCF_NFCID2_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* NFC-F T3T device activated */ - break; -#endif /* RFAL_FEATURE_NFCF */ - - /*******************************************************************************/ - /* Passive NFC-V Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCV - case RFAL_NFC_LISTEN_TYPE_NFCV: - - rfalNfcvPollerInitialize(); - - /* No specific activation needed for a T5T */ - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfcv.InvRes.UID; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCV_UID_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* NFC-V T5T device activated */ - break; -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* Passive ST25TB Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_ST25TB - case RFAL_NFC_LISTEN_TYPE_ST25TB: - - rfalSt25tbPollerInitialize(); - - /* No specific activation needed for a ST25TB */ - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.st25tb.UID; - gNfcDev.devList[devIt].nfcidLen = RFAL_ST25TB_UID_LEN; - - gNfcDev.devList[devIt].rfInterface = RFAL_NFC_INTERFACE_RF; /* ST25TB device activated */ - break; -#endif /* RFAL_FEATURE_ST25TB */ - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - - gNfcDev.activeDev = &gNfcDev.devList[devIt]; /* Assign active device to be used further on */ - return ERR_NONE; -} - -/*! - ****************************************************************************** - * \brief Listener Activation - * - * This method handles the listen mode Activation according to the different - * protocols the Reader/Initiator performs - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_PROTO : Unexpected frame received - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -#if RFAL_FEATURE_LISTEN_MODE -static ReturnCode rfalNfcListenActivation(void) { - bool isDataRcvd; - ReturnCode ret; - rfalLmState lmSt; - rfalBitRate bitRate; -#if RFAL_FEATURE_NFC_DEP - uint8_t hdrLen; - - /* Set the header length in NFC-A */ - hdrLen = (RFAL_NFCDEP_SB_LEN + RFAL_NFCDEP_LEN_LEN); -#endif /* RFAL_FEATURE_NFC_DEP */ - - lmSt = rfalListenGetState(&isDataRcvd, &bitRate); - - switch(lmSt) { -#if RFAL_FEATURE_NFCA - /*******************************************************************************/ - case RFAL_LM_STATE_ACTIVE_A: /* NFC-A CE activation */ - case RFAL_LM_STATE_ACTIVE_Ax: - - if(isDataRcvd) /* Check if Reader/Initator has sent some data */ - { - /* Check if received data is a Sleep request */ - if(rfalNfcaListenerIsSleepReq( - gNfcDev.rxBuf.rfBuf, - rfalConvBitsToBytes(gNfcDev.rxLen))) /* Check if received data is a SLP_REQ */ - { - /* Set the Listen Mode in Sleep state */ - EXIT_ON_ERR( - ret, - rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, - gNfcDev.rxBuf.rfBuf, - sizeof(gNfcDev.rxBuf.rfBuf), - &gNfcDev.rxLen)); - } - - else if(gNfcDev.disc.activate_after_sak) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCA; - rfalListenSetState(RFAL_LM_STATE_ACTIVE_A); - return ERR_NONE; - } -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_LISTEN - /* Check if received data is a valid RATS */ - else if(rfalIsoDepIsRats( - gNfcDev.rxBuf.rfBuf, (uint8_t)rfalConvBitsToBytes(gNfcDev.rxLen))) { - rfalIsoDepAtsParam atsParam; - rfalIsoDepListenActvParam rxParam; - - /* Set ATS parameters */ - atsParam.fsci = (uint8_t)RFAL_ISODEP_DEFAULT_FSCI; - atsParam.fwi = RFAL_ISODEP_DEFAULT_FWI; - atsParam.sfgi = RFAL_ISODEP_DEFAULT_SFGI; - atsParam.didSupport = false; - atsParam.ta = RFAL_ISODEP_ATS_TA_SAME_D; - atsParam.hb = NULL; - atsParam.hbLen = 0; - - /* Set Rx parameters */ - rxParam.rxBuf = - (rfalIsoDepBufFormat*)&gNfcDev.rxBuf - .isoDepBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - rxParam.rxLen = &gNfcDev.rxLen; - rxParam.isoDepDev = &gNfcDev.devList->proto.isoDep; - rxParam.isRxChaining = &gNfcDev.isRxChaining; - - rfalListenSetState(RFAL_LM_STATE_CARDEMU_4A); /* Set next state CE T4T */ - rfalIsoDepInitialize(); /* Initialize ISO-DEP layer to handle ISO14443-a activation / RATS */ - - /* Set ISO-DEP layer to digest RATS and handle activation */ - EXIT_ON_ERR( - ret, - rfalIsoDepListenStartActivation( - &atsParam, NULL, gNfcDev.rxBuf.rfBuf, gNfcDev.rxLen, rxParam)); - } -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -#if RFAL_FEATURE_NFC_DEP - - /* Check if received data is a valid ATR_REQ */ - else if(rfalNfcDepIsAtrReq( - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen), - gNfcDev.devList->nfcid)) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCA; - EXIT_ON_ERR( - ret, - rfalNfcNfcDepActivate( - gNfcDev.devList, - RFAL_NFCDEP_COMM_PASSIVE, - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen))); - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - else { - return ERR_PROTO; - } - } - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_LISTEN - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_4A: /* T4T ISO-DEP activation */ - - ret = rfalIsoDepListenGetActivationStatus(); - if(ret == ERR_NONE) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCA; - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_ISODEP; - gNfcDev.devList->nfcid = NULL; - gNfcDev.devList->nfcidLen = 0; - } - return ret; -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_F: /* NFC-F CE activation */ - - if(isDataRcvd) /* Wait for the first received data */ - { -#if RFAL_FEATURE_NFC_DEP - /* Set the header length in NFC-F */ - hdrLen = RFAL_NFCDEP_LEN_LEN; - - if(rfalNfcDepIsAtrReq( - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen), - gNfcDev.devList->nfcid)) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCF; - EXIT_ON_ERR( - ret, - rfalNfcNfcDepActivate( - gNfcDev.devList, - RFAL_NFCDEP_COMM_PASSIVE, - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen))); - } else -#endif /* RFAL_FEATURE_NFC_DEP */ - { - rfalListenSetState( - RFAL_LM_STATE_CARDEMU_3); /* First data already received - set T3T CE */ - } - } - return ERR_BUSY; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_3: /* T3T activated */ - - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCF; - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_RF; - gNfcDev.devList->nfcid = NULL; - gNfcDev.devList->nfcidLen = 0; - - return ERR_NONE; - -#if RFAL_FEATURE_NFC_DEP - /*******************************************************************************/ - case RFAL_LM_STATE_TARGET_A: /* NFC-DEP activation */ - case RFAL_LM_STATE_TARGET_F: - - ret = rfalNfcDepListenGetActivationStatus(); - if(ret == ERR_NONE) { - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_NFCDEP; - gNfcDev.devList->nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - } - return ret; -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - case RFAL_LM_STATE_IDLE: /* AP2P activation */ - if(isDataRcvd) /* Check if Reader/Initator has sent some data */ - { - if((gNfcDev.lmMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) /* Check if AP2P is enabled */ - { -#if RFAL_FEATURE_NFC_DEP - /* Calculate the header length in NFC-A or NFC-F mode*/ - hdrLen = - ((bitRate == RFAL_BR_106) ? (RFAL_NFCDEP_SB_LEN + RFAL_NFCDEP_LEN_LEN) : - RFAL_NFCDEP_LEN_LEN); - - if(rfalNfcDepIsAtrReq( - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen), - NULL)) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_AP2P; - rfalSetMode((RFAL_MODE_LISTEN_ACTIVE_P2P), bitRate, bitRate); - EXIT_ON_ERR( - ret, - rfalNfcNfcDepActivate( - gNfcDev.devList, - RFAL_NFCDEP_COMM_ACTIVE, - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen))); - } else -#endif /* RFAL_FEATURE_NFC_DEP */ - { - return ERR_PROTO; - } - } - } - return ERR_BUSY; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_A: - case RFAL_LM_STATE_READY_Ax: - case RFAL_LM_STATE_SLEEP_A: - case RFAL_LM_STATE_SLEEP_AF: - return ERR_BUSY; - - /*******************************************************************************/ - case RFAL_LM_STATE_POWER_OFF: - return ERR_LINK_LOSS; - - default: /* Wait for activation */ - break; - } - - return ERR_INTERNAL; -} -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -/*! - ****************************************************************************** - * \brief Poller NFC DEP Activate - * - * This method performs NFC-DEP Activation - * - * \param[in] device : device info - * \param[in] commMode : communication mode (Passive/Active) - * \param[in] atrReq : received ATR_REQ - * \param[in] atrReqLen : received ATR_REQ size - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -#if RFAL_FEATURE_NFC_DEP -static ReturnCode rfalNfcNfcDepActivate( - rfalNfcDevice* device, - rfalNfcDepCommMode commMode, - const uint8_t* atrReq, - uint16_t atrReqLen) { - rfalNfcDepAtrParam initParam; - - /* Suppress warnings if Listen mode is disabled */ - NO_WARNING(atrReq); - NO_WARNING(atrReqLen); - - /* If we are in Poll mode */ - if(rfalNfcIsRemDevListener(device->type)) { - /*******************************************************************************/ - /* If Passive F use the NFCID2 retrieved from SENSF */ - if(device->type == RFAL_NFC_LISTEN_TYPE_NFCF) { - initParam.nfcid = device->dev.nfcf.sensfRes.NFCID2; - initParam.nfcidLen = RFAL_NFCF_NFCID2_LEN; - } else { - initParam.nfcid = gNfcDev.disc.nfcid3; - initParam.nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - } - - initParam.BS = RFAL_NFCDEP_Bx_NO_HIGH_BR; - initParam.BR = RFAL_NFCDEP_Bx_NO_HIGH_BR; - initParam.DID = RFAL_NFCDEP_DID_NO; - initParam.NAD = RFAL_NFCDEP_NAD_NO; - initParam.LR = RFAL_NFCDEP_LR_254; - initParam.GB = gNfcDev.disc.GB; - initParam.GBLen = gNfcDev.disc.GBLen; - initParam.commMode = commMode; - initParam.operParam = - (RFAL_NFCDEP_OPER_FULL_MI_EN | RFAL_NFCDEP_OPER_EMPTY_DEP_DIS | - RFAL_NFCDEP_OPER_ATN_EN | RFAL_NFCDEP_OPER_RTOX_REQ_EN); - - rfalNfcDepInitialize(); - /* Perform NFC-DEP (P2P) activation: ATR and PSL if supported */ - return rfalNfcDepInitiatorHandleActivation( - &initParam, gNfcDev.disc.maxBR, &device->proto.nfcDep); - } - /* If we are in Listen mode */ -#if RFAL_FEATURE_LISTEN_MODE - else if(rfalNfcIsRemDevPoller(device->type)) { - rfalNfcDepListenActvParam actvParams; - rfalNfcDepTargetParam targetParam; - - ST_MEMCPY(targetParam.nfcid3, (uint8_t*)gNfcDev.disc.nfcid3, RFAL_NFCDEP_NFCID3_LEN); - targetParam.bst = RFAL_NFCDEP_Bx_NO_HIGH_BR; - targetParam.brt = RFAL_NFCDEP_Bx_NO_HIGH_BR; - targetParam.to = RFAL_NFCDEP_WT_TRG_MAX_L13; /* [LLCP] 1.3 6.2.1 */ - targetParam.ppt = rfalNfcDepLR2PP(RFAL_NFCDEP_LR_254); - if(gNfcDev.disc.GBLen >= RFAL_NFCDEP_GB_MAX_LEN) { - return ERR_PARAM; - } - targetParam.GBtLen = gNfcDev.disc.GBLen; - if(gNfcDev.disc.GBLen > 0U) { - ST_MEMCPY(targetParam.GBt, gNfcDev.disc.GB, gNfcDev.disc.GBLen); - } - targetParam.operParam = - (RFAL_NFCDEP_OPER_FULL_MI_EN | RFAL_NFCDEP_OPER_EMPTY_DEP_DIS | - RFAL_NFCDEP_OPER_ATN_EN | RFAL_NFCDEP_OPER_RTOX_REQ_EN); - targetParam.commMode = commMode; - - /* Set activation buffer (including header) for NFC-DEP */ - actvParams.rxBuf = - (rfalNfcDepBufFormat*)&gNfcDev.rxBuf - .nfcDepBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - actvParams.rxLen = &gNfcDev.rxLen; - actvParams.isRxChaining = &gNfcDev.isRxChaining; - actvParams.nfcDepDev = &gNfcDev.devList->proto.nfcDep; - - rfalListenSetState( - ((device->type == RFAL_NFC_POLL_TYPE_NFCA) ? RFAL_LM_STATE_TARGET_A : - RFAL_LM_STATE_TARGET_F)); - - rfalNfcDepInitialize(); - /* Perform NFC-DEP (P2P) activation: send ATR_RES and handle activation */ - return rfalNfcDepListenStartActivation(&targetParam, atrReq, atrReqLen, actvParams); - } -#endif /* RFAL_FEATURE_LISTEN_MODE */ - - else { - return ERR_INTERNAL; - } -} -#endif /* RFAL_FEATURE_NFC_DEP */ - -/*! - ****************************************************************************** - * \brief Poller NFC Deactivate - * - * This method Deactivates the device if a deactivation procedure exists - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcDeactivation(void) { - /* Check if a device has been activated */ - if(gNfcDev.activeDev != NULL) { - if(rfalNfcIsRemDevListener( - gNfcDev.activeDev->type)) /* Listen mode no additional deactivation to be performed*/ - { -#ifndef RFAL_NFC_SKIP_DEACT - switch(gNfcDev.activeDev->rfInterface) { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - break; /* No specific deactivation to be performed */ - - /*******************************************************************************/ -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_POLL - case RFAL_NFC_INTERFACE_ISODEP: - rfalIsoDepDeselect(); /* Send a Deselect to device */ - break; -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - - /*******************************************************************************/ -#if RFAL_FEATURE_NFC_DEP - case RFAL_NFC_INTERFACE_NFCDEP: - switch(gNfcDev.activeDev->type) { - case RFAL_NFC_LISTEN_TYPE_AP2P: - rfalNfcDepRLS(); /* Send a Release to device */ - break; - default: - rfalNfcDepDSL(); /* Send a Deselect to device */ - break; - } - break; -#endif /* RFAL_FEATURE_NFC_DEP */ - - default: - return ERR_REQUEST; - } -#endif /* RFAL_NFC_SKIP_DEACT */ - } - } - -#if RFAL_FEATURE_WAKEUP_MODE - rfalWakeUpModeStop(); -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#if RFAL_FEATURE_LISTEN_MODE - rfalListenStop(); -#else - rfalFieldOff(); -#endif - - gNfcDev.activeDev = NULL; - return ERR_NONE; -} diff --git a/lib/ST25RFAL002/source/rfal_nfcDep.c b/lib/ST25RFAL002/source/rfal_nfcDep.c deleted file mode 100644 index ad851ac5d2..0000000000 --- a/lib/ST25RFAL002/source/rfal_nfcDep.c +++ /dev/null @@ -1,2730 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: NFCC firmware - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcDep.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-DEP protocol - * - * NFC-DEP is also known as NFCIP - Near Field Communication - * Interface and Protocol - * - * This implementation was based on the following specs: - * - NFC Forum Digital 1.1 - * - ECMA 340 3rd Edition 2013 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcDep.h" -#include "rfal_nfcf.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#if RFAL_FEATURE_NFC_DEP - -/* Check for valid Block/Payload length Digital 2.0 Table 90*/ -#if((RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 64) && (RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 128) && \ - (RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 192) && (RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 254)) -#error \ - " RFAL: Invalid NFC-DEP Block Max length. Please change RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN. " -#endif - -/* Check for valid PDU length */ -#if((RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN < RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN)) -#error " RFAL: Invalid NFC-DEP PDU Max length. Please change RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN. " -#endif - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define NFCIP_ATR_RETRY_MAX 2U /*!< Max consecutive retrys of an ATR REQ with transm error*/ - -#define NFCIP_PSLPAY_LEN (2U) /*!< PSL Payload length (BRS + FSL) */ -#define NFCIP_PSLREQ_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< PSL REQ length (incl LEN) */ -#define NFCIP_PSLRES_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< PSL RES length (incl LEN) */ - -#define NFCIP_ATRREQ_BUF_LEN \ - (RFAL_NFCDEP_ATRREQ_MAX_LEN + RFAL_NFCDEP_LEN_LEN) /*!< ATR REQ max length (incl LEN) */ -#define NFCIP_ATRRES_BUF_LEN \ - (RFAL_NFCDEP_ATRRES_MAX_LEN + RFAL_NFCDEP_LEN_LEN) /*!< ATR RES max length (incl LEN) */ - -#define NFCIP_RLSREQ_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< RLS REQ length (incl LEN) */ -#define NFCIP_RLSRES_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< RSL RES length (incl LEN) */ -#define NFCIP_RLSRES_MIN \ - (2U + RFAL_NFCDEP_LEN_LEN) /*!< Minimum length for a RLS RES (incl LEN) */ - -#define NFCIP_DSLREQ_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< DSL REQ length (incl LEN) */ -#define NFCIP_DSLRES_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< DSL RES length (incl LEN) */ -#define NFCIP_DSLRES_MIN \ - (2U + RFAL_NFCDEP_LEN_LEN) /*!< Minimum length for a DSL RES (incl LEN) */ - -#define NFCIP_DSLRES_MAX_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< Maximum length for a DSL RES (incl LEN) */ -#define NFCIP_RLSRES_MAX_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< Minimum length for a RLS RES (incl LEN) */ -#define NFCIP_TARGET_RES_MAX \ - (MAX(NFCIP_RLSRES_MAX_LEN, NFCIP_DSLRES_MAX_LEN)) /*!< Max target control res length */ - -#define NFCIP_NO_FWT RFAL_FWT_NONE /*!< No FWT value - Target Mode */ -#define NFCIP_INIT_MIN_RTOX 1U /*!< Minimum RTOX value Digital 1.0 14.8.4.1 */ -#define NFCIP_INIT_MAX_RTOX 59U /*!< Maximum RTOX value Digital 1.0 14.8.4.1 */ - -#define NFCIP_TARG_MIN_RTOX 1U /*!< Minimum target RTOX value Digital 1.0 14.8.4.1 */ -#define NFCIP_TARG_MAX_RTOX 59U /*!< Maximum target RTOX value Digital 1.0 14.8.4.1 */ - -#define NFCIP_TRECOV 1280U /*!< Digital 1.0 A.10 Trecov */ - -#define NFCIP_TIMEOUT_ADJUSTMENT \ - 3072U /*!< Timeout Adjustment to compensate timing from end of Tx to end of frame */ -#define NFCIP_RWT_ACTIVATION \ - (0x1000001U + \ - NFCIP_TIMEOUT_ADJUSTMENT) /*!< Digital 2.2 B.11 RWT ACTIVATION 2^24 + RWT Delta + Adjustment*/ -#define NFCIP_RWT_ACM_ACTIVATION \ - (0x200001U + \ - NFCIP_TIMEOUT_ADJUSTMENT) /*!< Digital 2.2 B.11 RWT ACTIVATION 2^21 + RWT Delta + Adjustment*/ - -#define RFAL_NFCDEP_HEADER_PAD \ - (RFAL_NFCDEP_DEPREQ_HEADER_LEN - \ - RFAL_NFCDEP_LEN_MIN) /*!< Difference between expected rcvd header len and max foreseen */ - -#ifndef RFAL_NFCDEP_MAX_TX_RETRYS -#define RFAL_NFCDEP_MAX_TX_RETRYS \ - (uint8_t)3U /*!< Number of retransmit retyrs */ -#endif /* RFAL_NFCDEP_MAX_TX_RETRYS */ - -#ifndef RFAL_NFCDEP_TO_RETRYS -#define RFAL_NFCDEP_TO_RETRYS \ - (uint8_t)3U /*!< Number of retrys for Timeout */ -#endif /* RFAL_NFCDEP_TO_RETRYS */ - -#ifndef RFAL_NFCDEP_MAX_RTOX_RETRYS -#define RFAL_NFCDEP_MAX_RTOX_RETRYS \ - (uint8_t)10U /*!< Number of retrys for RTOX Digital 2.0 17.12.4.3 */ -#endif /* RFAL_NFCDEP_MAX_RTOX_RETRYS */ - -#ifndef RFAL_NFCDEP_MAX_NACK_RETRYS -#define RFAL_NFCDEP_MAX_NACK_RETRYS \ - (uint8_t)3U /*!< Number of retrys for NACK */ -#endif /* RFAL_NFCDEP_MAX_NACK_RETRYS */ - -#ifndef RFAL_NFCDEP_MAX_ATN_RETRYS -#define RFAL_NFCDEP_MAX_ATN_RETRYS \ - (uint8_t)3U /*!< Number of retrys for ATN */ -#endif /* RFAL_NFCDEP_MAX_ATN_RETRYS */ - -#define NFCIP_MIN_TXERROR_LEN \ - 4U /*!< Minimum frame length with error to be ignored Digital 1.0 14.12.5.4 */ - -#define NFCIP_REQ (uint8_t)0xD4U /*!= NFCIP_ST_INIT_IDLE) && \ - ((st) <= \ - NFCIP_ST_INIT_RLS)) /*!< Checks if module is set as Initiator */ -#define nfcipIsTarget(st) \ - (!nfcipIsInitiator( \ - st)) /*!< Checks if module is set as Target */ - -#define nfcipIsBRAllowed(br, mBR) \ - (((1U << (br)) & (mBR)) != \ - 0U) /*!< Checks bit rate is allowed by given mask */ - -#define nfcipIsEmptyDEPEnabled(op) \ - (!nfcipIsEmptyDEPDisabled( \ - op)) /*!< Checks if empty payload is allowed by operation config NCI 1.0 Table 81 */ -#define nfcipIsEmptyDEPDisabled(op) \ - (((op)&RFAL_NFCDEP_OPER_EMPTY_DEP_DIS) != \ - 0U) /*!< Checks if empty payload is not allowed by operation config NCI 1.0 Table 81 */ - -#define nfcipIsRTOXReqEnabled(op) \ - (!nfcipIsRTOXReqDisabled( \ - op)) /*!< Checks if send a RTOX_REQ is allowed by operation config NCI 1.0 Table 81 */ -#define nfcipIsRTOXReqDisabled(op) \ - (((op)&RFAL_NFCDEP_OPER_RTOX_REQ_DIS) != \ - 0U) /*!< Checks if send a RTOX_REQ is not allowed by operation config NCI 1.0 Table 81 */ - -/*! Checks if isDeactivating callback is set and calls it, otherwise returns false */ -#define nfcipIsDeactivationPending() \ - ((gNfcip.isDeactivating == NULL) ? false : gNfcip.isDeactivating()) - -/*! Returns the RWT Activation according to the current communication mode */ -#define nfcipRWTActivation() \ - ((gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_ACTIVE) ? NFCIP_RWT_ACM_ACTIVATION : \ - NFCIP_RWT_ACTIVATION) - -#define nfcipRTOXAdjust(v) \ - ((v) - ((v) >> 3)) /*!< Adjust RTOX timer value to a percentage of the total, current 88% */ - -/*******************************************************************************/ - -// timerPollTimeoutValue is necessary after timerCalculateTimeout so that system will wake up upon timer timeout. -#define nfcipTimerStart(timer, time_ms) \ - do { \ - platformTimerDestroy(timer); \ - (timer) = platformTimerCreate((uint16_t)(time_ms)); \ - } while(0) /*!< Configures and starts the RTOX timer */ -#define nfcipTimerisExpired(timer) \ - platformTimerIsExpired(timer) /*!< Checks RTOX timer has expired */ -#define nfcipTimerDestroy(timer) \ - platformTimerDestroy(timer) /*!< Destroys RTOX timer */ - -#define nfcipLogE(...) /*!< Macro for the error log method */ -#define nfcipLogW(...) /*!< Macro for the warning log method */ -#define nfcipLogI(...) /*!< Macro for the info log method */ -#define nfcipLogD(...) /*!< Macro for the debug log method */ - -/*! Digital 1.1 - 16.12.5.2 The Target SHALL NOT attempt any error recovery and remains in Rx mode upon Transmission or a Protocol Error */ -#define nfcDepReEnableRx(rxB, rxBL, rxL) \ - rfalTransceiveBlockingTx( \ - NULL, \ - 0, \ - (rxB), \ - (rxBL), \ - (rxL), \ - (RFAL_TXRX_FLAGS_DEFAULT | (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_ON), \ - RFAL_FWT_NONE) - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -/*! Struct that holds all DEP parameters/configs for the following communications */ -typedef struct { - uint8_t did; /*!< Device ID (DID) to be used */ - - uint8_t* txBuf; /*!< Pointer to the Tx buffer to be sent */ - uint16_t txBufLen; /*!< Length of the data in the txBuf */ - uint8_t txBufPaylPos; /*!< Position inside txBuf where data starts */ - bool txChaining; /*!< Flag indicating chaining on transmission */ - - uint8_t* rxBuf; /*!< Pointer to the Rx buffer for incoming data */ - uint16_t rxBufLen; /*!< Length of the data in the rxBuf */ - uint8_t rxBufPaylPos; /*!< Position inside rxBuf where data is to be placed*/ - - uint32_t fwt; /*!< Frame Waiting Time (FWT) to be used */ - uint32_t dFwt; /*!< Delta Frame Waiting Time (dFWT) to be used */ - uint16_t fsc; /*!< Frame Size (FSC) to be used */ - -} rfalNfcDepDEPParams; - -/*! NFCIP module states */ -typedef enum { - NFCIP_ST_IDLE, - NFCIP_ST_INIT_IDLE, - NFCIP_ST_INIT_ATR, - NFCIP_ST_INIT_PSL, - NFCIP_ST_INIT_DEP_IDLE, - NFCIP_ST_INIT_DEP_TX, - NFCIP_ST_INIT_DEP_RX, - NFCIP_ST_INIT_DEP_ATN, - NFCIP_ST_INIT_DSL, - NFCIP_ST_INIT_RLS, - - NFCIP_ST_TARG_WAIT_ATR, - NFCIP_ST_TARG_WAIT_ACTV, - NFCIP_ST_TARG_DEP_IDLE, - NFCIP_ST_TARG_DEP_RX, - NFCIP_ST_TARG_DEP_RTOX, - NFCIP_ST_TARG_DEP_TX, - NFCIP_ST_TARG_DEP_SLEEP -} rfalNfcDepState; - -/*! NFCIP commands (Request, Response) */ -typedef enum { - NFCIP_CMD_ATR_REQ = 0x00, - NFCIP_CMD_ATR_RES = 0x01, - NFCIP_CMD_WUP_REQ = 0x02, - NFCIP_CMD_WUP_RES = 0x03, - NFCIP_CMD_PSL_REQ = 0x04, - NFCIP_CMD_PSL_RES = 0x05, - NFCIP_CMD_DEP_REQ = 0x06, - NFCIP_CMD_DEP_RES = 0x07, - NFCIP_CMD_DSL_REQ = 0x08, - NFCIP_CMD_DSL_RES = 0x09, - NFCIP_CMD_RLS_REQ = 0x0A, - NFCIP_CMD_RLS_RES = 0x0B -} rfalNfcDepCmd; - -/*! Struct that holds all NFCIP data */ -typedef struct { - rfalNfcDepConfigs cfg; /*!< Holds the current configuration to be used */ - - rfalNfcDepState state; /*!< Current state of the NFCIP module */ - uint8_t pni; /*!< Packet Number Information (PNI) counter */ - - uint8_t lastCmd; /*!< Last command sent */ - uint8_t lastPFB; /*!< Last PFB sent */ - uint8_t lastPFBnATN; /*!< Last PFB sent (excluding ATN) */ - uint8_t lastRTOX; /*!< Last RTOX value sent */ - - uint8_t cntTxRetrys; /*!< Retransmissions counter */ - uint8_t cntTORetrys; /*!< Timeouts counter */ - uint8_t cntRTOXRetrys; /*!< RTOX counter */ - uint8_t cntNACKRetrys; /*!< NACK counter */ - uint8_t cntATNRetrys; /*!< Attention (ATN) counter */ - - uint16_t fsc; /*!< Current Frame Size (FSC) to be used */ - bool isTxChaining; /*!< Flag for chaining on Transmission */ - bool isRxChaining; /*!< Flag for chaining on Reception */ - uint8_t* txBuf; /*!< Pointer to the Tx buffer to be sent */ - uint8_t* rxBuf; /*!< Pointer to the Rx buffer for incoming data */ - uint16_t txBufLen; /*!< Length of the data in the txBuf */ - uint16_t rxBufLen; /*!< Length of rxBuf buffer */ - uint16_t* rxRcvdLen; /*!< Length of the data in the rxBuf */ - uint8_t txBufPaylPos; /*!< Position in txBuf where data starts */ - uint8_t rxBufPaylPos; /*!< Position in rxBuf where data is to be placed */ - bool* isChaining; /*!< Flag for chaining on Reception */ - - rfalNfcDepDevice* nfcDepDev; /*!< Pointer to NFC-DEP device info */ - - uint32_t RTOXTimer; /*!< Timer used for RTOX */ - rfalNfcDepDeactCallback isDeactivating; /*!< Deactivating flag check callback */ - - bool isReqPending; /*!< Flag pending REQ from Target activation */ - bool isTxPending; /*!< Flag pending DEP Block while waiting RTOX Ack */ - bool isWait4RTOX; /*!< Flag for waiting RTOX Ack */ - - rfalNfcDepPduTxRxParam PDUParam; /*!< PDU TxRx params */ - uint16_t PDUTxPos; /*!< PDU Tx position */ - uint16_t PDURxPos; /*!< PDU Rx position */ - bool isPDURxChaining; /*!< PDU Transceive chaining flag */ -} rfalNfcDep; - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -static rfalNfcDep gNfcip; /*!< NFCIP module instance */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -static ReturnCode nfcipTxRx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint32_t fwt, - uint8_t* paylBuf, - uint8_t paylBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxActLen); -static ReturnCode nfcipTx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint8_t* paylBuf, - uint16_t paylLen, - uint8_t pfbData, - uint32_t fwt); -static ReturnCode nfcipDEPControlMsg(uint8_t pfb, uint8_t RTOX); -static ReturnCode nfcipInitiatorHandleDEP( - ReturnCode rxRes, - uint16_t rxLen, - uint16_t* outActRxLen, - bool* outIsChaining); -static ReturnCode - nfcipTargetHandleRX(ReturnCode rxRes, uint16_t* outActRxLen, bool* outIsChaining); -static ReturnCode nfcipTargetHandleActivation(rfalNfcDepDevice* nfcDepDev, uint8_t* outBRS); - -/*! - ****************************************************************************** - * \brief NFCIP Configure - * - * Configures the nfcip layer with the given configurations - * - * \param[in] cfg : nfcip configuration for following communication - ****************************************************************************** - */ -static void nfcipConfig(const rfalNfcDepConfigs* cfg); - -/*! - ****************************************************************************** - * \brief Set DEP parameters - * - * This method sets the parameters/configs for following Data Exchange - * Sets the nfcip module state according to the role it is configured - * - * - * \warning To be used only after proper Initiator/Target activation: - * nfcipTargetHandleActivation() or nfcipInitiatorActivate() has - * returned success - * - * This must be called before nfcipRun() in case of Target to pass - * rxBuffer - * - * Everytime some data needs to be transmitted call this to set it and - * call nfcipRun() until done or error - * - * \param[in] DEPParams : the parameters to be used during Data Exchange - ****************************************************************************** - */ -static void nfcipSetDEPParams(const rfalNfcDepDEPParams* DEPParams); - -/*! - ****************************************************************************** - * \brief NFCIP run protocol - * - * This method handles all the nfcip protocol during Data Exchange (DEP - * requests and responses). - * - * A data exchange cycle is considered a DEP REQ and a DEP RES. - * - * In case of Tx chaining(MI) must signal it with nfcipSetDEPParams() - * In case of Rx chaining(MI) outIsChaining will be set to true and the - * current data returned - * - * \param[out] outActRxLen : data received length - * \param[out] outIsChaining : true if other peer is performing chaining(MI) - * - * \return ERR_NONE : Data exchange cycle completed successfully - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - * \return ERR_AGAIN : Other peer is doing chaining(MI), current block - * was received successfully call again until complete - * - ****************************************************************************** - */ -static ReturnCode nfcipRun(uint16_t* outActRxLen, bool* outIsChaining); - -/*! - ****************************************************************************** - * \brief Transmission method - * - * This method checks if the current communication is Active or Passive - * and performs the necessary procedures for each communication type - * - * Transmits the data hold in txBuf - * - * \param[in] txBuf : buffer to transmit - * \param[in] txBufLen : txBuffer capacity - * \param[in] fwt : fwt for current Tx - * - * \return ERR_NONE : No error - ****************************************************************************** - */ -static ReturnCode nfcipDataTx(uint8_t* txBuf, uint16_t txBufLen, uint32_t fwt); - -/*! - ****************************************************************************** - * \brief Reception method - * - * This method checks if the current communication is Active or Passive - * and calls the appropriate reception method - * - * Copies incoming data to rxBuf - * - * \param[in] blocking : reception is to be done blocking or non-blocking - * - * \return ERR_BUSY : Busy - * \return ERR_NONE : No error - ****************************************************************************** - */ -static ReturnCode nfcipDataRx(bool blocking); - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ - -/*******************************************************************************/ -static bool nfcipDxIsSupported(uint8_t Dx, uint8_t BRx, uint8_t BSx) { - uint8_t Bx; - - /* Take the min of the possible bit rates, we'll use one for both directions */ - Bx = MIN(BRx, BSx); - - /* Lower bit rates must be supported for P2P */ - if((Dx <= (uint8_t)RFAL_NFCDEP_Dx_04_424)) { - return true; - } - - if((Dx == (uint8_t)RFAL_NFCDEP_Dx_08_848) && (Bx >= (uint8_t)RFAL_NFCDEP_Bx_08_848)) { - return true; - } - - return false; -} - -/*******************************************************************************/ -static ReturnCode nfcipTxRx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint32_t fwt, - uint8_t* paylBuf, - uint8_t paylBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxActLen) { - ReturnCode ret; - - if((cmd == NFCIP_CMD_DEP_REQ) || - (cmd == NFCIP_CMD_DEP_RES)) /* this method cannot be used for DEPs */ - { - return ERR_PARAM; - } - - /* Assign the global params for this TxRx */ - gNfcip.rxBuf = rxBuf; - gNfcip.rxBufLen = rxBufLen; - gNfcip.rxRcvdLen = rxActLen; - - /*******************************************************************************/ - /* Transmission */ - /*******************************************************************************/ - if(txBuf != NULL) /* if nothing to Tx, just do Rx */ - { - EXIT_ON_ERR(ret, nfcipTx(cmd, txBuf, paylBuf, paylBufLen, 0, fwt)); - } - - /*******************************************************************************/ - /* Reception */ - /*******************************************************************************/ - ret = nfcipDataRx(true); - if(ret != ERR_NONE) { - return ret; - } - - /*******************************************************************************/ - *rxActLen = *rxBuf; /* Use LEN byte instead due to with/without CRC modes */ - return ERR_NONE; /* Tx and Rx completed successfully */ -} - -/*******************************************************************************/ -static ReturnCode nfcipDEPControlMsg(uint8_t pfb, uint8_t RTOX) { - uint8_t ctrlMsg[20]; - rfalNfcDepCmd depCmd; - uint32_t fwt; - - /*******************************************************************************/ - /* Calculate Cmd and fwt to be used */ - /*******************************************************************************/ - depCmd = - ((gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) ? NFCIP_CMD_DEP_RES : NFCIP_CMD_DEP_REQ); - fwt = - ((gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) ? - NFCIP_NO_FWT : - (nfcip_PFBisSTO(pfb) ? ((RTOX * gNfcip.cfg.fwt) + gNfcip.cfg.dFwt) : - (gNfcip.cfg.fwt + gNfcip.cfg.dFwt))); - - if(nfcip_PFBisSTO(pfb)) { - ctrlMsg[RFAL_NFCDEP_DEPREQ_HEADER_LEN] = RTOX; - return nfcipTx( - depCmd, ctrlMsg, &ctrlMsg[RFAL_NFCDEP_DEPREQ_HEADER_LEN], sizeof(uint8_t), pfb, fwt); - } else { - return nfcipTx(depCmd, ctrlMsg, NULL, 0, pfb, fwt); - } -} - -/*******************************************************************************/ -static void nfcipClearCounters(void) { - gNfcip.cntATNRetrys = 0; - gNfcip.cntNACKRetrys = 0; - gNfcip.cntTORetrys = 0; - gNfcip.cntTxRetrys = 0; - gNfcip.cntRTOXRetrys = 0; -} - -/*******************************************************************************/ -static ReturnCode nfcipInitiatorHandleDEP( - ReturnCode rxRes, - uint16_t rxLen, - uint16_t* outActRxLen, - bool* outIsChaining) { - ReturnCode ret; - uint8_t nfcDepLen; - uint8_t rxMsgIt; - uint8_t rxPFB; - uint8_t rxRTOX; - uint8_t optHdrLen; - - ret = ERR_INTERNAL; - rxMsgIt = 0; - optHdrLen = 0; - - *outActRxLen = 0; - *outIsChaining = false; - - /*******************************************************************************/ - /* Handle reception errors */ - /*******************************************************************************/ - switch(rxRes) { - /*******************************************************************************/ - /* Timeout -> Digital 1.0 14.15.5.6 */ - case ERR_TIMEOUT: - - nfcipLogI(" NFCIP(I) TIMEOUT TORetrys:%d \r\n", gNfcip.cntTORetrys); - - /* Digital 1.0 14.15.5.6 - If nTO >= Max raise protocol error */ - if(gNfcip.cntTORetrys++ >= RFAL_NFCDEP_TO_RETRYS) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Upon Timeout error, if Deactivation is pending, no more error recovery - * will be done #54. - * This is used to address the issue some devices that havea big TO. - * Normally LLCP layer has timeout already, and NFCIP layer is still - * running error handling, retrying ATN/NACKs */ - /*******************************************************************************/ - if(nfcipIsDeactivationPending()) { - nfcipLogI(" skipping error recovery due deactivation pending \r\n"); - return ERR_TIMEOUT; - } - - /* Digital 1.0 14.15.5.6 1) If last PDU was NACK */ - if(nfcip_PFBisRNACK(gNfcip.lastPFB)) { - /* Digital 1.0 14.15.5.6 2) if NACKs failed raise protocol error */ - if(gNfcip.cntNACKRetrys++ >= RFAL_NFCDEP_MAX_NACK_RETRYS) { - return ERR_PROTO; - } - - /* Send NACK */ - nfcipLogI(" NFCIP(I) Sending NACK retry: %d \r\n", gNfcip.cntNACKRetrys); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBRPDU_NACK(gNfcip.pni), 0)); - return ERR_BUSY; - } - - nfcipLogI(" NFCIP(I) Checking if to send ATN ATNRetrys: %d \r\n", gNfcip.cntATNRetrys); - - /* Digital 1.0 14.15.5.6 3) Otherwise send ATN */ - if(gNfcip.cntATNRetrys++ >= RFAL_NFCDEP_MAX_NACK_RETRYS) { - return ERR_PROTO; - } - - /* Send ATN */ - nfcipLogI(" NFCIP(I) Sending ATN \r\n"); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_ATN(), 0)); - return ERR_BUSY; - - /*******************************************************************************/ - /* Data rcvd with error -> Digital 1.0 14.12.5.4 */ - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - case ERR_RF_COLLISION: - - nfcipLogI(" NFCIP(I) rx Error: %d \r\n", rxRes); - - /* Digital 1.0 14.12.5.4 Tx Error with data, ignore */ - if(rxLen < NFCIP_MIN_TXERROR_LEN) { - nfcipLogI(" NFCIP(I) Transmission error w data \r\n"); -#if 0 - if(gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_PASSIVE) - { - nfcipLogI( " NFCIP(I) Transmission error w data -> reEnabling Rx \r\n" ); - nfcipReEnableRxTout( NFCIP_TRECOV ); - return ERR_BUSY; - } -#endif /* 0 */ - } - - /* Digital 1.1 16.12.5.4 if NACKs failed raise Transmission error */ - if(gNfcip.cntNACKRetrys++ >= RFAL_NFCDEP_MAX_NACK_RETRYS) { - return ERR_FRAMING; - } - - /* Send NACK */ - nfcipLogI(" NFCIP(I) Sending NACK \r\n"); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBRPDU_NACK(gNfcip.pni), 0)); - return ERR_BUSY; - - case ERR_NONE: - break; - - case ERR_BUSY: - return ERR_BUSY; /* Debug purposes */ - - default: - nfcipLogW(" NFCIP(I) Error: %d \r\n", rxRes); - return rxRes; - } - - /*******************************************************************************/ - /* Rx OK check if valid DEP PDU */ - /*******************************************************************************/ - - /* Due to different modes on ST25R391x (with/without CRC) use NFC-DEP LEN instead of bytes retrieved */ - nfcDepLen = gNfcip.rxBuf[rxMsgIt++]; - - nfcipLogD(" NFCIP(I) rx OK: %d bytes \r\n", nfcDepLen); - - /* Digital 1.0 14.15.5.5 Protocol Error */ - if(gNfcip.rxBuf[rxMsgIt++] != NFCIP_RES) { - nfcipLogW(" NFCIP(I) error %02X instead of %02X \r\n", gNfcip.rxBuf[--rxMsgIt], NFCIP_RES); - return ERR_PROTO; - } - - /* Digital 1.0 14.15.5.5 Protocol Error */ - if(gNfcip.rxBuf[rxMsgIt++] != (uint8_t)NFCIP_CMD_DEP_RES) { - nfcipLogW( - " NFCIP(I) error %02X instead of %02X \r\n", - gNfcip.rxBuf[--rxMsgIt], - NFCIP_CMD_DEP_RES); - return ERR_PROTO; - } - - rxPFB = gNfcip.rxBuf[rxMsgIt++]; - - /*******************************************************************************/ - /* Check for valid PFB type */ - if(!(nfcip_PFBisSPDU(rxPFB) || nfcip_PFBisRPDU(rxPFB) || nfcip_PFBisIPDU(rxPFB))) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Digital 1.0 14.8.2.1 check if DID is expected and match -> Protocol Error */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || !nfcip_PFBhasDID(rxPFB)) { - return ERR_PROTO; - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasDID(rxPFB)) /* DID not expected but rcv */ - { - return ERR_PROTO; - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - /* Digital 1.0 14.6.2.8 & 14.6.3.11 NAD must not be used */ - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - if((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.nad) || !nfcip_PFBhasNAD(rxPFB)) { - return ERR_PROTO; - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasNAD(rxPFB)) /* NAD not expected but rcv */ - { - return ERR_PROTO; - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - /* Process R-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisRPDU(rxPFB)) { - /*******************************************************************************/ - /* R ACK */ - /*******************************************************************************/ - if(nfcip_PFBisRACK(rxPFB)) { - nfcipLogI(" NFCIP(I) Rcvd ACK \r\n"); - if(gNfcip.pni == nfcip_PBF_PNI(rxPFB)) { - /* 14.12.3.3 R-ACK with correct PNI -> Increment */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - /* R-ACK while not performing chaining -> Protocol error*/ - if(!gNfcip.isTxChaining) { - return ERR_PROTO; - } - - nfcipClearCounters(); - gNfcip.state = NFCIP_ST_INIT_DEP_IDLE; - return ERR_NONE; /* This block has been transmitted */ - } else /* Digital 1.0 14.12.4.5 ACK with wrong PNI Initiator may retransmit */ - { - if(gNfcip.cntTxRetrys++ >= RFAL_NFCDEP_MAX_TX_RETRYS) { - return ERR_PROTO; - } - - /* Extended the MAY in Digital 1.0 14.12.4.5 to only reTransmit if the ACK - * is for the previous DEP, otherwise raise Protocol immediately - * If the PNI difference is more than 1 it is worthless to reTransmit 3x - * and after raise the error */ - - if(nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB)) { - /* ReTransmit */ - nfcipLogI(" NFCIP(I) Rcvd ACK prev PNI -> reTx \r\n"); - gNfcip.state = NFCIP_ST_INIT_DEP_TX; - return ERR_BUSY; - } - - nfcipLogI(" NFCIP(I) Rcvd ACK unexpected far PNI -> Error \r\n"); - return ERR_PROTO; - } - } else /* Digital 1.0 - 14.12.5.2 Target must never send NACK */ - { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Process S-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisSPDU(rxPFB)) { - nfcipLogI(" NFCIP(I) Rcvd S-PDU \r\n"); - /*******************************************************************************/ - /* S ATN */ - /*******************************************************************************/ - if(nfcip_PFBisSATN(rxPFB)) /* If is a S-ATN */ - { - nfcipLogI(" NFCIP(I) Rcvd ATN \r\n"); - if(nfcip_PFBisSATN(gNfcip.lastPFB)) /* Check if is expected */ - { - gNfcip.cntATNRetrys = 0; /* Clear ATN counter */ - - /* Although spec is not clear NFC Forum Digital test is expecting to - * retransmit upon receiving ATN_RES */ - if(nfcip_PFBisSTO(gNfcip.lastPFBnATN)) { - nfcipLogI(" NFCIP(I) Rcvd ATN -> reTx RTOX_RES \r\n"); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_TO(), gNfcip.lastRTOX)); - } else { - /* ReTransmit ? */ - if(gNfcip.cntTxRetrys++ >= RFAL_NFCDEP_MAX_TX_RETRYS) { - return ERR_PROTO; - } - - nfcipLogI(" NFCIP(I) Rcvd ATN -> reTx PNI: %d \r\n", gNfcip.pni); - gNfcip.state = NFCIP_ST_INIT_DEP_TX; - } - - return ERR_BUSY; - } else /* Digital 1.0 14.12.4.4 & 14.12.4.8 */ - { - return ERR_PROTO; - } - } - /*******************************************************************************/ - /* S TO */ - /*******************************************************************************/ - else if(nfcip_PFBisSTO(rxPFB)) /* If is a S-TO (RTOX) */ - { - nfcipLogI(" NFCIP(I) Rcvd TO \r\n"); - - rxRTOX = gNfcip.rxBuf[rxMsgIt++]; - - /* Digital 1.1 16.12.4.3 - Initiator MAY stop accepting subsequent RTOX Req * - * - RTOX request to an ATN -> Protocol error */ - if((gNfcip.cntRTOXRetrys++ > RFAL_NFCDEP_MAX_RTOX_RETRYS) || - nfcip_PFBisSATN(gNfcip.lastPFB)) { - return ERR_PROTO; - } - - /* Digital 1.1 16.8.4.1 RTOX must be between [1,59] */ - if((rxRTOX < NFCIP_INIT_MIN_RTOX) || (rxRTOX > NFCIP_INIT_MAX_RTOX)) { - return ERR_PROTO; - } - - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_TO(), rxRTOX)); - gNfcip.lastRTOX = rxRTOX; - - return ERR_BUSY; - } else { - /* Unexpected S-PDU */ - return ERR_PROTO; /* PRQA S 2880 # MISRA 2.1 - Guard code to prevent unexpected behavior */ - } - } - - /*******************************************************************************/ - /* Process I-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisIPDU(rxPFB)) { - if(gNfcip.pni != nfcip_PBF_PNI(rxPFB)) { - nfcipLogI( - " NFCIP(I) Rcvd IPDU wrong PNI curPNI: %d rxPNI: %d \r\n", - gNfcip.pni, - nfcip_PBF_PNI(rxPFB)); - return ERR_PROTO; - } - - nfcipLogD(" NFCIP(I) Rcvd IPDU OK PNI: %d \r\n", gNfcip.pni); - - /* 14.12.3.3 I-PDU with correct PNI -> Increment */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - /* Successful data Exchange */ - nfcipClearCounters(); - *outActRxLen = ((uint16_t)nfcDepLen - RFAL_NFCDEP_DEP_HEADER - (uint16_t)optHdrLen); - - if((&gNfcip.rxBuf[gNfcip.rxBufPaylPos] != - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen]) && - (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gNfcip.rxBuf[gNfcip.rxBufPaylPos], - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen], - *outActRxLen); - } - - /*******************************************************************************/ - /* Check if target is indicating chaining MI */ - /*******************************************************************************/ - if(nfcip_PFBisIMI(rxPFB)) { - gNfcip.isRxChaining = true; - *outIsChaining = true; - - nfcipLogD(" NFCIP(I) Rcvd IPDU OK w MI -> ACK \r\n"); - EXIT_ON_ERR( - ret, nfcipDEPControlMsg(nfcip_PFBRPDU_ACK(gNfcip.pni), gNfcip.rxBuf[rxMsgIt++])); - - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived*/ - } else { - gNfcip.isRxChaining = false; - gNfcip.state = NFCIP_ST_INIT_DEP_IDLE; - - ret = ERR_NONE; /* Data exchange done */ - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode - nfcipTargetHandleRX(ReturnCode rxRes, uint16_t* outActRxLen, bool* outIsChaining) { - ReturnCode ret; - uint8_t nfcDepLen; - uint8_t rxMsgIt; - uint8_t rxPFB; - uint8_t optHdrLen; - uint8_t resBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_TARGET_RES_MAX]; - - ret = ERR_INTERNAL; - rxMsgIt = 0; - optHdrLen = 0; - - *outActRxLen = 0; - *outIsChaining = false; - - /*******************************************************************************/ - /* Handle reception errors */ - /*******************************************************************************/ - switch(rxRes) { - /*******************************************************************************/ - case ERR_NONE: - break; - - case ERR_LINK_LOSS: - nfcipLogW(" NFCIP(T) Error: %d \r\n", rxRes); - return rxRes; - - case ERR_BUSY: - return ERR_BUSY; /* Debug purposes */ - - case ERR_TIMEOUT: - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - case ERR_PROTO: - default: - /* Digital 1.1 16.12.5.2 The Target MUST NOT attempt any error recovery. * - * The Target MUST always stay in receive mode when a * - * Transmission Error or a Protocol Error occurs. * - * * - * Do not push Transmission/Protocol Errors to upper layer in Listen Mode #766 */ - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; - } - - /*******************************************************************************/ - /* Rx OK check if valid DEP PDU */ - /*******************************************************************************/ - - /* Due to different modes on ST25R391x (with/without CRC) use NFC-DEP LEN instead of bytes retrieved */ - nfcDepLen = gNfcip.rxBuf[rxMsgIt++]; - - nfcipLogD(" NFCIP(T) rx OK: %d bytes \r\n", nfcDepLen); - - if(gNfcip.rxBuf[rxMsgIt++] != NFCIP_REQ) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad request */ - } - - /*******************************************************************************/ - /* Check whether target rcvd a normal DEP or deactivation request */ - /*******************************************************************************/ - switch(gNfcip.rxBuf[rxMsgIt++]) { - /*******************************************************************************/ - case(uint8_t)NFCIP_CMD_DEP_REQ: - break; /* Continue to normal DEP processing */ - - /*******************************************************************************/ - case(uint8_t)NFCIP_CMD_DSL_REQ: - - nfcipLogI(" NFCIP(T) rx DSL \r\n"); - - /* Digital 1.0 14.9.1.2 If DID is used and incorrect ignore it */ - /* [Digital 1.0, 16.9.1.2]: If DID == 0, Target SHALL ignore DSL_REQ with DID */ - if((((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || - (nfcDepLen != RFAL_NFCDEP_DSL_RLS_LEN_DID)) && - (gNfcip.cfg.did != RFAL_NFCDEP_DID_NO)) || - ((gNfcip.cfg.did == RFAL_NFCDEP_DID_NO) && - (nfcDepLen != RFAL_NFCDEP_DSL_RLS_LEN_NO_DID))) { - nfcipLogI(" NFCIP(T) DSL wrong DID, ignoring \r\n"); - return ERR_BUSY; - } - - nfcipTx(NFCIP_CMD_DSL_RES, resBuf, NULL, 0, 0, NFCIP_NO_FWT); - - gNfcip.state = NFCIP_ST_TARG_DEP_SLEEP; - return ERR_SLEEP_REQ; - - /*******************************************************************************/ - case(uint8_t)NFCIP_CMD_RLS_REQ: - - nfcipLogI(" NFCIP(T) rx RLS \r\n"); - - /* Digital 1.0 14.10.1.2 If DID is used and incorrect ignore it */ - /* [Digital 1.0, 16.10.2.2]: If DID == 0, Target SHALL ignore DSL_REQ with DID */ - if((((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || - (nfcDepLen != RFAL_NFCDEP_DSL_RLS_LEN_DID)) && - (gNfcip.cfg.did != RFAL_NFCDEP_DID_NO)) || - ((gNfcip.cfg.did == RFAL_NFCDEP_DID_NO) && - (nfcDepLen > RFAL_NFCDEP_DSL_RLS_LEN_NO_DID))) { - nfcipLogI(" NFCIP(T) RLS wrong DID, ignoring \r\n"); - return ERR_BUSY; - } - - nfcipTx(NFCIP_CMD_RLS_RES, resBuf, NULL, 0, 0, NFCIP_NO_FWT); - - gNfcip.state = NFCIP_ST_TARG_DEP_IDLE; - return ERR_RELEASE_REQ; - - /*******************************************************************************/ - /*case NFCIP_CMD_PSL_REQ: PSL must be handled in Activation only */ - /*case NFCIP_CMD_WUP_REQ: WUP not in NFC Forum Digital 1.0 */ - default: - - /* Don't go to NFCIP_ST_TARG_DEP_IDLE state as it needs to ignore this * - * invalid frame, and keep waiting for more frames */ - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad frame */ - } - - /*******************************************************************************/ - - rxPFB = gNfcip.rxBuf[rxMsgIt++]; /* Store rcvd PFB */ - - /*******************************************************************************/ - /* Check for valid PFB type */ - if(!(nfcip_PFBisSPDU(rxPFB) || nfcip_PFBisRPDU(rxPFB) || nfcip_PFBisIPDU(rxPFB))) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore invalid PFB */ - } - - /*******************************************************************************/ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if(!nfcip_PFBhasDID(rxPFB)) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad/missing DID */ - } - if(gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) /* MISRA 13.5 */ - { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad/missing DID */ - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasDID(rxPFB)) /* DID not expected but rcv */ - { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected DID */ - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - if((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || !nfcip_PFBhasDID(rxPFB)) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad/missing DID */ - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasNAD(rxPFB)) /* NAD not expected but rcv */ - { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected NAD */ - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - /* Process R-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisRPDU(rxPFB)) { - nfcipLogD(" NFCIP(T) Rcvd R-PDU \r\n"); - /*******************************************************************************/ - /* R ACK */ - /*******************************************************************************/ - if(nfcip_PFBisRACK(rxPFB)) { - nfcipLogI(" NFCIP(T) Rcvd ACK \r\n"); - if(gNfcip.pni == nfcip_PBF_PNI(rxPFB)) { - /* R-ACK while not performing chaining -> Protocol error */ - if(!gNfcip.isTxChaining) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected ACK */ - } - - /* This block has been transmitted and acknowledged, perform RTOX until next data is provided */ - - /* Digital 1.1 16.12.4.7 - If ACK rcvd continue with chaining or an RTOX */ - nfcipTimerStart( - gNfcip.RTOXTimer, - nfcipRTOXAdjust(nfcipConv1FcToMs(rfalNfcDepWT2RWT(gNfcip.cfg.to)))); - gNfcip.state = NFCIP_ST_TARG_DEP_RTOX; - - return ERR_NONE; /* This block has been transmitted */ - } - - /* Digital 1.0 14.12.3.4 - If last send was ATN and rx PNI is minus 1 */ - else if( - nfcip_PFBisSATN(gNfcip.lastPFB) && - (nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB))) { - nfcipLogI(" NFCIP(T) wrong PNI, last was ATN reTx \r\n"); - /* Spec says to leave current PNI as is, but will be Inc after Tx, remaining the same */ - gNfcip.pni = nfcip_PNIDec(gNfcip.pni); - - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return ERR_BUSY; - } else { - /* MISRA 15.7 - Empty else */ - } - } - /*******************************************************************************/ - /* R NACK */ - /*******************************************************************************/ - /* ISO 18092 12.6.1.3.3 When rcv NACK if PNI = prev PNI sent -> reTx */ - else if(nfcip_PFBisRNACK(rxPFB) && (nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB))) { - nfcipLogI(" NFCIP(T) Rcvd NACK \r\n"); - - gNfcip.pni = nfcip_PNIDec(gNfcip.pni); /* Dec so that has the prev PNI */ - - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return ERR_BUSY; - } else { - nfcipLogI(" NFCIP(T) Unexpected R-PDU \r\n"); - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected R-PDU */ - } - } - - /*******************************************************************************/ - /* Process S-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisSPDU(rxPFB)) { - nfcipLogD(" NFCIP(T) Rcvd S-PDU \r\n"); - - /*******************************************************************************/ - /* S ATN */ - /*******************************************************************************/ - /* ISO 18092 12.6.3 Attention */ - if(nfcip_PFBisSATN(rxPFB)) /* If is a S-ATN */ - { - nfcipLogI(" NFCIP(T) Rcvd ATN curPNI: %d \r\n", gNfcip.pni); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_ATN(), 0)); - return ERR_BUSY; - } - - /*******************************************************************************/ - /* S TO */ - /*******************************************************************************/ - else if(nfcip_PFBisSTO(rxPFB)) /* If is a S-TO (RTOX) */ - { - if(nfcip_PFBisSTO(gNfcip.lastPFBnATN)) { - nfcipLogI(" NFCIP(T) Rcvd TO \r\n"); - - /* Digital 1.1 16.8.4.6 RTOX value in RES different that in REQ -> Protocol Error */ - if(gNfcip.lastRTOX != gNfcip.rxBuf[rxMsgIt++]) { - nfcipLogI(" NFCIP(T) Mismatched RTOX value \r\n"); - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected RTOX value */ - } - - /* Clear waiting for RTOX Ack Flag */ - gNfcip.isWait4RTOX = false; - - /* Check if a Tx is already pending */ - if(gNfcip.isTxPending) { - nfcipLogW(" NFCIP(T) Tx pending, go immediately to TX \r\n"); - - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return ERR_BUSY; - } - - /* Start RTOX timer and change to check state */ - nfcipTimerStart( - gNfcip.RTOXTimer, - nfcipRTOXAdjust( - nfcipConv1FcToMs(gNfcip.lastRTOX * rfalNfcDepWT2RWT(gNfcip.cfg.to)))); - gNfcip.state = NFCIP_ST_TARG_DEP_RTOX; - - return ERR_BUSY; - } - } else { - /* Unexpected S-PDU */ - nfcipLogI( - " NFCIP(T) Unexpected S-PDU \r\n"); /* PRQA S 2880 # MISRA 2.1 - Guard code to prevent unexpected behavior */ - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected S-PDU */ - } - } - - /*******************************************************************************/ - /* Process I-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisIPDU(rxPFB)) { - if(gNfcip.pni != nfcip_PBF_PNI(rxPFB)) { - nfcipLogI( - " NFCIP(T) Rcvd IPDU wrong PNI curPNI: %d rxPNI: %d \r\n", - gNfcip.pni, - nfcip_PBF_PNI(rxPFB)); - - /* Digital 1.1 16.12.3.4 - If last send was ATN and rx PNI is minus 1 */ - if(nfcip_PFBisSATN(gNfcip.lastPFB) && - (nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB))) { - /* Spec says to leave current PNI as is, but will be Inc after Data Tx, remaining the same */ - gNfcip.pni = nfcip_PNIDec(gNfcip.pni); - - if(nfcip_PFBisIMI(rxPFB)) { - nfcipLogI( - " NFCIP(T) PNI = prevPNI && ATN before && chaining -> send ACK \r\n"); - EXIT_ON_ERR( - ret, - nfcipDEPControlMsg(nfcip_PFBRPDU_ACK(gNfcip.pni), gNfcip.rxBuf[rxMsgIt++])); - - /* Digital 1.1 16.12.3.4 (...) leave the current PNI unchanged afterwards */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - } else { - nfcipLogI(" NFCIP(T) PNI = prevPNI && ATN before -> reTx last I-PDU \r\n"); - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - } - - return ERR_BUSY; - } - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad PNI value */ - } - - nfcipLogD(" NFCIP(T) Rcvd IPDU OK PNI: %d \r\n", gNfcip.pni); - - /*******************************************************************************/ - /* Successful data exchange */ - /*******************************************************************************/ - *outActRxLen = ((uint16_t)nfcDepLen - RFAL_NFCDEP_DEP_HEADER - (uint16_t)optHdrLen); - - nfcipClearCounters(); - - if((&gNfcip.rxBuf[gNfcip.rxBufPaylPos] != - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen]) && - (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gNfcip.rxBuf[gNfcip.rxBufPaylPos], - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen], - *outActRxLen); - } - - /*******************************************************************************/ - /* Check if Initiator is indicating chaining MI */ - /*******************************************************************************/ - if(nfcip_PFBisIMI(rxPFB)) { - gNfcip.isRxChaining = true; - *outIsChaining = true; - - nfcipLogD(" NFCIP(T) Rcvd IPDU OK w MI -> ACK \r\n"); - EXIT_ON_ERR( - ret, nfcipDEPControlMsg(nfcip_PFBRPDU_ACK(gNfcip.pni), gNfcip.rxBuf[rxMsgIt++])); - - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived*/ - } else { - if(gNfcip.isRxChaining) { - nfcipLogI(" NFCIP(T) Rcvd last IPDU chaining finished \r\n"); - } - - /*******************************************************************************/ - /* Reception done, send to DH and start RTOX timer */ - /*******************************************************************************/ - nfcipTimerStart( - gNfcip.RTOXTimer, - nfcipRTOXAdjust(nfcipConv1FcToMs(rfalNfcDepWT2RWT(gNfcip.cfg.to)))); - gNfcip.state = NFCIP_ST_TARG_DEP_RTOX; - - gNfcip.isRxChaining = false; - ret = ERR_NONE; /* Data exchange done */ - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode nfcipTx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint8_t* paylBuf, - uint16_t paylLen, - uint8_t pfbData, - uint32_t fwt) { - uint16_t txBufIt; - uint8_t* txBlock; - uint8_t* payloadBuf; - uint8_t pfb; - - if(txBuf == NULL) { - return ERR_PARAM; - } - - payloadBuf = paylBuf; /* MISRA 17.8: Use intermediate variable */ - - if((paylLen == 0U) || (payloadBuf == NULL)) { - payloadBuf = (uint8_t*)&txBuf - [RFAL_NFCDEP_DEPREQ_HEADER_LEN]; /* If not a DEP (no Data) ensure enough space for header */ - } - - txBufIt = 0; - pfb = pfbData; /* MISRA 17.8: Use intermediate variable */ - - txBlock = payloadBuf; /* Point to beginning of the Data, and go backwards */ - - gNfcip.lastCmd = (uint8_t)cmd; /* Store last cmd sent */ - gNfcip.lastPFB = NFCIP_PFB_INVALID; /* Reset last pfb sent */ - - /*******************************************************************************/ - /* Compute outgoing NFCIP message */ - /*******************************************************************************/ - switch(cmd) { - /*******************************************************************************/ - case NFCIP_CMD_ATR_RES: - case NFCIP_CMD_ATR_REQ: - - rfalNfcDepSetNFCID(payloadBuf, gNfcip.cfg.nfcid, gNfcip.cfg.nfcidLen); /* NFCID */ - txBufIt += RFAL_NFCDEP_NFCID3_LEN; - - payloadBuf[txBufIt++] = gNfcip.cfg.did; /* DID */ - payloadBuf[txBufIt++] = gNfcip.cfg.bs; /* BS */ - payloadBuf[txBufIt++] = gNfcip.cfg.br; /* BR */ - - if(cmd == NFCIP_CMD_ATR_RES) { - payloadBuf[txBufIt++] = gNfcip.cfg.to; /* ATR_RES[ TO ] */ - } - - if(gNfcip.cfg.gbLen > 0U) { - payloadBuf[txBufIt++] = nfcip_PPwGB(gNfcip.cfg.lr); /* PP signalling GB */ - ST_MEMCPY( - &payloadBuf[txBufIt], gNfcip.cfg.gb, gNfcip.cfg.gbLen); /* set General Bytes */ - txBufIt += gNfcip.cfg.gbLen; - } else { - payloadBuf[txBufIt++] = rfalNfcDepLR2PP(gNfcip.cfg.lr); /* PP without GB */ - } - - if((txBufIt + RFAL_NFCDEP_CMDTYPE_LEN + RFAL_NFCDEP_CMD_LEN) > - RFAL_NFCDEP_ATRREQ_MAX_LEN) /* Check max ATR length (ATR_REQ = ATR_RES)*/ - { - return ERR_PARAM; - } - break; - - /*******************************************************************************/ - case NFCIP_CMD_WUP_REQ: /* ISO 18092 - 12.5.2.1 */ - - rfalNfcDepSetNFCID((payloadBuf), gNfcip.cfg.nfcid, gNfcip.cfg.nfcidLen); /* NFCID */ - txBufIt += RFAL_NFCDEP_NFCID3_LEN; - - *(--txBlock) = gNfcip.cfg.did; /* DID */ - break; - - /*******************************************************************************/ - case NFCIP_CMD_WUP_RES: /* ISO 18092 - 12.5.2.2 */ - case NFCIP_CMD_PSL_REQ: - case NFCIP_CMD_PSL_RES: - - *(--txBlock) = gNfcip.cfg.did; /* DID */ - break; - - /*******************************************************************************/ - case NFCIP_CMD_RLS_REQ: - case NFCIP_CMD_RLS_RES: - case NFCIP_CMD_DSL_REQ: - case NFCIP_CMD_DSL_RES: - - /* Digital 1.0 - 14.8.1.1 & 14.9.1.1 & 14.10.1.1 Only add DID if not 0 */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - *(--txBlock) = gNfcip.cfg.did; /* DID */ - } - break; - - /*******************************************************************************/ - case NFCIP_CMD_DEP_REQ: - case NFCIP_CMD_DEP_RES: - - /* Compute optional PFB bits */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - pfb |= NFCIP_PFB_DID_BIT; - } - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - pfb |= NFCIP_PFB_NAD_BIT; - } - if((gNfcip.isTxChaining) && (nfcip_PFBisIPDU(pfb))) { - pfb |= NFCIP_PFB_MI_BIT; - } - - /* Store PFB for future handling */ - gNfcip.lastPFB = pfb; /* store PFB sent */ - - if(!nfcip_PFBisSATN(pfb)) { - gNfcip.lastPFBnATN = pfb; /* store last PFB different then ATN */ - } - - /* Add NAD if it is to be supported */ - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - *(--txBlock) = gNfcip.cfg.nad; /* NAD */ - } - - /* Digital 1.0 - 14.8.1.1 & 14.8.1.1 Only add DID if not 0 */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - *(--txBlock) = gNfcip.cfg.did; /* DID */ - } - - *(--txBlock) = pfb; /* PFB */ - - /* NCI 1.0 - Check if Empty frames are allowed */ - if((paylLen == 0U) && nfcipIsEmptyDEPDisabled(gNfcip.cfg.oper) && nfcip_PFBisIPDU(pfb)) { - return ERR_PARAM; - } - break; - - /*******************************************************************************/ - default: - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Prepend Header */ - /*******************************************************************************/ - *(--txBlock) = (uint8_t)cmd; /* CMD */ - *(--txBlock) = (uint8_t)(nfcipCmdIsReq(cmd) ? NFCIP_REQ : NFCIP_RES); /* CMDType */ - - txBufIt += - paylLen + - (uint16_t)((uint32_t)payloadBuf - (uint32_t)txBlock); /* Calculate overall buffer size */ - - if(txBufIt > gNfcip.fsc) /* Check if msg length violates the maximum payload size FSC */ - { - return ERR_NOTSUPP; - } - - /*******************************************************************************/ - return nfcipDataTx(txBlock, txBufIt, fwt); -} - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -static void nfcipConfig(const rfalNfcDepConfigs* cfg) { - if(cfg == NULL) { - return; - } - - ST_MEMCPY(&gNfcip.cfg, cfg, sizeof(rfalNfcDepConfigs)); /* Copy given config to local */ - - gNfcip.cfg.to = - MIN(RFAL_NFCDEP_WT_TRG_MAX, gNfcip.cfg.to); /* Ensure proper WT value */ - gNfcip.cfg.did = nfcip_DIDMax(gNfcip.cfg.did); /* Ensure proper DID value */ - gNfcip.fsc = rfalNfcDepLR2FS(gNfcip.cfg.lr); /* Calculate FSC based on given LR */ - - gNfcip.state = - ((gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) ? NFCIP_ST_TARG_WAIT_ATR : - NFCIP_ST_INIT_IDLE); -} - -/*******************************************************************************/ -static ReturnCode nfcipRun(uint16_t* outActRxLen, bool* outIsChaining) { - ReturnCode ret; - - ret = ERR_SYNTAX; - - nfcipLogD(" NFCIP Run() state: %d \r\n", gNfcip.state); - - switch(gNfcip.state) { - /*******************************************************************************/ - case NFCIP_ST_IDLE: - case NFCIP_ST_INIT_DEP_IDLE: - case NFCIP_ST_TARG_DEP_IDLE: - case NFCIP_ST_TARG_DEP_SLEEP: - return ERR_NONE; - - /*******************************************************************************/ - case NFCIP_ST_INIT_DEP_TX: - - nfcipLogD(" NFCIP(I) Tx PNI: %d txLen: %d \r\n", gNfcip.pni, gNfcip.txBufLen); - ret = nfcipTx( - NFCIP_CMD_DEP_REQ, - gNfcip.txBuf, - &gNfcip.txBuf[gNfcip.txBufPaylPos], - gNfcip.txBufLen, - nfcip_PFBIPDU(gNfcip.pni), - (gNfcip.cfg.fwt + gNfcip.cfg.dFwt)); - - switch(ret) { - case ERR_NONE: - gNfcip.state = NFCIP_ST_INIT_DEP_RX; - break; - - case ERR_PARAM: - default: - gNfcip.state = NFCIP_ST_INIT_DEP_IDLE; - return ret; - } - /* fall through */ - - /*******************************************************************************/ - case NFCIP_ST_INIT_DEP_RX: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - ret = nfcipDataRx(false); - - if(ret != ERR_BUSY) { - ret = nfcipInitiatorHandleDEP(ret, *gNfcip.rxRcvdLen, outActRxLen, outIsChaining); - } - - break; - - /*******************************************************************************/ - case NFCIP_ST_TARG_DEP_RTOX: - - if(!nfcipTimerisExpired(gNfcip.RTOXTimer)) /* Do nothing until RTOX timer has expired */ - { - return ERR_BUSY; - } - - /* If we cannot send a RTOX raise a Timeout error so that we do not - * hold the field On forever in AP2P */ - if(nfcipIsRTOXReqDisabled(gNfcip.cfg.oper)) { - /* We should reEnable Rx, and measure time between our field Off to - * either report link loss or recover #287 */ - nfcipLogI(" NFCIP(T) RTOX not sent due to config, NOT reenabling Rx \r\n"); - return ERR_TIMEOUT; - } - - if(gNfcip.cntRTOXRetrys++ > - RFAL_NFCDEP_MAX_RTOX_RETRYS) /* Check maximum consecutive RTOX requests */ - { - return ERR_PROTO; - } - - nfcipLogI(" NFCIP(T) RTOX sent \r\n"); - - gNfcip.lastRTOX = - nfcip_RTOXTargMax(gNfcip.cfg.to); /* Calculate requested RTOX value, and send it */ - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_TO(), gNfcip.lastRTOX)); - - /* Set waiting for RTOX Ack Flag */ - gNfcip.isWait4RTOX = true; - - gNfcip.state = NFCIP_ST_TARG_DEP_RX; /* Go back to Rx to process RTOX ack */ - return ERR_BUSY; - - /*******************************************************************************/ - case NFCIP_ST_TARG_DEP_TX: - - nfcipLogD(" NFCIP(T) Tx PNI: %d txLen: %d \r\n", gNfcip.pni, gNfcip.txBufLen); - ret = nfcipTx( - NFCIP_CMD_DEP_RES, - gNfcip.txBuf, - &gNfcip.txBuf[gNfcip.txBufPaylPos], - gNfcip.txBufLen, - nfcip_PFBIPDU(gNfcip.pni), - NFCIP_NO_FWT); - - /* Clear flags */ - gNfcip.isTxPending = false; - gNfcip.isWait4RTOX = false; - - /* Digital 1.0 14.12.3.4 Increment the current PNI after Tx */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - switch(ret) { - case ERR_NONE: - gNfcip.state = NFCIP_ST_TARG_DEP_RX; /* All OK, goto Rx state */ - break; - - case ERR_PARAM: - default: - gNfcip.state = NFCIP_ST_TARG_DEP_IDLE; /* Upon Tx error, goto IDLE state */ - return ret; - } - /* fall through */ - - /*******************************************************************************/ - case NFCIP_ST_TARG_DEP_RX: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - if(gNfcip.isReqPending) /* if already has Data should be from a DEP from nfcipTargetHandleActivation() */ - { - nfcipLogD(" NFCIP(T) Skipping Rx Using DEP from Activation \r\n"); - - gNfcip.isReqPending = false; - ret = ERR_NONE; - } else { - ret = nfcipDataRx(false); - } - - if(ret != ERR_BUSY) { - ret = nfcipTargetHandleRX(ret, outActRxLen, outIsChaining); - } - - break; - - /*******************************************************************************/ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - return ret; -} - -/*******************************************************************************/ -void rfalNfcDepSetDeactivatingCallback(rfalNfcDepDeactCallback pFunc) { - gNfcip.isDeactivating = pFunc; -} - -/*******************************************************************************/ -void rfalNfcDepInitialize(void) { - nfcipLogD(" NFCIP Ini() \r\n"); - - gNfcip.state = NFCIP_ST_IDLE; - gNfcip.isDeactivating = NULL; - - gNfcip.isTxPending = false; - gNfcip.isWait4RTOX = false; - gNfcip.isReqPending = false; - - gNfcip.cfg.oper = - (RFAL_NFCDEP_OPER_FULL_MI_DIS | RFAL_NFCDEP_OPER_EMPTY_DEP_EN | RFAL_NFCDEP_OPER_ATN_EN | - RFAL_NFCDEP_OPER_RTOX_REQ_EN); - - gNfcip.cfg.did = RFAL_NFCDEP_DID_NO; - gNfcip.cfg.nad = RFAL_NFCDEP_NAD_NO; - - gNfcip.cfg.br = RFAL_NFCDEP_Bx_NO_HIGH_BR; - gNfcip.cfg.bs = RFAL_NFCDEP_Bx_NO_HIGH_BR; - - gNfcip.cfg.lr = RFAL_NFCDEP_LR_254; - gNfcip.fsc = rfalNfcDepLR2FS(gNfcip.cfg.lr); - - gNfcip.cfg.gbLen = 0; - - gNfcip.cfg.fwt = RFAL_NFCDEP_MAX_FWT; - gNfcip.cfg.dFwt = RFAL_NFCDEP_MAX_FWT; - - gNfcip.pni = 0; - - /* Destroy any ongoing RTOX timer*/ - nfcipTimerDestroy(gNfcip.RTOXTimer); - gNfcip.RTOXTimer = 0U; - - gNfcip.PDUTxPos = 0; - gNfcip.PDURxPos = 0; - gNfcip.PDUParam.rxLen = NULL; - gNfcip.PDUParam.rxBuf = NULL; - gNfcip.PDUParam.txBuf = NULL; - - nfcipClearCounters(); -} - -/*******************************************************************************/ -static void nfcipSetDEPParams(const rfalNfcDepDEPParams* DEPParams) { - nfcipLogD(" NFCIP SetDEP() txLen: %d \r\n", DEPParams->txBufLen); - - gNfcip.isTxChaining = DEPParams->txChaining; - gNfcip.txBuf = DEPParams->txBuf; - gNfcip.rxBuf = DEPParams->rxBuf; - gNfcip.txBufLen = DEPParams->txBufLen; - gNfcip.rxBufLen = DEPParams->rxBufLen; - gNfcip.txBufPaylPos = DEPParams->txBufPaylPos; - gNfcip.rxBufPaylPos = DEPParams->rxBufPaylPos; - - if(DEPParams->did != RFAL_NFCDEP_DID_KEEP) { - gNfcip.cfg.did = nfcip_DIDMax(DEPParams->did); - } - - gNfcip.cfg.fwt = DEPParams->fwt; - gNfcip.cfg.dFwt = DEPParams->dFwt; - gNfcip.fsc = DEPParams->fsc; - - if(gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) { - /* If there's any data to be sent go for Tx */ - if(DEPParams->txBufLen > 0U) { - /* Ensure that an RTOX Ack is not being expected at moment */ - if(!gNfcip.isWait4RTOX) { - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return; - } else { - /* If RTOX Ack is expected, signal a pending Tx to be transmitted right after */ - gNfcip.isTxPending = true; - nfcipLogW(" NFCIP(T) Waiting RTOX, queueing outgoing DEP Block \r\n"); - } - } - - /*Digital 1.0 14.12.4.1 In target mode the first PDU MUST be sent by the Initiator */ - gNfcip.state = NFCIP_ST_TARG_DEP_RX; - return; - } - - /* New data TxRx request clear previous error counters for consecutive TxRx without reseting communication/protocol layer*/ - nfcipClearCounters(); - - gNfcip.state = NFCIP_ST_INIT_DEP_TX; -} - -/*******************************************************************************/ -bool rfalNfcDepTargetRcvdATR(void) { - return ( - (gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) && nfcipIsTarget(gNfcip.state) && - (gNfcip.state > NFCIP_ST_TARG_WAIT_ATR)); -} - -/*******************************************************************************/ -bool rfalNfcDepIsAtrReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid3) { - uint8_t msgIt; - - msgIt = 0; - - if((bufLen < RFAL_NFCDEP_ATRREQ_MIN_LEN) || (bufLen > RFAL_NFCDEP_ATRREQ_MAX_LEN)) { - return false; - } - - if(buf[msgIt++] != NFCIP_REQ) { - return false; - } - - if(buf[msgIt++] != (uint8_t)NFCIP_CMD_ATR_REQ) { - return false; - } - - /* Output NFID3 if requested */ - if(nfcid3 != NULL) { - ST_MEMCPY(nfcid3, &buf[RFAL_NFCDEP_ATR_REQ_NFCID3_POS], RFAL_NFCDEP_NFCID3_LEN); - } - - return true; -} - -/*******************************************************************************/ -static ReturnCode nfcipTargetHandleActivation(rfalNfcDepDevice* nfcDepDev, uint8_t* outBRS) { - ReturnCode ret; - uint8_t msgIt; - uint8_t txBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_PSLRES_LEN]; - - /*******************************************************************************/ - /* Check if we are in correct state */ - /*******************************************************************************/ - if(gNfcip.state != NFCIP_ST_TARG_WAIT_ACTV) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Check required parameters */ - /*******************************************************************************/ - if(outBRS == NULL) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Wait and process incoming cmd (PSL / DEP) */ - /*******************************************************************************/ - ret = nfcipDataRx(false); - - if(ret != ERR_NONE) { - return ret; - } - - msgIt = 0; - *outBRS = RFAL_NFCDEP_BRS_MAINTAIN; /* set out BRS to be maintained */ - - msgIt++; /* Skip LEN byte */ - - if(gNfcip.rxBuf[msgIt++] != NFCIP_REQ) { - return ERR_PROTO; - } - - if(gNfcip.rxBuf[msgIt] == (uint8_t)NFCIP_CMD_PSL_REQ) { - msgIt++; - - if(gNfcip.rxBuf[msgIt++] != gNfcip.cfg.did) /* Checking DID */ - { - return ERR_PROTO; - } - - nfcipLogI(" NFCIP(T) PSL REQ rcvd \r\n"); - - *outBRS = gNfcip.rxBuf[msgIt++]; /* assign output BRS value */ - - /* Store FSL(LR) and update current config */ - gNfcip.cfg.lr = (gNfcip.rxBuf[msgIt++] & RFAL_NFCDEP_LR_VAL_MASK); - gNfcip.fsc = rfalNfcDepLR2FS(gNfcip.cfg.lr); - - /*******************************************************************************/ - /* Update NFC-DDE Device info */ - if(nfcDepDev != NULL) { - /* Update Bitrate info */ - /* PRQA S 4342 2 # MISRA 10.5 - Layout of enum rfalBitRate and definition of rfalNfcDepBRS2DSI guarantee no invalid enum values to be created */ - nfcDepDev->info.DSI = (rfalBitRate)rfalNfcDepBRS2DSI( - *outBRS); /* DSI codes the bit rate from Initiator to Target */ - nfcDepDev->info.DRI = (rfalBitRate)rfalNfcDepBRS2DRI( - *outBRS); /* DRI codes the bit rate from Target to Initiator */ - - /* Update Length Reduction and Frame Size */ - nfcDepDev->info.LR = gNfcip.cfg.lr; - nfcDepDev->info.FS = gNfcip.fsc; - - /* Update PPi byte */ - nfcDepDev->activation.Initiator.ATR_REQ.PPi &= ~RFAL_NFCDEP_PP_LR_MASK; - nfcDepDev->activation.Initiator.ATR_REQ.PPi |= rfalNfcDepLR2PP(gNfcip.cfg.lr); - } - - rfalSetBitRate(RFAL_BR_KEEP, gNfcip.nfcDepDev->info.DSI); - - EXIT_ON_ERR(ret, nfcipTx(NFCIP_CMD_PSL_RES, txBuf, NULL, 0, 0, NFCIP_NO_FWT)); - } else { - if(gNfcip.rxBuf[msgIt] == (uint8_t)NFCIP_CMD_DEP_REQ) { - msgIt++; - - /*******************************************************************************/ - /* Digital 1.0 14.12.3.1 PNI must be initialized to 0 */ - if(nfcip_PBF_PNI(gNfcip.rxBuf[msgIt]) != 0U) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Digital 1.0 14.8.2.1 check if DID is expected and match -> Protocol Error */ - if(nfcip_PFBhasDID(gNfcip.rxBuf[msgIt])) { - if(gNfcip.rxBuf[++msgIt] != gNfcip.cfg.did) { - return ERR_PROTO; - } - } else if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) /* DID expected but not rcv */ - { - return ERR_PROTO; - } else { - /* MISRA 15.7 - Empty else */ - } - } - - /* Signal Request pending to be digested on normal Handling (DEP_REQ, DSL_REQ, RLS_REQ) */ - gNfcip.isReqPending = true; - } - - gNfcip.state = NFCIP_ST_TARG_DEP_RX; - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalNfcDepATR(const rfalNfcDepAtrParam* param, rfalNfcDepAtrRes* atrRes, uint8_t* atrResLen) { - ReturnCode ret; - rfalNfcDepConfigs cfg; - uint16_t rxLen; - uint8_t msgIt; - uint8_t txBuf[RFAL_NFCDEP_ATRREQ_MAX_LEN]; - uint8_t rxBuf[NFCIP_ATRRES_BUF_LEN]; - - if((param == NULL) || (atrRes == NULL) || (atrResLen == NULL)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Configure NFC-DEP layer */ - /*******************************************************************************/ - - cfg.did = param->DID; - cfg.nad = param->NAD; - cfg.fwt = RFAL_NFCDEP_MAX_FWT; - cfg.dFwt = RFAL_NFCDEP_MAX_FWT; - cfg.br = param->BR; - cfg.bs = param->BS; - cfg.lr = param->LR; - cfg.to = RFAL_NFCDEP_WT_TRG_MAX; /* Not used in Initiator mode */ - - cfg.gbLen = param->GBLen; - if(cfg.gbLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(cfg.gb, param->GB, cfg.gbLen); - } - - cfg.nfcidLen = param->nfcidLen; - if(cfg.nfcidLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(cfg.nfcid, param->nfcid, cfg.nfcidLen); - } - - cfg.role = RFAL_NFCDEP_ROLE_INITIATOR; - cfg.oper = param->operParam; - cfg.commMode = param->commMode; - - rfalNfcDepInitialize(); - nfcipConfig(&cfg); - - /*******************************************************************************/ - /* Send ATR_REQ */ - /*******************************************************************************/ - - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_ATR_REQ, - txBuf, - nfcipRWTActivation(), - NULL, - 0, - rxBuf, - NFCIP_ATRRES_BUF_LEN, - &rxLen)); - - /*******************************************************************************/ - /* ATR sent, check response */ - /*******************************************************************************/ - msgIt = 0; - rxLen = ((uint16_t)rxBuf[msgIt++] - RFAL_NFCDEP_LEN_LEN); /* use LEN byte */ - - if((rxLen < RFAL_NFCDEP_ATRRES_MIN_LEN) || - (rxLen > RFAL_NFCDEP_ATRRES_MAX_LEN)) /* Checking length: ATR_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != NFCIP_RES) /* Checking if is a response*/ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != (uint8_t)NFCIP_CMD_ATR_RES) /* Checking if is a ATR RES */ - { - return ERR_PROTO; - } - - ST_MEMCPY((uint8_t*)atrRes, (rxBuf + RFAL_NFCDEP_LEN_LEN), rxLen); - *atrResLen = (uint8_t)rxLen; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepPSL(uint8_t BRS, uint8_t FSL) { - ReturnCode ret; - uint16_t rxLen; - uint8_t msgIt; - uint8_t txBuf[NFCIP_PSLREQ_LEN + NFCIP_PSLPAY_LEN]; - uint8_t rxBuf[NFCIP_PSLRES_LEN]; - - msgIt = NFCIP_PSLREQ_LEN; - - txBuf[msgIt++] = BRS; - txBuf[msgIt++] = FSL; - - /*******************************************************************************/ - /* Send PSL REQ and wait for response */ - /*******************************************************************************/ - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_PSL_REQ, - txBuf, - nfcipRWTActivation(), - &txBuf[NFCIP_PSLREQ_LEN], - (msgIt - NFCIP_PSLREQ_LEN), - rxBuf, - NFCIP_PSLRES_LEN, - &rxLen)); - - /*******************************************************************************/ - /* PSL sent, check response */ - /*******************************************************************************/ - msgIt = 0; - rxLen = (uint16_t)(rxBuf[msgIt++]); /* use LEN byte */ - - if(rxLen < NFCIP_PSLRES_LEN) /* Checking length: LEN + RLS_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != NFCIP_RES) /* Checking if is a response */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != (uint8_t)NFCIP_CMD_PSL_RES) /* Checking if is a PSL RES */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != gNfcip.cfg.did) /* Checking DID */ - { - return ERR_PROTO; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepDSL(void) { - ReturnCode ret; - uint8_t txBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_DSLREQ_LEN]; - uint8_t rxBuf[NFCIP_DSLRES_LEN]; - uint8_t rxMsgIt; - uint16_t rxLen = 0; - - if(gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) { - return ERR_NONE; /* Target has no deselect procedure */ - } - - /* Repeating a DSL REQ is optional, not doing it */ - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_DSL_REQ, - txBuf, - nfcipRWTActivation(), - NULL, - 0, - rxBuf, - (uint16_t)sizeof(rxBuf), - &rxLen)); - - /*******************************************************************************/ - rxMsgIt = 0; - - if(rxBuf[rxMsgIt++] < NFCIP_DSLRES_MIN) /* Checking length: LEN + DSL_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != NFCIP_RES) /* Checking if is a response */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != (uint8_t)NFCIP_CMD_DSL_RES) /* Checking if is DSL RES */ - { - return ERR_PROTO; - } - - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if(rxBuf[rxMsgIt++] != gNfcip.cfg.did) { - return ERR_PROTO; - } - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepRLS(void) { - ReturnCode ret; - uint8_t txBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_RLSREQ_LEN]; - uint8_t rxBuf[NFCIP_RLSRES_LEN]; - uint8_t rxMsgIt; - uint16_t rxLen = 0; - - if(gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) /* Target has no release procedure */ - { - return ERR_NONE; - } - - /* Repeating a RLS REQ is optional, not doing it */ - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_RLS_REQ, - txBuf, - nfcipRWTActivation(), - NULL, - 0, - rxBuf, - (uint16_t)sizeof(rxBuf), - &rxLen)); - - /*******************************************************************************/ - rxMsgIt = 0; - - if(rxBuf[rxMsgIt++] < NFCIP_RLSRES_MIN) /* Checking length: LEN + RLS_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != NFCIP_RES) /* Checking if is a response */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != (uint8_t)NFCIP_CMD_RLS_RES) /* Checking if is RLS RES */ - { - return ERR_PROTO; - } - - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if(rxBuf[rxMsgIt++] != gNfcip.cfg.did) { - return ERR_PROTO; - } - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepInitiatorHandleActivation( - rfalNfcDepAtrParam* param, - rfalBitRate desiredBR, - rfalNfcDepDevice* nfcDepDev) { - ReturnCode ret; - uint8_t maxRetyrs; - uint8_t PSL_BRS; - uint8_t PSL_FSL; - bool sendPSL; - - if((param == NULL) || (nfcDepDev == NULL)) { - return ERR_PARAM; - } - - param->NAD = RFAL_NFCDEP_NAD_NO; /* Digital 1.1 16.6.2.9 Initiator SHALL NOT use NAD */ - maxRetyrs = NFCIP_ATR_RETRY_MAX; - - /*******************************************************************************/ - /* Send ATR REQ and wait for response */ - /*******************************************************************************/ - do { /* Upon transmission error ATR REQ should be retried */ - - ret = rfalNfcDepATR( - param, - &nfcDepDev->activation.Target.ATR_RES, - &nfcDepDev->activation.Target.ATR_RESLen); - - if(nfcipIsTransmissionError(ret)) { - continue; - } - break; - } while((maxRetyrs--) != 0U); - - if(ret != ERR_NONE) { - return ret; - } - - /*******************************************************************************/ - /* Compute NFC-DEP device with ATR_RES */ - /*******************************************************************************/ - nfcDepDev->info.GBLen = (nfcDepDev->activation.Target.ATR_RESLen - RFAL_NFCDEP_ATRRES_MIN_LEN); - nfcDepDev->info.DID = nfcDepDev->activation.Target.ATR_RES.DID; - nfcDepDev->info.NAD = - RFAL_NFCDEP_NAD_NO; /* Digital 1.1 16.6.3.11 Initiator SHALL ignore b1 of PPt */ - nfcDepDev->info.LR = rfalNfcDepPP2LR(nfcDepDev->activation.Target.ATR_RES.PPt); - nfcDepDev->info.FS = rfalNfcDepLR2FS(nfcDepDev->info.LR); - nfcDepDev->info.WT = (nfcDepDev->activation.Target.ATR_RES.TO & RFAL_NFCDEP_WT_MASK); - nfcDepDev->info.FWT = rfalNfcDepCalculateRWT(nfcDepDev->info.WT); - nfcDepDev->info.dFWT = RFAL_NFCDEP_WT_DELTA; - - rfalGetBitRate(&nfcDepDev->info.DSI, &nfcDepDev->info.DRI); - - /*******************************************************************************/ - /* Check if a PSL needs to be sent */ - /*******************************************************************************/ - sendPSL = false; - PSL_BRS = rfalNfcDepDx2BRS( - nfcDepDev->info.DSI); /* Set current bit rate divisor on both directions */ - PSL_FSL = nfcDepDev->info.LR; /* Set current Frame Size */ - - /* Activity 1.0 9.4.4.15 & 9.4.6.3 NFC-DEP Activation PSL - * Activity 2.0 9.4.4.17 & 9.4.6.6 NFC-DEP Activation PSL - * - * PSL_REQ shall only be sent if desired bit rate is different from current (Activity 1.0) - * PSL_REQ shall be sent to update LR or bit rate (Activity 2.0) - * */ - -#if 0 /* PSL due to LR is disabled, can be enabled if desired*/ - /*******************************************************************************/ - /* Check Frame Size */ - /*******************************************************************************/ - if( gNfcip.cfg.lr < nfcDepDev->info.LR ) /* If our Length reduction is smaller */ - { - sendPSL = true; - - nfcDepDev->info.LR = MIN( nfcDepDev->info.LR, gNfcip.cfg.lr ); - - gNfcip.cfg.lr = nfcDepDev->info.LR; /* Update nfcip LR to be used */ - gNfcip.fsc = rfalNfcDepLR2FS( gNfcip.cfg.lr ); /* Update nfcip FSC to be used */ - - PSL_FSL = gNfcip.cfg.lr; /* Set LR to be sent */ - - nfcipLogI( " NFCIP(I) Frame Size differ, PSL new fsc: %d \r\n", gNfcip.fsc ); - } -#endif - - /*******************************************************************************/ - /* Check Baud rates */ - /*******************************************************************************/ - if((nfcDepDev->info.DSI != desiredBR) && - (desiredBR != RFAL_BR_KEEP)) /* if desired BR is different */ - { - if(nfcipDxIsSupported( - (uint8_t)desiredBR, - nfcDepDev->activation.Target.ATR_RES.BRt, - nfcDepDev->activation.Target.ATR_RES - .BSt)) /* if desired BR is supported */ /* MISRA 13.5 */ - { - sendPSL = true; - PSL_BRS = rfalNfcDepDx2BRS(desiredBR); - - nfcipLogI(" NFCIP(I) BR differ, PSL BR: 0x%02X \r\n", PSL_BRS); - } - } - - /*******************************************************************************/ - if(sendPSL) { - /*******************************************************************************/ - /* Send PSL REQ and wait for response */ - /*******************************************************************************/ - EXIT_ON_ERR(ret, rfalNfcDepPSL(PSL_BRS, PSL_FSL)); - - /* Check if bit rate has been changed */ - if(nfcDepDev->info.DSI != desiredBR) { - /* Check if device was in Passive NFC-A and went to higher bit rates, use NFC-F */ - if((nfcDepDev->info.DSI == RFAL_BR_106) && - (gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_PASSIVE)) { -#if RFAL_FEATURE_NFCF - /* If Passive initialize NFC-F module */ - rfalNfcfPollerInitialize(desiredBR); -#else /* RFAL_FEATURE_NFCF */ - return ERR_NOTSUPP; -#endif /* RFAL_FEATURE_NFCF */ - } - - nfcDepDev->info.DRI = desiredBR; /* DSI Bit Rate coding from Initiator to Target */ - nfcDepDev->info.DSI = desiredBR; /* DRI Bit Rate coding from Target to Initiator */ - - rfalSetBitRate(nfcDepDev->info.DSI, nfcDepDev->info.DRI); - } - - return ERR_NONE; /* PSL has been sent */ - } - - return ERR_NONE; /* No PSL has been sent */ -} - -/*******************************************************************************/ -uint32_t rfalNfcDepCalculateRWT(uint8_t wt) { - /* Digital 1.0 14.6.3.8 & Digital 1.1 16.6.3.9 */ - /* Digital 1.1 16.6.3.9 treat all RFU values as WT=14 */ - uint8_t responseWaitTime = MIN(RFAL_NFCDEP_WT_INI_MAX, wt); - - return (uint32_t)rfalNfcDepWT2RWT(responseWaitTime); -} - -/*******************************************************************************/ -static ReturnCode nfcipDataTx(uint8_t* txBuf, uint16_t txBufLen, uint32_t fwt) { - return rfalTransceiveBlockingTx( - txBuf, - txBufLen, - gNfcip.rxBuf, - gNfcip.rxBufLen, - gNfcip.rxRcvdLen, - (RFAL_TXRX_FLAGS_DEFAULT | (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_ON), - ((fwt == NFCIP_NO_FWT) ? RFAL_FWT_NONE : fwt)); -} - -/*******************************************************************************/ -static ReturnCode nfcipDataRx(bool blocking) { - ReturnCode ret; - - /* Perform Rx either blocking or non-blocking */ - if(blocking) { - ret = rfalTransceiveBlockingRx(); - } else { - ret = rfalGetTransceiveStatus(); - } - - if(ret != ERR_BUSY) { - if(gNfcip.rxRcvdLen != NULL) { - (*gNfcip.rxRcvdLen) = rfalConvBitsToBytes(*gNfcip.rxRcvdLen); - - if((ret == ERR_NONE) && (gNfcip.rxBuf != NULL)) { - /* Digital 1.1 16.4.1.3 - Length byte LEN SHALL have a value between 3 and 255 -> otherwise treat as Transmission Error * - * - Ensure that actual received and frame length do match, otherwise treat as Transmission error */ - if((*gNfcip.rxRcvdLen != (uint16_t)*gNfcip.rxBuf) || - (*gNfcip.rxRcvdLen < RFAL_NFCDEP_LEN_MIN) || - (*gNfcip.rxRcvdLen > RFAL_NFCDEP_LEN_MAX)) { - return ERR_FRAMING; - } - } - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepListenStartActivation( - const rfalNfcDepTargetParam* param, - const uint8_t* atrReq, - uint16_t atrReqLength, - rfalNfcDepListenActvParam rxParam) { - ReturnCode ret; - rfalNfcDepConfigs cfg; - - if((param == NULL) || (atrReq == NULL) || (rxParam.rxLen == NULL)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Check whether is a valid ATR_REQ Compute NFC-DEP device */ - if(!rfalNfcDepIsAtrReq(atrReq, atrReqLength, NULL)) { - return ERR_PARAM; - } - - rxParam.nfcDepDev->activation.Initiator.ATR_REQLen = - (uint8_t)atrReqLength; /* nfcipIsAtrReq() is already checking Min and Max buffer lengths */ - if(atrReqLength > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY( - (uint8_t*)&rxParam.nfcDepDev->activation.Initiator.ATR_REQ, atrReq, atrReqLength); - } - - rxParam.nfcDepDev->info.GBLen = (uint8_t)(atrReqLength - RFAL_NFCDEP_ATRREQ_MIN_LEN); - rxParam.nfcDepDev->info.DID = rxParam.nfcDepDev->activation.Initiator.ATR_REQ.DID; - rxParam.nfcDepDev->info.NAD = - RFAL_NFCDEP_NAD_NO; /* Digital 1.1 16.6.2.9 Initiator SHALL NOT use NAD */ - rxParam.nfcDepDev->info.LR = - rfalNfcDepPP2LR(rxParam.nfcDepDev->activation.Initiator.ATR_REQ.PPi); - rxParam.nfcDepDev->info.FS = rfalNfcDepLR2FS(rxParam.nfcDepDev->info.LR); - rxParam.nfcDepDev->info.WT = 0; - rxParam.nfcDepDev->info.FWT = NFCIP_NO_FWT; - rxParam.nfcDepDev->info.dFWT = NFCIP_NO_FWT; - - rfalGetBitRate(&rxParam.nfcDepDev->info.DSI, &rxParam.nfcDepDev->info.DRI); - - /* Store Device Info location, updated upon a PSL */ - gNfcip.nfcDepDev = rxParam.nfcDepDev; - - /*******************************************************************************/ - cfg.did = rxParam.nfcDepDev->activation.Initiator.ATR_REQ.DID; - cfg.nad = RFAL_NFCDEP_NAD_NO; - - cfg.fwt = RFAL_NFCDEP_MAX_FWT; - cfg.dFwt = RFAL_NFCDEP_MAX_FWT; - - cfg.br = param->brt; - cfg.bs = param->bst; - - cfg.lr = rfalNfcDepPP2LR(param->ppt); - - cfg.gbLen = param->GBtLen; - if(cfg.gbLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(cfg.gb, param->GBt, cfg.gbLen); - } - - cfg.nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - ST_MEMCPY(cfg.nfcid, param->nfcid3, RFAL_NFCDEP_NFCID3_LEN); - - cfg.to = param->to; - - cfg.role = RFAL_NFCDEP_ROLE_TARGET; - cfg.oper = param->operParam; - cfg.commMode = param->commMode; - - rfalNfcDepInitialize(); - nfcipConfig(&cfg); - - /*******************************************************************************/ - /* Reply with ATR RES to Initiator */ - /*******************************************************************************/ - gNfcip.rxBuf = (uint8_t*)rxParam.rxBuf; - gNfcip.rxBufLen = sizeof(rfalNfcDepBufFormat); - gNfcip.rxRcvdLen = rxParam.rxLen; - gNfcip.rxBufPaylPos = RFAL_NFCDEP_DEPREQ_HEADER_LEN; - gNfcip.isChaining = rxParam.isRxChaining; - gNfcip.txBufPaylPos = RFAL_NFCDEP_DEPREQ_HEADER_LEN; - - EXIT_ON_ERR(ret, nfcipTx(NFCIP_CMD_ATR_RES, (uint8_t*)gNfcip.rxBuf, NULL, 0, 0, NFCIP_NO_FWT)); - - gNfcip.state = NFCIP_ST_TARG_WAIT_ACTV; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepListenGetActivationStatus(void) { - ReturnCode err; - uint8_t BRS; - - BRS = RFAL_NFCDEP_BRS_MAINTAIN; - - err = nfcipTargetHandleActivation(gNfcip.nfcDepDev, &BRS); - - switch(err) { - case ERR_NONE: - - if(BRS != RFAL_NFCDEP_BRS_MAINTAIN) { - /* DSI codes the bit rate from Initiator to Target */ - /* DRI codes the bit rate from Target to Initiator */ - - if(gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_ACTIVE) { - EXIT_ON_ERR( - err, - rfalSetMode( - RFAL_MODE_LISTEN_ACTIVE_P2P, - gNfcip.nfcDepDev->info.DRI, - gNfcip.nfcDepDev->info.DSI)); - } else { - EXIT_ON_ERR( - err, - rfalSetMode( - ((RFAL_BR_106 == gNfcip.nfcDepDev->info.DRI) ? RFAL_MODE_LISTEN_NFCA : - RFAL_MODE_LISTEN_NFCF), - gNfcip.nfcDepDev->info.DRI, - gNfcip.nfcDepDev->info.DSI)); - } - } - break; - - case ERR_BUSY: - // do nothing - break; - - case ERR_PROTO: - default: - // re-enable receiving of data - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - break; - } - - return err; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepStartTransceive(const rfalNfcDepTxRxParam* param) { - rfalNfcDepDEPParams nfcDepParams; - - nfcDepParams.txBuf = (uint8_t*)param->txBuf; - nfcDepParams.txBufLen = param->txBufLen; - nfcDepParams.txChaining = param->isTxChaining; - nfcDepParams.txBufPaylPos = - RFAL_NFCDEP_DEPREQ_HEADER_LEN; /* position in txBuf where actual outgoing data is located */ - nfcDepParams.did = RFAL_NFCDEP_DID_KEEP; - nfcDepParams.rxBufPaylPos = RFAL_NFCDEP_DEPREQ_HEADER_LEN; - nfcDepParams.rxBuf = (uint8_t*)param->rxBuf; - nfcDepParams.rxBufLen = sizeof(rfalNfcDepBufFormat); - nfcDepParams.fsc = param->FSx; - nfcDepParams.fwt = param->FWT; - nfcDepParams.dFwt = param->dFWT; - - gNfcip.rxRcvdLen = param->rxLen; - gNfcip.isChaining = param->isRxChaining; - - nfcipSetDEPParams(&nfcDepParams); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepGetTransceiveStatus(void) { - return nfcipRun(gNfcip.rxRcvdLen, gNfcip.isChaining); -} - -/*******************************************************************************/ -static void rfalNfcDepPdu2BLockParam( - rfalNfcDepPduTxRxParam pduParam, - rfalNfcDepTxRxParam* blockParam, - uint16_t txPos, - uint16_t rxPos) { - uint16_t maxInfLen; - - NO_WARNING(rxPos); /* Keep this param for future use */ - - blockParam->DID = pduParam.DID; - blockParam->FSx = pduParam.FSx; - blockParam->FWT = pduParam.FWT; - blockParam->dFWT = pduParam.dFWT; - - /* Calculate max INF/Payload to be sent to other device */ - maxInfLen = (blockParam->FSx - (RFAL_NFCDEP_HEADER + RFAL_NFCDEP_DEP_PFB_LEN)); - maxInfLen += ((blockParam->DID != RFAL_NFCDEP_DID_NO) ? RFAL_NFCDEP_DID_LEN : 0U); - - if((pduParam.txBufLen - txPos) > maxInfLen) { - blockParam->isTxChaining = true; - blockParam->txBufLen = maxInfLen; - } else { - blockParam->isTxChaining = false; - blockParam->txBufLen = (pduParam.txBufLen - txPos); - } - - /* TxBuf is moved to the beginning for every Block */ - blockParam->txBuf = - (rfalNfcDepBufFormat*)pduParam - .txBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - blockParam->rxBuf = - pduParam - .tmpBuf; /* Simply using the pdu buffer is not possible because of current ACK handling */ - blockParam->isRxChaining = &gNfcip.isPDURxChaining; - blockParam->rxLen = pduParam.rxLen; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepStartPduTransceive(rfalNfcDepPduTxRxParam param) { - rfalNfcDepTxRxParam txRxParam; - - /* Initialize and store APDU context */ - gNfcip.PDUParam = param; - gNfcip.PDUTxPos = 0; - gNfcip.PDURxPos = 0; - - /* Convert PDU TxRxParams to Block TxRxParams */ - rfalNfcDepPdu2BLockParam(gNfcip.PDUParam, &txRxParam, gNfcip.PDUTxPos, gNfcip.PDURxPos); - - return rfalNfcDepStartTransceive(&txRxParam); -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepGetPduTransceiveStatus(void) { - ReturnCode ret; - rfalNfcDepTxRxParam txRxParam; - - ret = rfalNfcDepGetTransceiveStatus(); - switch(ret) { - /*******************************************************************************/ - case ERR_NONE: - - /* Check if we are still doing chaining on Tx */ - if(gNfcip.isTxChaining) { - /* Add already Tx bytes */ - gNfcip.PDUTxPos += gNfcip.txBufLen; - - /* Convert APDU TxRxParams to I-Block TxRxParams */ - rfalNfcDepPdu2BLockParam( - gNfcip.PDUParam, &txRxParam, gNfcip.PDUTxPos, gNfcip.PDURxPos); - - if(txRxParam.txBufLen > 0U) /* MISRA 21.18 */ - { - /* Move next Block to beginning of APDU Tx buffer */ - ST_MEMCPY( - gNfcip.PDUParam.txBuf->pdu, - &gNfcip.PDUParam.txBuf->pdu[gNfcip.PDUTxPos], - txRxParam.txBufLen); - } - - EXIT_ON_ERR(ret, rfalNfcDepStartTransceive(&txRxParam)); - return ERR_BUSY; - } - - /* PDU TxRx is done */ - /* fall through */ - - /*******************************************************************************/ - case ERR_AGAIN: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Check if no PDU transceive has been started before (data from rfalNfcDepListenStartActivation) */ - if(gNfcip.PDUParam.rxLen == NULL) { - /* In Listen mode first chained packet cannot be retrieved via APDU interface */ - if(ret == ERR_AGAIN) { - return ERR_NOTSUPP; - } - - /* TxRx is complete and full data is already available */ - return ERR_NONE; - } - - if((*gNfcip.PDUParam.rxLen) > 0U) /* MISRA 21.18 */ - { - /* Ensure that data in tmpBuf still fits into PDU buffer */ - if((uint16_t)((uint16_t)gNfcip.PDURxPos + (*gNfcip.PDUParam.rxLen)) > - RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN) { - return ERR_NOMEM; - } - - /* Copy chained packet from tmp buffer to PDU buffer */ - ST_MEMCPY( - &gNfcip.PDUParam.rxBuf->pdu[gNfcip.PDURxPos], - gNfcip.PDUParam.tmpBuf->inf, - *gNfcip.PDUParam.rxLen); - gNfcip.PDURxPos += *gNfcip.PDUParam.rxLen; - } - - /* Update output param rxLen */ - *gNfcip.PDUParam.rxLen = gNfcip.PDURxPos; - - /* Wait for following Block or PDU TxRx is done */ - return ((ret == ERR_AGAIN) ? ERR_BUSY : ERR_NONE); - - /*******************************************************************************/ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - return ret; -} - -#endif /* RFAL_FEATURE_NFC_DEP */ diff --git a/lib/ST25RFAL002/source/rfal_nfca.c b/lib/ST25RFAL002/source/rfal_nfca.c deleted file mode 100644 index d4e4c93880..0000000000 --- a/lib/ST25RFAL002/source/rfal_nfca.c +++ /dev/null @@ -1,886 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfca.c - * - * \author Gustavo Patricio - * - * \brief Provides several NFC-A convenience methods and definitions - * - * It provides a Poller (ISO14443A PCD) interface and as well as - * some NFC-A Listener (ISO14443A PICC) helpers. - * - * The definitions and helpers methods provided by this module are only - * up to ISO14443-3 layer - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfca.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCA -#define RFAL_FEATURE_NFCA false /* NFC-A module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCA - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCA_SLP_FWT \ - rfalConvMsTo1fc(1) /*!< Check 1ms for any modulation ISO14443-3 6.4.3 */ -#define RFAL_NFCA_SLP_CMD 0x50U /*!< SLP cmd (byte1) Digital 1.1 6.9.1 & Table 20 */ -#define RFAL_NFCA_SLP_BYTE2 0x00U /*!< SLP byte2 Digital 1.1 6.9.1 & Table 20 */ -#define RFAL_NFCA_SLP_CMD_POS 0U /*!< SLP cmd position Digital 1.1 6.9.1 & Table 20 */ -#define RFAL_NFCA_SLP_BYTE2_POS 1U /*!< SLP byte2 position Digital 1.1 6.9.1 & Table 20 */ - -#define RFAL_NFCA_SDD_CT 0x88U /*!< Cascade Tag value Digital 1.1 6.7.2 */ -#define RFAL_NFCA_SDD_CT_LEN 1U /*!< Cascade Tag length */ - -#define RFAL_NFCA_SLP_REQ_LEN 2U /*!< SLP_REQ length */ - -#define RFAL_NFCA_SEL_CMD_LEN 1U /*!< SEL_CMD length */ -#define RFAL_NFCA_SEL_PAR_LEN 1U /*!< SEL_PAR length */ -#define RFAL_NFCA_SEL_SELPAR \ - rfalNfcaSelPar(7U, 0U) /*!< SEL_PAR on Select is always with 4 data/nfcid */ -#define RFAL_NFCA_BCC_LEN 1U /*!< BCC length */ - -#define RFAL_NFCA_SDD_REQ_LEN \ - (RFAL_NFCA_SEL_CMD_LEN + RFAL_NFCA_SEL_PAR_LEN) /*!< SDD_REQ length */ -#define RFAL_NFCA_SDD_RES_LEN \ - (RFAL_NFCA_CASCADE_1_UID_LEN + RFAL_NFCA_BCC_LEN) /*!< SDD_RES length */ - -#define RFAL_NFCA_T_RETRANS 5U /*!< t RETRANSMISSION [3, 33]ms EMVCo 2.6 A.5 */ -#define RFAL_NFCA_N_RETRANS 2U /*!< Number of retries EMVCo 2.6 9.6.1.3 */ - -/*! SDD_REQ (Select) Cascade Levels */ -enum { - RFAL_NFCA_SEL_CASCADE_L1 = 0, /*!< SDD_REQ Cascade Level 1 */ - RFAL_NFCA_SEL_CASCADE_L2 = 1, /*!< SDD_REQ Cascade Level 2 */ - RFAL_NFCA_SEL_CASCADE_L3 = 2 /*!< SDD_REQ Cascade Level 3 */ -}; - -/*! SDD_REQ (Select) request Cascade Level command Digital 1.1 Table 15 */ -enum { - RFAL_NFCA_CMD_SEL_CL1 = 0x93, /*!< SDD_REQ command Cascade Level 1 */ - RFAL_NFCA_CMD_SEL_CL2 = 0x95, /*!< SDD_REQ command Cascade Level 2 */ - RFAL_NFCA_CMD_SEL_CL3 = 0x97, /*!< SDD_REQ command Cascade Level 3 */ -}; - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -#define rfalNfcaSelPar(nBy, nbi) \ - (uint8_t)( \ - (((nBy) << 4U) & 0xF0U) | \ - ((nbi)&0x0FU)) /*!< Calculates SEL_PAR with the bytes/bits to be sent */ -#define rfalNfcaCLn2SELCMD(cl) \ - (uint8_t)( \ - (uint8_t)(RFAL_NFCA_CMD_SEL_CL1) + \ - (2U * (cl))) /*!< Calculates SEL_CMD with the given cascade level */ -#define rfalNfcaNfcidLen2CL(len) \ - ((len) / 5U) /*!< Calculates cascade level by the NFCID length */ -#define rfalNfcaRunBlocking(e, fn) \ - do { \ - (e) = (fn); \ - rfalWorker(); \ - } while((e) == ERR_BUSY) /*!< Macro used for the blocking methods */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Colission Resolution states */ -typedef enum { - RFAL_NFCA_CR_IDLE, /*!< IDLE state */ - RFAL_NFCA_CR_CL, /*!< New Cascading Level state */ - RFAL_NFCA_CR_SDD, /*!< Perform anticollsion state */ - RFAL_NFCA_CR_SEL, /*!< Perform CL Selection state */ - RFAL_NFCA_CR_DONE /*!< Collision Resolution done state */ -} colResState; - -/*! Colission Resolution context */ -typedef struct { - uint8_t devLimit; /*!< Device limit to be used */ - rfalComplianceMode compMode; /*!< Compliancy mode to be used */ - rfalNfcaListenDevice* - nfcaDevList; /*!< Location of the device list */ - uint8_t* devCnt; /*!< Location of the device counter */ - bool collPending; /*!< Collision pending flag */ - - bool* collPend; /*!< Location of collision pending flag (Single CR) */ - rfalNfcaSelReq selReq; /*!< SelReqused during anticollision (Single CR) */ - rfalNfcaSelRes* selRes; /*!< Location to place of the SEL_RES(SAK) (Single CR) */ - uint8_t* nfcId1; /*!< Location to place the NFCID1 (Single CR) */ - uint8_t* nfcId1Len; /*!< Location to place the NFCID1 length (Single CR) */ - uint8_t cascadeLv; /*!< Current Cascading Level (Single CR) */ - colResState state; /*!< Single Collision Resolution state (Single CR) */ - uint8_t bytesTxRx; /*!< TxRx bytes used during anticollision loop (Single CR) */ - uint8_t bitsTxRx; /*!< TxRx bits used during anticollision loop (Single CR) */ - uint16_t rxLen; - uint32_t tmrFDT; /*!< FDT timer used between SED_REQs (Single CR) */ - uint8_t retries; /*!< Retries to be performed upon a timeout error (Single CR)*/ - uint8_t backtrackCnt; /*!< Backtrack retries (Single CR) */ - bool doBacktrack; /*!< Backtrack flag (Single CR) */ -} colResParams; - -/*! RFAL NFC-A instance */ -typedef struct { - colResParams CR; /*!< Collision Resolution context */ -} rfalNfca; - -/*! SLP_REQ (HLTA) format Digital 1.1 6.9.1 & Table 20 */ -typedef struct { - uint8_t frame[RFAL_NFCA_SLP_REQ_LEN]; /*!< SLP: 0x50 0x00 */ -} rfalNfcaSlpReq; - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ -static rfalNfca gNfca; /*!< RFAL NFC-A instance */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static uint8_t rfalNfcaCalculateBcc(const uint8_t* buf, uint8_t bufLen); -static ReturnCode rfalNfcaPollerStartSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len); -static ReturnCode rfalNfcaPollerGetSingleCollisionResolutionStatus(void); - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -static uint8_t rfalNfcaCalculateBcc(const uint8_t* buf, uint8_t bufLen) { - uint8_t i; - uint8_t BCC; - - BCC = 0; - - /* BCC is XOR over first 4 bytes of the SDD_RES Digital 1.1 6.7.2 */ - for(i = 0; i < bufLen; i++) { - BCC ^= buf[i]; - } - - return BCC; -} - -/*******************************************************************************/ -static ReturnCode rfalNfcaPollerStartSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len) { - /* Check parameters */ - if((collPending == NULL) || (selRes == NULL) || (nfcId1 == NULL) || (nfcId1Len == NULL)) { - return ERR_PARAM; - } - - /* Initialize output parameters */ - *collPending = false; /* Activity 1.1 9.3.4.6 */ - *nfcId1Len = 0; - ST_MEMSET(nfcId1, 0x00, RFAL_NFCA_CASCADE_3_UID_LEN); - - /* Save parameters */ - gNfca.CR.devLimit = devLimit; - gNfca.CR.collPend = collPending; - gNfca.CR.selRes = selRes; - gNfca.CR.nfcId1 = nfcId1; - gNfca.CR.nfcId1Len = nfcId1Len; - - platformTimerDestroy(gNfca.CR.tmrFDT); - gNfca.CR.tmrFDT = 0U; - gNfca.CR.retries = RFAL_NFCA_N_RETRANS; - gNfca.CR.cascadeLv = (uint8_t)RFAL_NFCA_SEL_CASCADE_L1; - gNfca.CR.state = RFAL_NFCA_CR_CL; - - gNfca.CR.doBacktrack = false; - gNfca.CR.backtrackCnt = 3U; - - return ERR_NONE; -} - -/*******************************************************************************/ -static ReturnCode rfalNfcaPollerGetSingleCollisionResolutionStatus(void) { - ReturnCode ret; - uint8_t collBit = 1U; /* standards mandate or recommend collision bit to be set to One. */ - - /* Check if FDT timer is still running */ - if(!platformTimerIsExpired(gNfca.CR.tmrFDT) && (gNfca.CR.tmrFDT != 0U)) { - return ERR_BUSY; - } - - /*******************************************************************************/ - /* Go through all Cascade Levels Activity 1.1 9.3.4 */ - if(gNfca.CR.cascadeLv > (uint8_t)RFAL_NFCA_SEL_CASCADE_L3) { - return ERR_INTERNAL; - } - - switch(gNfca.CR.state) { - /*******************************************************************************/ - case RFAL_NFCA_CR_CL: - - /* Initialize the SDD_REQ to send for the new cascade level */ - ST_MEMSET((uint8_t*)&gNfca.CR.selReq, 0x00, sizeof(rfalNfcaSelReq)); - - gNfca.CR.bytesTxRx = RFAL_NFCA_SDD_REQ_LEN; - gNfca.CR.bitsTxRx = 0U; - gNfca.CR.state = RFAL_NFCA_CR_SDD; - - /* fall through */ - - /*******************************************************************************/ - case RFAL_NFCA_CR_SDD: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Calculate SEL_CMD and SEL_PAR with the bytes/bits to be sent */ - gNfca.CR.selReq.selCmd = rfalNfcaCLn2SELCMD(gNfca.CR.cascadeLv); - gNfca.CR.selReq.selPar = rfalNfcaSelPar(gNfca.CR.bytesTxRx, gNfca.CR.bitsTxRx); - - /* Send SDD_REQ (Anticollision frame) */ - ret = rfalISO14443ATransceiveAnticollisionFrame( - (uint8_t*)&gNfca.CR.selReq, - &gNfca.CR.bytesTxRx, - &gNfca.CR.bitsTxRx, - &gNfca.CR.rxLen, - RFAL_NFCA_FDTMIN); - - /* Retry upon timeout EMVCo 2.6 9.6.1.3 */ - if((ret == ERR_TIMEOUT) && (gNfca.CR.devLimit == 0U) && (gNfca.CR.retries != 0U)) { - gNfca.CR.retries--; - platformTimerDestroy(gNfca.CR.tmrFDT); - gNfca.CR.tmrFDT = platformTimerCreate(RFAL_NFCA_T_RETRANS); - break; - } - - /* Covert rxLen into bytes */ - gNfca.CR.rxLen = rfalConvBitsToBytes(gNfca.CR.rxLen); - - if((ret == ERR_TIMEOUT) && (gNfca.CR.backtrackCnt != 0U) && (!gNfca.CR.doBacktrack) && - !((RFAL_NFCA_SDD_REQ_LEN == gNfca.CR.bytesTxRx) && (0U == gNfca.CR.bitsTxRx))) { - /* In multiple card scenarios it may always happen that some - * collisions of a weaker tag go unnoticed. If then a later - * collision is recognized and the strong tag has a 0 at the - * collision position then no tag will respond. Catch this - * corner case and then try with the bit being sent as zero. */ - rfalNfcaSensRes sensRes; - ret = ERR_RF_COLLISION; - rfalNfcaPollerCheckPresence(RFAL_14443A_SHORTFRAME_CMD_REQA, &sensRes); - /* Algorithm below does a post-increment, decrement to go back to current position */ - if(0U == gNfca.CR.bitsTxRx) { - gNfca.CR.bitsTxRx = 7; - gNfca.CR.bytesTxRx--; - } else { - gNfca.CR.bitsTxRx--; - } - collBit = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] & (1U << gNfca.CR.bitsTxRx)); - collBit = (uint8_t)((0U == collBit) ? 1U : 0U); // invert the collision bit - gNfca.CR.doBacktrack = true; - gNfca.CR.backtrackCnt--; - } else { - gNfca.CR.doBacktrack = false; - } - - if(ret == ERR_RF_COLLISION) { - /* Check received length */ - if((gNfca.CR.bytesTxRx + ((gNfca.CR.bitsTxRx != 0U) ? 1U : 0U)) > - (RFAL_NFCA_SDD_RES_LEN + RFAL_NFCA_SDD_REQ_LEN)) { - return ERR_PROTO; - } - - if(((gNfca.CR.bytesTxRx + ((gNfca.CR.bitsTxRx != 0U) ? 1U : 0U)) > - (RFAL_NFCA_CASCADE_1_UID_LEN + RFAL_NFCA_SDD_REQ_LEN)) && - (gNfca.CR.backtrackCnt != 0U)) { /* Collision in BCC: Anticollide only UID part */ - gNfca.CR.backtrackCnt--; - gNfca.CR.bytesTxRx = RFAL_NFCA_CASCADE_1_UID_LEN + RFAL_NFCA_SDD_REQ_LEN - 1U; - gNfca.CR.bitsTxRx = 7; - collBit = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] & (1U << gNfca.CR.bitsTxRx)); /* Not a real collision, extract the actual bit for the subsequent code */ - } - - if((gNfca.CR.devLimit == 0U) && !(*gNfca.CR.collPend)) { - /* Activity 1.0 & 1.1 9.3.4.12: If CON_DEVICES_LIMIT has a value of 0, then - * NFC Forum Device is configured to perform collision detection only */ - *gNfca.CR.collPend = true; - return ERR_IGNORE; - } - - *gNfca.CR.collPend = true; - - /* Set and select the collision bit, with the number of bytes/bits successfully TxRx */ - if(collBit != 0U) { - ((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] | (1U << gNfca.CR.bitsTxRx)); /* MISRA 10.3 */ - } else { - ((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] & ~(1U << gNfca.CR.bitsTxRx)); /* MISRA 10.3 */ - } - - gNfca.CR.bitsTxRx++; - - /* Check if number of bits form a byte */ - if(gNfca.CR.bitsTxRx == RFAL_BITS_IN_BYTE) { - gNfca.CR.bitsTxRx = 0; - gNfca.CR.bytesTxRx++; - } - break; - } - - /*******************************************************************************/ - /* Check if Collision loop has failed */ - if(ret != ERR_NONE) { - return ret; - } - - /* If collisions are to be reported check whether the response is complete */ - if((gNfca.CR.devLimit == 0U) && (gNfca.CR.rxLen != sizeof(rfalNfcaSddRes))) { - return ERR_PROTO; - } - - /* Check if the received BCC match */ - if(gNfca.CR.selReq.bcc != - rfalNfcaCalculateBcc(gNfca.CR.selReq.nfcid1, RFAL_NFCA_CASCADE_1_UID_LEN)) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Anticollision OK, Select this Cascade Level */ - gNfca.CR.selReq.selPar = RFAL_NFCA_SEL_SELPAR; - - gNfca.CR.retries = RFAL_NFCA_N_RETRANS; - gNfca.CR.state = RFAL_NFCA_CR_SEL; - break; - - /*******************************************************************************/ - case RFAL_NFCA_CR_SEL: - - /* Send SEL_REQ (Select command) - Retry upon timeout EMVCo 2.6 9.6.1.3 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&gNfca.CR.selReq, - sizeof(rfalNfcaSelReq), - (uint8_t*)gNfca.CR.selRes, - sizeof(rfalNfcaSelRes), - &gNfca.CR.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCA_FDTMIN); - - /* Retry upon timeout EMVCo 2.6 9.6.1.3 */ - if((ret == ERR_TIMEOUT) && (gNfca.CR.devLimit == 0U) && (gNfca.CR.retries != 0U)) { - gNfca.CR.retries--; - platformTimerDestroy(gNfca.CR.tmrFDT); - gNfca.CR.tmrFDT = platformTimerCreate(RFAL_NFCA_T_RETRANS); - break; - } - - if(ret != ERR_NONE) { - return ret; - } - - /* Ensure proper response length */ - if(gNfca.CR.rxLen != sizeof(rfalNfcaSelRes)) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Check cascade byte, if cascade tag then go next cascade level */ - if(*gNfca.CR.selReq.nfcid1 == RFAL_NFCA_SDD_CT) { - /* Cascade Tag present, store nfcid1 bytes (excluding cascade tag) and continue for next CL */ - ST_MEMCPY( - &gNfca.CR.nfcId1[*gNfca.CR.nfcId1Len], - &((uint8_t*)&gNfca.CR.selReq.nfcid1)[RFAL_NFCA_SDD_CT_LEN], - (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN)); - *gNfca.CR.nfcId1Len += (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN); - - /* Go to next cascade level */ - gNfca.CR.state = RFAL_NFCA_CR_CL; - gNfca.CR.cascadeLv++; - } else { - /* UID Selection complete, Stop Cascade Level loop */ - ST_MEMCPY( - &gNfca.CR.nfcId1[*gNfca.CR.nfcId1Len], - (uint8_t*)&gNfca.CR.selReq.nfcid1, - RFAL_NFCA_CASCADE_1_UID_LEN); - *gNfca.CR.nfcId1Len += RFAL_NFCA_CASCADE_1_UID_LEN; - - gNfca.CR.state = RFAL_NFCA_CR_DONE; - break; /* Only flag operation complete on the next execution */ - } - break; - - /*******************************************************************************/ - case RFAL_NFCA_CR_DONE: - return ERR_NONE; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - return ERR_BUSY; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCA, RFAL_BR_106, RFAL_BR_106)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCA); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCA_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCA_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerCheckPresence(rfal14443AShortFrameCmd cmd, rfalNfcaSensRes* sensRes) { - ReturnCode ret; - uint16_t rcvLen; - - /* Digital 1.1 6.10.1.3 For Commands ALL_REQ, SENS_REQ, SDD_REQ, and SEL_REQ, the NFC Forum Device * - * MUST treat receipt of a Listen Frame at a time after FDT(Listen, min) as a Timeour Error */ - - ret = rfalISO14443ATransceiveShortFrame( - cmd, - (uint8_t*)sensRes, - (uint8_t)rfalConvBytesToBits(sizeof(rfalNfcaSensRes)), - &rcvLen, - RFAL_NFCA_FDTMIN); - if((ret == ERR_RF_COLLISION) || (ret == ERR_CRC) || (ret == ERR_NOMEM) || - (ret == ERR_FRAMING) || (ret == ERR_PAR)) { - ret = ERR_NONE; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode - rfalNfcaPollerTechnologyDetection(rfalComplianceMode compMode, rfalNfcaSensRes* sensRes) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalNfcaPollerCheckPresence( - ((compMode == RFAL_COMPLIANCE_MODE_EMV) ? RFAL_14443A_SHORTFRAME_CMD_WUPA : - RFAL_14443A_SHORTFRAME_CMD_REQA), - sensRes)); - - /* Send SLP_REQ as Activity 1.1 9.2.3.6 and EMVCo 2.6 9.2.1.3 */ - if(compMode != RFAL_COMPLIANCE_MODE_ISO) { - rfalNfcaPollerSleep(); - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalNfcaPollerStartSingleCollisionResolution( - devLimit, collPending, selRes, nfcId1, nfcId1Len)); - rfalNfcaRunBlocking(ret, rfalNfcaPollerGetSingleCollisionResolutionStatus()); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerStartFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt) { - ReturnCode ret; - rfalNfcaSensRes sensRes; - uint16_t rcvLen; - - if((nfcaDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = 0; - ret = ERR_NONE; - - /*******************************************************************************/ - /* Send ALL_REQ before Anticollision if a Sleep was sent before Activity 1.1 9.3.4.1 and EMVco 2.6 9.3.2.1 */ - if(compMode != RFAL_COMPLIANCE_MODE_ISO) { - ret = rfalISO14443ATransceiveShortFrame( - RFAL_14443A_SHORTFRAME_CMD_WUPA, - (uint8_t*)&nfcaDevList->sensRes, - (uint8_t)rfalConvBytesToBits(sizeof(rfalNfcaSensRes)), - &rcvLen, - RFAL_NFCA_FDTMIN); - if(ret != ERR_NONE) { - if((compMode == RFAL_COMPLIANCE_MODE_EMV) || - ((ret != ERR_RF_COLLISION) && (ret != ERR_CRC) && (ret != ERR_FRAMING) && - (ret != ERR_PAR))) { - return ret; - } - } - - /* Check proper SENS_RES/ATQA size */ - if((ret == ERR_NONE) && (rfalConvBytesToBits(sizeof(rfalNfcaSensRes)) != rcvLen)) { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Store the SENS_RES from Technology Detection or from WUPA */ - sensRes = nfcaDevList->sensRes; - - if(devLimit > 0U) /* MISRA 21.18 */ - { - ST_MEMSET(nfcaDevList, 0x00, (sizeof(rfalNfcaListenDevice) * devLimit)); - } - - /* Restore the prev SENS_RES, assuming that the SENS_RES received is from first device - * When only one device is detected it's not woken up then we'll have no SENS_RES (ATQA) */ - nfcaDevList->sensRes = sensRes; - - /* Save parameters */ - gNfca.CR.devCnt = devCnt; - gNfca.CR.devLimit = devLimit; - gNfca.CR.nfcaDevList = nfcaDevList; - gNfca.CR.compMode = compMode; - -#if RFAL_FEATURE_T1T - /*******************************************************************************/ - /* Only check for T1T if previous SENS_RES was received without a transmission * - * error. When collisions occur bits in the SENS_RES may look like a T1T */ - /* If T1T Anticollision is not supported Activity 1.1 9.3.4.3 */ - if(rfalNfcaIsSensResT1T(&nfcaDevList->sensRes) && (devLimit != 0U) && (ret == ERR_NONE) && - (compMode != RFAL_COMPLIANCE_MODE_EMV)) { - /* RID_REQ shall be performed Activity 1.1 9.3.4.24 */ - rfalT1TPollerInitialize(); - EXIT_ON_ERR(ret, rfalT1TPollerRid(&nfcaDevList->ridRes)); - - *devCnt = 1U; - nfcaDevList->isSleep = false; - nfcaDevList->type = RFAL_NFCA_T1T; - nfcaDevList->nfcId1Len = RFAL_NFCA_CASCADE_1_UID_LEN; - ST_MEMCPY(&nfcaDevList->nfcId1, &nfcaDevList->ridRes.uid, RFAL_NFCA_CASCADE_1_UID_LEN); - - return ERR_NONE; - } -#endif /* RFAL_FEATURE_T1T */ - - return rfalNfcaPollerStartSingleCollisionResolution( - devLimit, - &gNfca.CR.collPending, - &nfcaDevList->selRes, - (uint8_t*)&nfcaDevList->nfcId1, - &nfcaDevList->nfcId1Len); -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerGetFullCollisionResolutionStatus(void) { - ReturnCode ret; - uint8_t newDevType; - - if((gNfca.CR.nfcaDevList == NULL) || (gNfca.CR.devCnt == NULL)) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Check whether a T1T has already been detected */ - if(rfalNfcaIsSensResT1T(&gNfca.CR.nfcaDevList->sensRes) && - (gNfca.CR.nfcaDevList->type == RFAL_NFCA_T1T)) { - /* T1T doesn't support Anticollision */ - return ERR_NONE; - } - - /*******************************************************************************/ - EXIT_ON_ERR(ret, rfalNfcaPollerGetSingleCollisionResolutionStatus()); - - /* Assign Listen Device */ - newDevType = ((uint8_t)gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].selRes.sak) & - RFAL_NFCA_SEL_RES_CONF_MASK; /* MISRA 10.8 */ - /* PRQA S 4342 1 # MISRA 10.5 - Guaranteed that no invalid enum values are created: see guard_eq_RFAL_NFCA_T2T, .... */ - gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].type = (rfalNfcaListenDeviceType)newDevType; - gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].isSleep = false; - (*gNfca.CR.devCnt)++; - - /* If a collision was detected and device counter is lower than limit Activity 1.1 9.3.4.21 */ - if((*gNfca.CR.devCnt < gNfca.CR.devLimit) && (gNfca.CR.collPending)) { - /* Put this device to Sleep Activity 1.1 9.3.4.22 */ - rfalNfcaPollerSleep(); - gNfca.CR.nfcaDevList[(*gNfca.CR.devCnt - 1U)].isSleep = true; - - /* Send a new SENS_REQ to check for other cards Activity 1.1 9.3.4.23 */ - ret = rfalNfcaPollerCheckPresence( - RFAL_14443A_SHORTFRAME_CMD_REQA, &gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].sensRes); - if(ret == ERR_TIMEOUT) { - /* No more devices found, exit */ - gNfca.CR.collPending = false; - } else { - /* Another device found, continue loop */ - gNfca.CR.collPending = true; - } - } else { - /* Exit loop */ - gNfca.CR.collPending = false; - } - - /*******************************************************************************/ - /* Check if collision resolution shall continue */ - if((*gNfca.CR.devCnt < gNfca.CR.devLimit) && (gNfca.CR.collPending)) { - EXIT_ON_ERR( - ret, - rfalNfcaPollerStartSingleCollisionResolution( - gNfca.CR.devLimit, - &gNfca.CR.collPending, - &gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].selRes, - (uint8_t*)&gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].nfcId1, - &gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].nfcId1Len)); - - return ERR_BUSY; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, rfalNfcaPollerStartFullCollisionResolution(compMode, devLimit, nfcaDevList, devCnt)); - rfalNfcaRunBlocking(ret, rfalNfcaPollerGetFullCollisionResolutionStatus()); - - return ret; -} - -ReturnCode rfalNfcaPollerSleepFullCollisionResolution( - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt) { - bool firstRound; - uint8_t tmpDevCnt; - ReturnCode ret; - - if((nfcaDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - /* Only use ALL_REQ (WUPA) on the first round */ - firstRound = true; - *devCnt = 0; - - /* Perform collision resolution until no new device is found */ - do { - tmpDevCnt = 0; - ret = rfalNfcaPollerFullCollisionResolution( - (firstRound ? RFAL_COMPLIANCE_MODE_NFC : RFAL_COMPLIANCE_MODE_ISO), - (devLimit - *devCnt), - &nfcaDevList[*devCnt], - &tmpDevCnt); - - if((ret == ERR_NONE) && (tmpDevCnt > 0U)) { - *devCnt += tmpDevCnt; - - /* Check whether to seacrh for more devices */ - if(*devCnt < devLimit) { - /* Set last found device to sleep (all others are slept already) */ - rfalNfcaPollerSleep(); - nfcaDevList[((*devCnt) - 1U)].isSleep = true; - - /* Check if any other device is present */ - ret = rfalNfcaPollerCheckPresence( - RFAL_14443A_SHORTFRAME_CMD_REQA, &nfcaDevList[*devCnt].sensRes); - if(ret == ERR_NONE) { - firstRound = false; - continue; - } - } - } - break; - } while(true); - - return ((*devCnt > 0U) ? ERR_NONE : ret); -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerSelect(const uint8_t* nfcid1, uint8_t nfcidLen, rfalNfcaSelRes* selRes) { - uint8_t i; - uint8_t cl; - uint8_t nfcidOffset; - uint16_t rxLen; - ReturnCode ret; - rfalNfcaSelReq selReq; - - if((nfcid1 == NULL) || (nfcidLen > RFAL_NFCA_CASCADE_3_UID_LEN) || (selRes == NULL)) { - return ERR_PARAM; - } - - /* Calculate Cascate Level */ - cl = rfalNfcaNfcidLen2CL(nfcidLen); - nfcidOffset = 0; - - /*******************************************************************************/ - /* Go through all Cascade Levels Activity 1.1 9.4.4 */ - for(i = RFAL_NFCA_SEL_CASCADE_L1; i <= cl; i++) { - /* Assign SEL_CMD according to the CLn and SEL_PAR*/ - selReq.selCmd = rfalNfcaCLn2SELCMD(i); - selReq.selPar = RFAL_NFCA_SEL_SELPAR; - - /* Compute NFCID/Data on the SEL_REQ command Digital 1.1 Table 18 */ - if(cl != i) { - *selReq.nfcid1 = RFAL_NFCA_SDD_CT; - ST_MEMCPY( - &selReq.nfcid1[RFAL_NFCA_SDD_CT_LEN], - &nfcid1[nfcidOffset], - (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN)); - nfcidOffset += (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN); - } else { - ST_MEMCPY(selReq.nfcid1, &nfcid1[nfcidOffset], RFAL_NFCA_CASCADE_1_UID_LEN); - } - - /* Calculate nfcid's BCC */ - selReq.bcc = rfalNfcaCalculateBcc((uint8_t*)&selReq.nfcid1, sizeof(selReq.nfcid1)); - - /*******************************************************************************/ - /* Send SEL_REQ */ - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&selReq, - sizeof(rfalNfcaSelReq), - (uint8_t*)selRes, - sizeof(rfalNfcaSelRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCA_FDTMIN)); - - /* Ensure proper response length */ - if(rxLen != sizeof(rfalNfcaSelRes)) { - return ERR_PROTO; - } - } - - /* REMARK: Could check if NFCID1 is complete */ - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerSleep(void) { - rfalNfcaSlpReq slpReq; - uint8_t rxBuf; /* dummy buffer, just to perform Rx */ - - slpReq.frame[RFAL_NFCA_SLP_CMD_POS] = RFAL_NFCA_SLP_CMD; - slpReq.frame[RFAL_NFCA_SLP_BYTE2_POS] = RFAL_NFCA_SLP_BYTE2; - - rfalTransceiveBlockingTxRx( - (uint8_t*)&slpReq, - sizeof(rfalNfcaSlpReq), - &rxBuf, - sizeof(rxBuf), - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCA_SLP_FWT); - - /* ISO14443-3 6.4.3 HLTA - If PICC responds with any modulation during 1 ms this response shall be interpreted as not acknowledge - Digital 2.0 6.9.2.1 & EMVCo 3.0 5.6.2.1 - consider the HLTA command always acknowledged - No check to be compliant with NFC and EMVCo, and to improve interoprability (Kovio RFID Tag) - */ - - return ERR_NONE; -} - -/*******************************************************************************/ -bool rfalNfcaListenerIsSleepReq(const uint8_t* buf, uint16_t bufLen) { - /* Check if length and payload match */ - if((bufLen != sizeof(rfalNfcaSlpReq)) || (buf[RFAL_NFCA_SLP_CMD_POS] != RFAL_NFCA_SLP_CMD) || - (buf[RFAL_NFCA_SLP_BYTE2_POS] != RFAL_NFCA_SLP_BYTE2)) { - return false; - } - - return true; -} - -/* If the guards here don't compile then the code above cannot work anymore. */ -extern uint8_t guard_eq_RFAL_NFCA_T2T - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_T2T) == (uint8_t)RFAL_NFCA_T2T) ? 1 : (-1)]; -extern uint8_t guard_eq_RFAL_NFCA_T4T - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_T4T) == (uint8_t)RFAL_NFCA_T4T) ? 1 : (-1)]; -extern uint8_t guard_eq_RFAL_NFCA_NFCDEP - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_NFCDEP) == (uint8_t)RFAL_NFCA_NFCDEP) ? - 1 : - (-1)]; -extern uint8_t guard_eq_RFAL_NFCA_T4T_NFCDEP - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_T4T_NFCDEP) == - (uint8_t)RFAL_NFCA_T4T_NFCDEP) ? - 1 : - (-1)]; -#endif /* RFAL_FEATURE_NFCA */ diff --git a/lib/ST25RFAL002/source/rfal_nfcb.c b/lib/ST25RFAL002/source/rfal_nfcb.c deleted file mode 100644 index 841b2554aa..0000000000 --- a/lib/ST25RFAL002/source/rfal_nfcb.c +++ /dev/null @@ -1,519 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcb.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-B (ISO14443B) helpers - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcb.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCB -#define RFAL_FEATURE_NFCB false /* NFC-B module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCB - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCB_SENSB_REQ_EXT_SENSB_RES_SUPPORTED \ - 0x10U /*!< Bit mask for Extended SensB Response support in SENSB_REQ */ -#define RFAL_NFCB_SENSB_RES_PROT_TYPE_RFU \ - 0x08U /*!< Bit mask for Protocol Type RFU in SENSB_RES */ -#define RFAL_NFCB_SLOT_MARKER_SC_SHIFT \ - 4U /*!< Slot Code position on SLOT_MARKER APn */ - -#define RFAL_NFCB_SLOTMARKER_SLOTCODE_MIN \ - 1U /*!< SLOT_MARKER Slot Code minimum Digital 1.1 Table 37 */ -#define RFAL_NFCB_SLOTMARKER_SLOTCODE_MAX \ - 16U /*!< SLOT_MARKER Slot Code maximum Digital 1.1 Table 37 */ - -#define RFAL_NFCB_ACTIVATION_FWT \ - (RFAL_NFCB_FWTSENSB + RFAL_NFCB_DTPOLL_20) /*!< FWT(SENSB) + dTbPoll Digital 2.0 7.9.1.3 */ - -/*! Advanced and Extended bit mask in Parameter of SENSB_REQ */ -#define RFAL_NFCB_SENSB_REQ_PARAM \ - (RFAL_NFCB_SENSB_REQ_ADV_FEATURE | RFAL_NFCB_SENSB_REQ_EXT_SENSB_RES_SUPPORTED) - -/*! NFC-B commands definition */ -enum { - RFAL_NFCB_CMD_SENSB_REQ = 0x05, /*!< SENSB_REQ (REQB) & SLOT_MARKER Digital 1.1 Table 24 */ - RFAL_NFCB_CMD_SENSB_RES = 0x50, /*!< SENSB_RES (ATQB) & SLOT_MARKER Digital 1.1 Table 27 */ - RFAL_NFCB_CMD_SLPB_REQ = 0x50, /*!< SLPB_REQ (HLTB command) Digital 1.1 Table 38 */ - RFAL_NFCB_CMD_SLPB_RES = 0x00 /*!< SLPB_RES (HLTB Answer) Digital 1.1 Table 39 */ -}; - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -#define rfalNfcbNI2NumberOfSlots(ni) \ - (uint8_t)(1U << (ni)) /*!< Converts the Number of slots Identifier to slot number */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! ALLB_REQ (WUPB) and SENSB_REQ (REQB) Command Format Digital 1.1 7.6.1 */ -typedef struct { - uint8_t cmd; /*!< xxxxB_REQ: 05h */ - uint8_t AFI; /*!< NFC Identifier */ - uint8_t PARAM; /*!< Application Data */ -} rfalNfcbSensbReq; - -/*! SLOT_MARKER Command format Digital 1.1 7.7.1 */ -typedef struct { - uint8_t APn; /*!< Slot number 2..16 | 0101b */ -} rfalNfcbSlotMarker; - -/*! SLPB_REQ (HLTB) Command Format Digital 1.1 7.8.1 */ -typedef struct { - uint8_t cmd; /*!< SLPB_REQ: 50h */ - uint8_t nfcid0[RFAL_NFCB_NFCID0_LEN]; /*!< NFC Identifier (PUPI)*/ -} rfalNfcbSlpbReq; - -/*! SLPB_RES (HLTB) Response Format Digital 1.1 7.8.2 */ -typedef struct { - uint8_t cmd; /*!< SLPB_RES: 00h */ -} rfalNfcbSlpbRes; - -/*! RFAL NFC-B instance */ -typedef struct { - uint8_t AFI; /*!< AFI to be used */ - uint8_t PARAM; /*!< PARAM to be used */ -} rfalNfcb; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode rfalNfcbCheckSensbRes(const rfalNfcbSensbRes* sensbRes, uint8_t sensbResLen); - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -static rfalNfcb gRfalNfcb; /*!< RFAL NFC-B Instance */ - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -static ReturnCode rfalNfcbCheckSensbRes(const rfalNfcbSensbRes* sensbRes, uint8_t sensbResLen) { - /* Check response length */ - if(((sensbResLen != RFAL_NFCB_SENSB_RES_LEN) && - (sensbResLen != RFAL_NFCB_SENSB_RES_EXT_LEN))) { - return ERR_PROTO; - } - - /* Check SENSB_RES and Protocol Type Digital 1.1 7.6.2.19 */ - if(((sensbRes->protInfo.FsciProType & RFAL_NFCB_SENSB_RES_PROT_TYPE_RFU) != 0U) || - (sensbRes->cmd != (uint8_t)RFAL_NFCB_CMD_SENSB_RES)) { - return ERR_PROTO; - } - return ERR_NONE; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCB, RFAL_BR_106, RFAL_BR_106)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCB); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCB_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCB_POLLER); - - gRfalNfcb.AFI = RFAL_NFCB_AFI; - gRfalNfcb.PARAM = RFAL_NFCB_PARAM; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerInitializeWithParams(uint8_t AFI, uint8_t PARAM) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalNfcbPollerInitialize()); - - gRfalNfcb.AFI = AFI; - gRfalNfcb.PARAM = (PARAM & RFAL_NFCB_SENSB_REQ_PARAM); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerCheckPresence( - rfalNfcbSensCmd cmd, - rfalNfcbSlots slots, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen) { - uint16_t rxLen; - ReturnCode ret; - rfalNfcbSensbReq sensbReq; - - /* Check if the command requested and given the slot number are valid */ - if(((RFAL_NFCB_SENS_CMD_SENSB_REQ != cmd) && (RFAL_NFCB_SENS_CMD_ALLB_REQ != cmd)) || - (slots > RFAL_NFCB_SLOT_NUM_16) || (sensbRes == NULL) || (sensbResLen == NULL)) { - return ERR_PARAM; - } - - *sensbResLen = 0; - ST_MEMSET(sensbRes, 0x00, sizeof(rfalNfcbSensbRes)); - - /* Compute SENSB_REQ */ - sensbReq.cmd = RFAL_NFCB_CMD_SENSB_REQ; - sensbReq.AFI = gRfalNfcb.AFI; - sensbReq.PARAM = - (((uint8_t)gRfalNfcb.PARAM & RFAL_NFCB_SENSB_REQ_PARAM) | (uint8_t)cmd | (uint8_t)slots); - - /* Send SENSB_REQ and disable AGC to detect collisions */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&sensbReq, - sizeof(rfalNfcbSensbReq), - (uint8_t*)sensbRes, - sizeof(rfalNfcbSensbRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCB_FWTSENSB); - - *sensbResLen = (uint8_t)rxLen; - - /* Check if a transmission error was detected */ - if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - /* Invalidate received frame as an error was detected (CollisionResolution checks if valid) */ - *sensbResLen = 0; - return ERR_NONE; - } - - if(ret == ERR_NONE) { - return rfalNfcbCheckSensbRes(sensbRes, *sensbResLen); - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerSleep(const uint8_t* nfcid0) { - uint16_t rxLen; - ReturnCode ret; - rfalNfcbSlpbReq slpbReq; - rfalNfcbSlpbRes slpbRes; - - if(nfcid0 == NULL) { - return ERR_PARAM; - } - - /* Compute SLPB_REQ */ - slpbReq.cmd = RFAL_NFCB_CMD_SLPB_REQ; - ST_MEMCPY(slpbReq.nfcid0, nfcid0, RFAL_NFCB_NFCID0_LEN); - - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&slpbReq, - sizeof(rfalNfcbSlpbReq), - (uint8_t*)&slpbRes, - sizeof(rfalNfcbSlpbRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCB_ACTIVATION_FWT)); - - /* Check SLPB_RES */ - if((rxLen != sizeof(rfalNfcbSlpbRes)) || (slpbRes.cmd != (uint8_t)RFAL_NFCB_CMD_SLPB_RES)) { - return ERR_PROTO; - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalNfcbPollerSlotMarker(uint8_t slotCode, rfalNfcbSensbRes* sensbRes, uint8_t* sensbResLen) { - ReturnCode ret; - rfalNfcbSlotMarker slotMarker; - uint16_t rxLen; - - /* Check parameters */ - if((sensbRes == NULL) || (sensbResLen == NULL) || - (slotCode < RFAL_NFCB_SLOTMARKER_SLOTCODE_MIN) || - (slotCode > RFAL_NFCB_SLOTMARKER_SLOTCODE_MAX)) { - return ERR_PARAM; - } - /* Compose and send SLOT_MARKER with disabled AGC to detect collisions */ - slotMarker.APn = - ((slotCode << RFAL_NFCB_SLOT_MARKER_SC_SHIFT) | (uint8_t)RFAL_NFCB_CMD_SENSB_REQ); - - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&slotMarker, - sizeof(rfalNfcbSlotMarker), - (uint8_t*)sensbRes, - sizeof(rfalNfcbSensbRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCB_ACTIVATION_FWT); - - *sensbResLen = (uint8_t)rxLen; - - /* Check if a transmission error was detected */ - if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - return ERR_RF_COLLISION; - } - - if(ret == ERR_NONE) { - return rfalNfcbCheckSensbRes(sensbRes, *sensbResLen); - } - - return ret; -} - -ReturnCode rfalNfcbPollerTechnologyDetection( - rfalComplianceMode compMode, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen) { - NO_WARNING(compMode); - - return rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_SENSB_REQ, RFAL_NFCB_SLOT_NUM_1, sensbRes, sensbResLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt) { - bool colPending; /* dummy */ - return rfalNfcbPollerSlottedCollisionResolution( - compMode, - devLimit, - RFAL_NFCB_SLOT_NUM_1, - RFAL_NFCB_SLOT_NUM_16, - nfcbDevList, - devCnt, - &colPending); -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerSlottedCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbSlots initSlots, - rfalNfcbSlots endSlots, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt, - bool* colPending) { - ReturnCode ret; - uint8_t slotsNum; - uint8_t slotCode; - uint8_t curDevCnt; - - /* Check parameters. In ISO | Activity 1.0 mode the initial slots must be 1 as continuation of Technology Detection */ - if((nfcbDevList == NULL) || (devCnt == NULL) || (colPending == NULL) || - (initSlots > RFAL_NFCB_SLOT_NUM_16) || (endSlots > RFAL_NFCB_SLOT_NUM_16) || - ((compMode == RFAL_COMPLIANCE_MODE_ISO) && (initSlots != RFAL_NFCB_SLOT_NUM_1))) { - return ERR_PARAM; - } - - /* Initialise as no error in case Activity 1.0 where the previous SENSB_RES from technology detection should be used */ - ret = ERR_NONE; - *devCnt = 0; - curDevCnt = 0; - *colPending = false; - - /* Send ALLB_REQ Activity 1.1 9.3.5.2 and 9.3.5.3 (Symbol 1 and 2) */ - if(compMode != RFAL_COMPLIANCE_MODE_ISO) { - ret = rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_ALLB_REQ, - initSlots, - &nfcbDevList->sensbRes, - &nfcbDevList->sensbResLen); - if((ret != ERR_NONE) && (initSlots == RFAL_NFCB_SLOT_NUM_1)) { - return ret; - } - } - - /* Check if there was a transmission error on WUPB EMVCo 2.6 9.3.3.1 */ - if((compMode == RFAL_COMPLIANCE_MODE_EMV) && (nfcbDevList->sensbResLen == 0U)) { - return ERR_FRAMING; - } - - for(slotsNum = (uint8_t)initSlots; slotsNum <= (uint8_t)endSlots; slotsNum++) { - do { - /* Activity 1.1 9.3.5.23 - Symbol 22 */ - if((compMode == RFAL_COMPLIANCE_MODE_NFC) && (curDevCnt != 0U)) { - rfalNfcbPollerSleep(nfcbDevList[((*devCnt) - (uint8_t)1U)].sensbRes.nfcid0); - nfcbDevList[((*devCnt) - (uint8_t)1U)].isSleep = true; - } - - /* Send SENSB_REQ with number of slots if not the first Activity 1.1 9.3.5.24 - Symbol 23 */ - if((slotsNum != (uint8_t)initSlots) || *colPending) { - /* PRQA S 4342 1 # MISRA 10.5 - Layout of rfalNfcbSlots and above loop guarantee that no invalid enum values are created. */ - ret = rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_SENSB_REQ, - (rfalNfcbSlots)slotsNum, - &nfcbDevList[*devCnt].sensbRes, - &nfcbDevList[*devCnt].sensbResLen); - } - - /* Activity 1.1 9.3.5.6 - Symbol 5 */ - slotCode = 0; - curDevCnt = 0; - *colPending = false; - - do { - /* Activity 1.1 9.3.5.26 - Symbol 25 */ - if(slotCode != 0U) { - ret = rfalNfcbPollerSlotMarker( - slotCode, - &nfcbDevList[*devCnt].sensbRes, - &nfcbDevList[*devCnt].sensbResLen); - } - - /* Activity 1.1 9.3.5.7 and 9.3.5.8 - Symbol 6 */ - if(ret != ERR_TIMEOUT) { - /* Activity 1.1 9.3.5.8 - Symbol 7 */ - if((rfalNfcbCheckSensbRes( - &nfcbDevList[*devCnt].sensbRes, nfcbDevList[*devCnt].sensbResLen) == - ERR_NONE) && - (ret == ERR_NONE)) { - nfcbDevList[*devCnt].isSleep = false; - - if(compMode == RFAL_COMPLIANCE_MODE_EMV) { - (*devCnt)++; - return ret; - } else if(compMode == RFAL_COMPLIANCE_MODE_ISO) { - /* Activity 1.0 9.3.5.8 - Symbol 7 */ - (*devCnt)++; - curDevCnt++; - - /* Activity 1.0 9.3.5.10 - Symbol 9 */ - if((*devCnt >= devLimit) || - (slotsNum == (uint8_t)RFAL_NFCB_SLOT_NUM_1)) { - return ret; - } - - /* Activity 1.0 9.3.5.11 - Symbol 10 */ - rfalNfcbPollerSleep(nfcbDevList[*devCnt - 1U].sensbRes.nfcid0); - nfcbDevList[*devCnt - 1U].isSleep = true; - } else if(compMode == RFAL_COMPLIANCE_MODE_NFC) { - /* Activity 1.1 9.3.5.10 and 9.3.5.11 - Symbol 9 and Symbol 11*/ - if(curDevCnt != 0U) { - rfalNfcbPollerSleep( - nfcbDevList[(*devCnt) - (uint8_t)1U].sensbRes.nfcid0); - nfcbDevList[(*devCnt) - (uint8_t)1U].isSleep = true; - } - - /* Activity 1.1 9.3.5.12 - Symbol 11 */ - (*devCnt)++; - curDevCnt++; - - /* Activity 1.1 9.3.5.6 - Symbol 13 */ - if((*devCnt >= devLimit) || - (slotsNum == (uint8_t)RFAL_NFCB_SLOT_NUM_1)) { - return ret; - } - } else { - /* MISRA 15.7 - Empty else */ - } - } else { - /* If deviceLimit is set to 0 the NFC Forum Device is configured to perform collision detection only Activity 1.0 and 1.1 9.3.5.5 - Symbol 4 */ - if((devLimit == 0U) && (slotsNum == (uint8_t)RFAL_NFCB_SLOT_NUM_1)) { - return ERR_RF_COLLISION; - } - - /* Activity 1.1 9.3.5.9 - Symbol 8 */ - *colPending = true; - } - } - - /* Activity 1.1 9.3.5.15 - Symbol 14 */ - slotCode++; - } while(slotCode < rfalNfcbNI2NumberOfSlots(slotsNum)); - - /* Activity 1.1 9.3.5.17 - Symbol 16 */ - if(!(*colPending)) { - return ERR_NONE; - } - - /* Activity 1.1 9.3.5.18 - Symbol 17 */ - } while( - curDevCnt != - 0U); /* If a collision is detected and card(s) were found on this loop keep the same number of available slots */ - } - - return ERR_NONE; -} - -/*******************************************************************************/ -uint32_t rfalNfcbTR2ToFDT(uint8_t tr2Code) { - /*******************************************************************************/ - /* MISRA 8.9 An object should be defined at block scope if its identifier only appears in a single function */ - /*! TR2 Table according to Digital 1.1 Table 33 */ - const uint16_t rfalNfcbTr2Table[] = {1792, 3328, 5376, 9472}; - /*******************************************************************************/ - - return rfalNfcbTr2Table[(tr2Code & RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK)]; -} - -#endif /* RFAL_FEATURE_NFCB */ diff --git a/lib/ST25RFAL002/source/rfal_nfcf.c b/lib/ST25RFAL002/source/rfal_nfcf.c deleted file mode 100644 index 7b5e4d58fc..0000000000 --- a/lib/ST25RFAL002/source/rfal_nfcf.c +++ /dev/null @@ -1,585 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcf.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-F Poller (FeliCa PCD) device - * - * The definitions and helpers methods provided by this module are - * aligned with NFC-F (FeliCa - JIS X6319-4) - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcf.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCF -#define RFAL_FEATURE_NFCF false /* NFC-F module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCF - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_NFCF_SENSF_REQ_LEN_MIN \ - 5U /*!< SENSF_RES minimum length */ - -#define RFAL_NFCF_READ_WO_ENCRYPTION_MIN_LEN \ - 15U /*!< Minimum length for a Check Command T3T 5.4.1 */ -#define RFAL_NFCF_WRITE_WO_ENCRYPTION_MIN_LEN \ - 31U /*!< Minimum length for an Update Command T3T 5.5.1 */ - -#define RFAL_NFCF_CHECK_RES_MIN_LEN \ - 11U /*!< CHECK Response minimum length T3T 1.0 Table 8 */ -#define RFAL_NFCF_UPDATE_RES_MIN_LEN \ - 11U /*!< UPDATE Response minimum length T3T 1.0 Table 8 */ - -#define RFAL_NFCF_CHECK_REQ_MAX_LEN \ - 86U /*!< Max length of a Check request T3T 1.0 Table 7 */ -#define RFAL_NFCF_CHECK_REQ_MAX_SERV \ - 15U /*!< Max Services number on Check request T3T 1.0 5.4.1.5 */ -#define RFAL_NFCF_CHECK_REQ_MAX_BLOCK \ - 15U /*!< Max Blocks number on Check request T3T 1.0 5.4.1.10 */ -#define RFAL_NFCF_UPDATE_REQ_MAX_SERV \ - 15U /*!< Max Services number Update request T3T 1.0 5.4.1.5 */ -#define RFAL_NFCF_UPDATE_REQ_MAX_BLOCK \ - 13U /*!< Max Blocks number on Update request T3T 1.0 5.4.1.10 */ - -/*! MRT Check | Uupdate = (Tt3t x ((A+1) + n (B+1)) x 4^E) + dRWTt3t T3T 5.8 - Max values used: A = 7 ; B = 7 ; E = 3 ; n = 15 (NFC Forum n = 15, JIS n = 32) -*/ -#define RFAL_NFCF_MRT_CHECK_UPDATE ((4096 * (8 + (15 * 8)) * 64) + 16) - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ -#define rfalNfcfSlots2CardNum(s) \ - ((uint8_t)(s) + 1U) /*!< Converts Time Slot Number (TSN) into num of slots */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Structure/Buffer to hold the SENSF_RES with LEN byte prepended */ -typedef struct { - uint8_t LEN; /*!< NFC-F LEN byte */ - rfalNfcfSensfRes SENSF_RES; /*!< SENSF_RES */ -} rfalNfcfSensfResBuf; - -/*! Greedy collection for NFCF GRE_POLL_F Activity 1.0 Table 10 */ -typedef struct { - uint8_t pollFound; /*!< Number of devices found by the Poll */ - uint8_t pollCollision; /*!< Number of collisions detected */ - rfalFeliCaPollRes POLL_F[RFAL_NFCF_POLL_MAXCARDS]; /*!< GRE_POLL_F Activity 1.0 Table 10 */ -} rfalNfcfGreedyF; - -/*! NFC-F SENSF_REQ format Digital 1.1 8.6.1 */ -typedef struct { - uint8_t CMD; /*!< Command code: 00h */ - uint8_t SC[RFAL_NFCF_SENSF_SC_LEN]; /*!< System Code */ - uint8_t RC; /*!< Request Code */ - uint8_t TSN; /*!< Time Slot Number */ -} rfalNfcfSensfReq; - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ -static rfalNfcfGreedyF gRfalNfcfGreedyF; /*!< Activity's NFCF Greedy collection */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static void rfalNfcfComputeValidSENF( - rfalNfcfListenDevice* outDevInfo, - uint8_t* curDevIdx, - uint8_t devLimit, - bool overwrite, - bool* nfcDepFound); - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -/*******************************************************************************/ -static void rfalNfcfComputeValidSENF( - rfalNfcfListenDevice* outDevInfo, - uint8_t* curDevIdx, - uint8_t devLimit, - bool overwrite, - bool* nfcDepFound) { - uint8_t tmpIdx; - bool duplicate; - const rfalNfcfSensfResBuf* sensfBuf; - rfalNfcfSensfResBuf sensfCopy; - - /*******************************************************************************/ - /* Go through all responses check if valid and duplicates */ - /*******************************************************************************/ - while((gRfalNfcfGreedyF.pollFound > 0U) && ((*curDevIdx) < devLimit)) { - duplicate = false; - gRfalNfcfGreedyF.pollFound--; - - /* MISRA 11.3 - Cannot point directly into different object type, use local copy */ - ST_MEMCPY( - (uint8_t*)&sensfCopy, - (uint8_t*)&gRfalNfcfGreedyF.POLL_F[gRfalNfcfGreedyF.pollFound], - sizeof(rfalNfcfSensfResBuf)); - - /* Point to received SENSF_RES */ - sensfBuf = &sensfCopy; - - /* Check for devices that are already in device list */ - for(tmpIdx = 0; tmpIdx < (*curDevIdx); tmpIdx++) { - if(ST_BYTECMP( - sensfBuf->SENSF_RES.NFCID2, - outDevInfo[tmpIdx].sensfRes.NFCID2, - RFAL_NFCF_NFCID2_LEN) == 0) { - duplicate = true; - break; - } - } - - /* If is a duplicate skip this (and not to overwrite)*/ - if(duplicate && !overwrite) { - continue; - } - - /* Check if response length is OK */ - if(((sensfBuf->LEN - RFAL_NFCF_HEADER_LEN) < RFAL_NFCF_SENSF_RES_LEN_MIN) || - ((sensfBuf->LEN - RFAL_NFCF_HEADER_LEN) > RFAL_NFCF_SENSF_RES_LEN_MAX)) { - continue; - } - - /* Check if the response is a SENSF_RES / Polling response */ - if(sensfBuf->SENSF_RES.CMD != (uint8_t)RFAL_NFCF_CMD_POLLING_RES) { - continue; - } - - /* Check if is an overwrite request or new device*/ - if(duplicate && overwrite) { - /* overwrite deviceInfo/GRE_SENSF_RES with SENSF_RES */ - outDevInfo[tmpIdx].sensfResLen = (sensfBuf->LEN - RFAL_NFCF_LENGTH_LEN); - ST_MEMCPY( - &outDevInfo[tmpIdx].sensfRes, - &sensfBuf->SENSF_RES, - outDevInfo[tmpIdx].sensfResLen); - continue; - } else { - /* fill deviceInfo/GRE_SENSF_RES with new SENSF_RES */ - outDevInfo[(*curDevIdx)].sensfResLen = (sensfBuf->LEN - RFAL_NFCF_LENGTH_LEN); - ST_MEMCPY( - &outDevInfo[(*curDevIdx)].sensfRes, - &sensfBuf->SENSF_RES, - outDevInfo[(*curDevIdx)].sensfResLen); - } - - /* Check if this device supports NFC-DEP and signal it (ACTIVITY 1.1 9.3.6.63) */ - *nfcDepFound = rfalNfcfIsNfcDepSupported(&outDevInfo[(*curDevIdx)]); - - (*curDevIdx)++; - } -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerInitialize(rfalBitRate bitRate) { - ReturnCode ret; - - if((bitRate != RFAL_BR_212) && (bitRate != RFAL_BR_424)) { - return ERR_PARAM; - } - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCF, bitRate, bitRate)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCF); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCF_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCF_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* cardList, - uint8_t* devCnt, - uint8_t* collisions) { - return rfalFeliCaPoll( - slots, sysCode, reqCode, cardList, rfalNfcfSlots2CardNum(slots), devCnt, collisions); -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerCheckPresence(void) { - gRfalNfcfGreedyF.pollFound = 0; - gRfalNfcfGreedyF.pollCollision = 0; - - /* ACTIVITY 1.0 & 1.1 - 9.2.3.17 SENSF_REQ must be with number of slots equal to 4 - * SC must be 0xFFFF - * RC must be 0x00 (No system code info required) */ - return rfalFeliCaPoll( - RFAL_FELICA_4_SLOTS, - RFAL_NFCF_SYSTEMCODE, - RFAL_FELICA_POLL_RC_NO_REQUEST, - gRfalNfcfGreedyF.POLL_F, - rfalNfcfSlots2CardNum(RFAL_FELICA_4_SLOTS), - &gRfalNfcfGreedyF.pollFound, - &gRfalNfcfGreedyF.pollCollision); -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcfListenDevice* nfcfDevList, - uint8_t* devCnt) { - ReturnCode ret; - bool nfcDepFound; - - if((nfcfDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = 0; - nfcDepFound = false; - - /*******************************************************************************************/ - /* ACTIVITY 1.0 - 9.3.6.3 Copy valid SENSF_RES in GRE_POLL_F into GRE_SENSF_RES */ - /* ACTIVITY 1.0 - 9.3.6.6 The NFC Forum Device MUST remove all entries from GRE_SENSF_RES[]*/ - /* ACTIVITY 1.1 - 9.3.63.59 Populate GRE_SENSF_RES with data from GRE_POLL_F */ - /* */ - /* CON_DEVICES_LIMIT = 0 Just check if devices from Tech Detection exceeds -> always true */ - /* Allow the number of slots open on Technology Detection */ - /*******************************************************************************************/ - rfalNfcfComputeValidSENF( - nfcfDevList, - devCnt, - ((devLimit == 0U) ? rfalNfcfSlots2CardNum(RFAL_FELICA_4_SLOTS) : devLimit), - false, - &nfcDepFound); - - /*******************************************************************************/ - /* ACTIVITY 1.0 - 9.3.6.4 */ - /* ACTIVITY 1.1 - 9.3.63.60 Check if devices found are lower than the limit */ - /* and send a SENSF_REQ if so */ - /*******************************************************************************/ - if(*devCnt < devLimit) { - /* ACTIVITY 1.0 - 9.3.6.5 Copy valid SENSF_RES and then to remove it - * ACTIVITY 1.1 - 9.3.6.65 Copy and filter duplicates - * For now, due to some devices keep generating different nfcid2, we use 1.0 - * Phones detected: Samsung Galaxy Nexus,Samsung Galaxy S3,Samsung Nexus S */ - *devCnt = 0; - - ret = rfalNfcfPollerPoll( - RFAL_FELICA_16_SLOTS, - RFAL_NFCF_SYSTEMCODE, - RFAL_FELICA_POLL_RC_NO_REQUEST, - gRfalNfcfGreedyF.POLL_F, - &gRfalNfcfGreedyF.pollFound, - &gRfalNfcfGreedyF.pollCollision); - if(ret == ERR_NONE) { - rfalNfcfComputeValidSENF(nfcfDevList, devCnt, devLimit, false, &nfcDepFound); - } - - /*******************************************************************************/ - /* ACTIVITY 1.1 - 9.3.6.63 Check if any device supports NFC DEP */ - /*******************************************************************************/ - if(nfcDepFound && (compMode == RFAL_COMPLIANCE_MODE_NFC)) { - ret = rfalNfcfPollerPoll( - RFAL_FELICA_16_SLOTS, - RFAL_NFCF_SYSTEMCODE, - RFAL_FELICA_POLL_RC_SYSTEM_CODE, - gRfalNfcfGreedyF.POLL_F, - &gRfalNfcfGreedyF.pollFound, - &gRfalNfcfGreedyF.pollCollision); - if(ret == ERR_NONE) { - rfalNfcfComputeValidSENF(nfcfDevList, devCnt, devLimit, true, &nfcDepFound); - } - } - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerCheck( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvdLen) { - uint8_t txBuf[RFAL_NFCF_CHECK_REQ_MAX_LEN]; - uint8_t msgIt; - uint8_t i; - ReturnCode ret; - const uint8_t* checkRes; - - /* Check parameters */ - if((nfcid2 == NULL) || (rxBuf == NULL) || (servBlock == NULL) || (servBlock->numBlock == 0U) || - (servBlock->numBlock > RFAL_NFCF_CHECK_REQ_MAX_BLOCK) || (servBlock->numServ == 0U) || - (servBlock->numServ > RFAL_NFCF_CHECK_REQ_MAX_SERV) || - (rxBufLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECK_RES_MIN_LEN))) { - return ERR_PARAM; - } - - msgIt = 0; - - /*******************************************************************************/ - /* Compose CHECK command/request */ - - txBuf[msgIt++] = RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION; /* Command Code */ - - ST_MEMCPY(&txBuf[msgIt], nfcid2, RFAL_NFCF_NFCID2_LEN); /* NFCID2 */ - msgIt += RFAL_NFCF_NFCID2_LEN; - - txBuf[msgIt++] = servBlock->numServ; /* NoS */ - for(i = 0; i < servBlock->numServ; i++) { - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 0U) & 0xFFU); /* Service Code */ - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 8U) & 0xFFU); - } - - txBuf[msgIt++] = servBlock->numBlock; /* NoB */ - for(i = 0; i < servBlock->numBlock; i++) { - txBuf[msgIt++] = - servBlock->blockList[i].conf; /* Block list element conf (Flag|Access|Service) */ - if((servBlock->blockList[i].conf & 0x80U) != - 0U) /* Check if 2 or 3 byte block list element */ - { - txBuf[msgIt++] = - (uint8_t)(servBlock->blockList[i].blockNum & 0xFFU); /* 1byte Block Num */ - } else { - txBuf[msgIt++] = - (uint8_t)((servBlock->blockList[i].blockNum >> 0U) & 0xFFU); /* 2byte Block Num */ - txBuf[msgIt++] = (uint8_t)((servBlock->blockList[i].blockNum >> 8U) & 0xFFU); - } - } - - /*******************************************************************************/ - /* Transceive CHECK command/request */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - rxBuf, - rxBufLen, - rcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCF_MRT_CHECK_UPDATE); - - if(ret == ERR_NONE) { - /* Skip LEN byte */ - checkRes = (rxBuf + RFAL_NFCF_LENGTH_LEN); - - /* Check response length */ - if(*rcvdLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECKUPDATE_RES_ST2_POS)) { - ret = ERR_PROTO; - } - /* Check for a valid response */ - else if( - (checkRes[RFAL_NFCF_CMD_POS] != (uint8_t)RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION_RES) || - (checkRes[RFAL_NFCF_CHECKUPDATE_RES_ST1_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS) || - (checkRes[RFAL_NFCF_CHECKUPDATE_RES_ST2_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS)) { - ret = ERR_REQUEST; - } - /* CHECK succesfull, remove header */ - else { - (*rcvdLen) -= (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECKUPDATE_RES_NOB_POS); - - if(*rcvdLen > 0U) { - ST_MEMMOVE(rxBuf, &checkRes[RFAL_NFCF_CHECKUPDATE_RES_NOB_POS], (*rcvdLen)); - } - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerUpdate( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* txBuf, - uint16_t txBufLen, - const uint8_t* blockData, - uint8_t* rxBuf, - uint16_t rxBufLen) { - uint8_t i; - uint16_t msgIt; - uint16_t rcvdLen; - uint16_t auxLen; - const uint8_t* updateRes; - ReturnCode ret; - - /* Check parameters */ - if((nfcid2 == NULL) || (rxBuf == NULL) || (servBlock == NULL) || (txBuf == NULL) || - (servBlock->numBlock == 0U) || (servBlock->numBlock > RFAL_NFCF_UPDATE_REQ_MAX_BLOCK) || - (servBlock->numServ == 0U) || (servBlock->numServ > RFAL_NFCF_UPDATE_REQ_MAX_SERV) || - (rxBufLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_UPDATE_RES_MIN_LEN))) { - return ERR_PARAM; - } - - /* Calculate required txBuffer lenth */ - auxLen = (uint16_t)( RFAL_NFCF_CMD_LEN + RFAL_NFCF_NFCID2_LEN + ( servBlock->numServ * sizeof(rfalNfcfServ) ) + - (servBlock->numBlock * sizeof(rfalNfcfBlockListElem)) + (uint16_t)((uint16_t)servBlock->numBlock * RFAL_NFCF_BLOCK_LEN) ); - - /* Check whether the provided buffer is sufficient for this request */ - if(txBufLen < auxLen) { - return ERR_PARAM; - } - - msgIt = 0; - - /*******************************************************************************/ - /* Compose UPDATE command/request */ - - txBuf[msgIt++] = RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION; /* Command Code */ - - ST_MEMCPY(&txBuf[msgIt], nfcid2, RFAL_NFCF_NFCID2_LEN); /* NFCID2 */ - msgIt += RFAL_NFCF_NFCID2_LEN; - - txBuf[msgIt++] = servBlock->numServ; /* NoS */ - for(i = 0; i < servBlock->numServ; i++) { - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 0U) & 0xFFU); /* Service Code */ - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 8U) & 0xFFU); - } - - txBuf[msgIt++] = servBlock->numBlock; /* NoB */ - for(i = 0; i < servBlock->numBlock; i++) { - txBuf[msgIt++] = - servBlock->blockList[i].conf; /* Block list element conf (Flag|Access|Service) */ - if((servBlock->blockList[i].conf & 0x80U) != - 0U) /* Check if 2 or 3 byte block list element */ - { - txBuf[msgIt++] = - (uint8_t)(servBlock->blockList[i].blockNum & 0xFFU); /* 1byte Block Num */ - } else { - txBuf[msgIt++] = - (uint8_t)((servBlock->blockList[i].blockNum >> 0U) & 0xFFU); /* 2byte Block Num */ - txBuf[msgIt++] = (uint8_t)((servBlock->blockList[i].blockNum >> 8U) & 0xFFU); - } - } - - auxLen = ((uint16_t)servBlock->numBlock * RFAL_NFCF_BLOCK_LEN); - ST_MEMCPY(&txBuf[msgIt], blockData, auxLen); /* Block Data */ - msgIt += auxLen; - - /*******************************************************************************/ - /* Transceive UPDATE command/request */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - rxBuf, - rxBufLen, - &rcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCF_MRT_CHECK_UPDATE); - - if(ret == ERR_NONE) { - /* Skip LEN byte */ - updateRes = (rxBuf + RFAL_NFCF_LENGTH_LEN); - - /* Check response length */ - if(rcvdLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECKUPDATE_RES_ST2_POS)) { - ret = ERR_PROTO; - } - /* Check for a valid response */ - else if( - (updateRes[RFAL_NFCF_CMD_POS] != - (uint8_t)RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION_RES) || - (updateRes[RFAL_NFCF_CHECKUPDATE_RES_ST1_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS) || - (updateRes[RFAL_NFCF_CHECKUPDATE_RES_ST2_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS)) { - ret = ERR_REQUEST; - } else { - /* MISRA 15.7 - Empty else */ - } - } - - return ret; -} - -/*******************************************************************************/ -bool rfalNfcfListenerIsT3TReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid2) { - /* Check cmd byte */ - switch(*buf) { - case RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION: - if(bufLen < RFAL_NFCF_READ_WO_ENCRYPTION_MIN_LEN) { - return false; - } - break; - - case RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION: - if(bufLen < RFAL_NFCF_WRITE_WO_ENCRYPTION_MIN_LEN) { - return false; - } - break; - - default: - return false; - } - - /* Output NFID2 if requested */ - if(nfcid2 != NULL) { - ST_MEMCPY(nfcid2, &buf[RFAL_NFCF_CMD_LEN], RFAL_NFCF_NFCID2_LEN); - } - - return true; -} - -#endif /* RFAL_FEATURE_NFCF */ diff --git a/lib/ST25RFAL002/source/rfal_nfcv.c b/lib/ST25RFAL002/source/rfal_nfcv.c deleted file mode 100644 index 22c9b87416..0000000000 --- a/lib/ST25RFAL002/source/rfal_nfcv.c +++ /dev/null @@ -1,1057 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcv.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-V Poller (ISO15693) device - * - * The definitions and helpers methods provided by this module are - * aligned with NFC-V (ISO15693) - * - * The definitions and helpers methods provided by this module - * are aligned with NFC-V Digital 2.1 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcv.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCV -#define RFAL_FEATURE_NFCV false /* NFC-V module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCV - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCV_INV_REQ_FLAG \ - 0x06U /*!< INVENTORY_REQ INV_FLAG Digital 2.1 9.6.1 */ -#define RFAL_NFCV_MASKVAL_MAX_LEN \ - 8U /*!< Mask value max length: 64 bits (UID length) */ -#define RFAL_NFCV_MASKVAL_MAX_1SLOT_LEN \ - 64U /*!< Mask value max length in 1 Slot mode in bits Digital 2.1 9.6.1.6 */ -#define RFAL_NFCV_MASKVAL_MAX_16SLOT_LEN \ - 60U /*!< Mask value max length in 16 Slot mode in bits Digital 2.1 9.6.1.6 */ -#define RFAL_NFCV_MAX_SLOTS \ - 16U /*!< NFC-V max number of Slots */ -#define RFAL_NFCV_INV_REQ_HEADER_LEN \ - 3U /*!< INVENTORY_REQ header length (INV_FLAG, CMD, MASK_LEN) */ -#define RFAL_NFCV_INV_RES_LEN \ - 10U /*!< INVENTORY_RES length */ -#define RFAL_NFCV_WR_MUL_REQ_HEADER_LEN \ - 4U /*!< Write Multiple header length (INV_FLAG, CMD, [UID], BNo, Bno) */ - -#define RFAL_NFCV_CMD_LEN \ - 1U /*!< Commandbyte length */ -#define RFAL_NFCV_FLAG_POS \ - 0U /*!< Flag byte position */ -#define RFAL_NFCV_FLAG_LEN \ - 1U /*!< Flag byte length */ -#define RFAL_NFCV_DATASTART_POS \ - 1U /*!< Position of start of data */ -#define RFAL_NFCV_DSFI_LEN \ - 1U /*!< DSFID length */ -#define RFAL_NFCV_SLPREQ_REQ_FLAG \ - 0x22U /*!< SLPV_REQ request flags Digital 2.0 (Candidate) 9.7.1.1 */ -#define RFAL_NFCV_RES_FLAG_NOERROR \ - 0x00U /*!< RES_FLAG indicating no error (checked during activation) */ - -#define RFAL_NFCV_MAX_COLL_SUPPORTED \ - 16U /*!< Maximum number of collisions supported by the Anticollision loop */ - -#define RFAL_NFCV_FDT_MAX \ - rfalConvMsTo1fc(20) /*!< Maximum Wait time FDTV,EOF and MAX2 Digital 2.1 B.5*/ -#define RFAL_NFCV_FDT_MAX1 \ - 4394U /*!< Read alike command FWT FDTV,LISTEN,MAX1 Digital 2.0 B.5 */ - -/*! Time from special frame to EOF - * ISO15693 2009 10.4.2 : 20ms - * NFC Forum defines Digital 2.0 9.7.4 : FDTV,EOF = [10 ; 20]ms - */ -#define RFAL_NFCV_FDT_EOF 20U - -/*! Time between slots - ISO 15693 defines t3min depending on modulation depth and data rate. - * With only high-bitrate supported, AM modulation and a length of 12 bytes (96bits) for INV_RES we get: - * - ISO t3min = 96/26 ms + 300us = 4 ms - * - NFC Forum defines FDTV,INVENT_NORES = (4394 + 2048)/fc. Digital 2.0 B.5*/ -#define RFAL_NFCV_FDT_V_INVENT_NORES 4U - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Checks if a valid INVENTORY_RES is valid Digital 2.2 9.6.2.1 & 9.6.2.3 */ -#define rfalNfcvCheckInvRes(f, l) \ - (((l) == rfalConvBytesToBits(RFAL_NFCV_INV_RES_LEN + RFAL_NFCV_CRC_LEN)) && \ - ((f) == RFAL_NFCV_RES_FLAG_NOERROR)) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-V INVENTORY_REQ format Digital 2.0 9.6.1 */ -typedef struct { - uint8_t INV_FLAG; /*!< Inventory Flags */ - uint8_t CMD; /*!< Command code: 01h */ - uint8_t MASK_LEN; /*!< Mask Value Length */ - uint8_t MASK_VALUE[RFAL_NFCV_MASKVAL_MAX_LEN]; /*!< Mask Value */ -} rfalNfcvInventoryReq; - -/*! NFC-V SLP_REQ format Digital 2.0 (Candidate) 9.7.1 */ -typedef struct { - uint8_t REQ_FLAG; /*!< Request Flags */ - uint8_t CMD; /*!< Command code: 02h */ - uint8_t UID[RFAL_NFCV_UID_LEN]; /*!< Mask Value */ -} rfalNfcvSlpvReq; - -/*! Container for a collision found during Anticollision loop */ -typedef struct { - uint8_t maskLen; - uint8_t maskVal[RFAL_NFCV_MASKVAL_MAX_LEN]; -} rfalNfcvCollision; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode rfalNfcvParseError(uint8_t err); - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -static ReturnCode rfalNfcvParseError(uint8_t err) { - switch(err) { - case RFAL_NFCV_ERROR_CMD_NOT_SUPPORTED: - case RFAL_NFCV_ERROR_OPTION_NOT_SUPPORTED: - return ERR_NOTSUPP; - - case RFAL_NFCV_ERROR_CMD_NOT_RECOGNIZED: - return ERR_PROTO; - - case RFAL_NFCV_ERROR_WRITE_FAILED: - return ERR_WRITE; - - default: - return ERR_REQUEST; - } -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCV, RFAL_BR_26p48, RFAL_BR_26p48)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCV); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCV_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCV_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerCheckPresence(rfalNfcvInventoryRes* invRes) { - ReturnCode ret; - - /* INVENTORY_REQ with 1 slot and no Mask Activity 2.0 (Candidate) 9.2.3.32 */ - ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, invRes, NULL); - - if((ret == ERR_RF_COLLISION) || (ret == ERR_CRC) || (ret == ERR_FRAMING) || - (ret == ERR_PROTO)) { - ret = ERR_NONE; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerInventory( - rfalNfcvNumSlots nSlots, - uint8_t maskLen, - const uint8_t* maskVal, - rfalNfcvInventoryRes* invRes, - uint16_t* rcvdLen) { - ReturnCode ret; - rfalNfcvInventoryReq invReq; - uint16_t rxLen; - - if(((maskVal == NULL) && (maskLen != 0U)) || (invRes == NULL)) { - return ERR_PARAM; - } - - invReq.INV_FLAG = (RFAL_NFCV_INV_REQ_FLAG | (uint8_t)nSlots); - invReq.CMD = RFAL_NFCV_CMD_INVENTORY; - invReq.MASK_LEN = (uint8_t)MIN( - maskLen, - ((nSlots == RFAL_NFCV_NUM_SLOTS_1) ? - RFAL_NFCV_MASKVAL_MAX_1SLOT_LEN : - RFAL_NFCV_MASKVAL_MAX_16SLOT_LEN)); /* Digital 2.0 9.6.1.6 */ - - if((rfalConvBitsToBytes(invReq.MASK_LEN) > 0U) && (maskVal != NULL)) /* MISRA 21.18 & 1.3 */ - { - ST_MEMCPY(invReq.MASK_VALUE, maskVal, rfalConvBitsToBytes(invReq.MASK_LEN)); - } - - ret = rfalISO15693TransceiveAnticollisionFrame( - (uint8_t*)&invReq, - (uint8_t)(RFAL_NFCV_INV_REQ_HEADER_LEN + rfalConvBitsToBytes(invReq.MASK_LEN)), - (uint8_t*)invRes, - sizeof(rfalNfcvInventoryRes), - &rxLen); - - /* Check for optional output parameter */ - if(rcvdLen != NULL) { - *rcvdLen = rxLen; - } - - if(ret == ERR_NONE) { - /* Check for valid INVENTORY_RES Digital 2.2 9.6.2.1 & 9.6.2.3 */ - if(!rfalNfcvCheckInvRes(invRes->RES_FLAG, rxLen)) { - return ERR_PROTO; - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcvListenDevice* nfcvDevList, - uint8_t* devCnt) { - ReturnCode ret; - uint8_t slotNum; - uint16_t rcvdLen; - uint8_t colIt; - uint8_t colCnt; - uint8_t colPos; - bool colPending; - rfalNfcvCollision colFound[RFAL_NFCV_MAX_COLL_SUPPORTED]; - - if((nfcvDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - /* Initialize parameters */ - *devCnt = 0; - colIt = 0; - colCnt = 0; - colPending = false; - ST_MEMSET(colFound, 0x00, (sizeof(rfalNfcvCollision) * RFAL_NFCV_MAX_COLL_SUPPORTED)); - - if(devLimit > 0U) /* MISRA 21.18 */ - { - ST_MEMSET(nfcvDevList, 0x00, (sizeof(rfalNfcvListenDevice) * devLimit)); - } - - NO_WARNING( - colPending); /* colPending is not exposed externally, in future it might become exposed/ouput parameter */ - - if(compMode == RFAL_COMPLIANCE_MODE_NFC) { - /* Send INVENTORY_REQ with one slot Activity 2.1 9.3.7.1 (Symbol 0) */ - ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &nfcvDevList->InvRes, NULL); - - if(ret == ERR_TIMEOUT) /* Exit if no device found Activity 2.1 9.3.7.2 (Symbol 1) */ - { - return ERR_NONE; - } - if(ret == - ERR_NONE) /* Device found without transmission error/collision Activity 2.1 9.3.7.3 (Symbol 2) */ - { - (*devCnt)++; - return ERR_NONE; - } - - /* A Collision has been identified Activity 2.1 9.3.7.4 (Symbol 3) */ - colPending = true; - colCnt = 1; - - /* Check if the Collision Resolution is set to perform only Collision detection Activity 2.1 9.3.7.5 (Symbol 4)*/ - if(devLimit == 0U) { - return ERR_RF_COLLISION; - } - - platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES); - - /*******************************************************************************/ - /* Collisions pending, Anticollision loop must be executed */ - /*******************************************************************************/ - } else { - /* Advance to 16 slots below without mask. Will give a good chance to identify multiple cards */ - colPending = true; - colCnt = 1; - } - - /* Execute until all collisions are resolved Activity 2.1 9.3.7.18 (Symbol 17) */ - do { - /* Activity 2.1 9.3.7.7 (Symbol 6 / 7) */ - colPending = false; - slotNum = 0; - - do { - if(slotNum == 0U) { - /* Send INVENTORY_REQ with 16 slots Activity 2.1 9.3.7.9 (Symbol 8) */ - ret = rfalNfcvPollerInventory( - RFAL_NFCV_NUM_SLOTS_16, - colFound[colIt].maskLen, - colFound[colIt].maskVal, - &nfcvDevList[(*devCnt)].InvRes, - &rcvdLen); - } else { - ret = rfalISO15693TransceiveEOFAnticollision( - (uint8_t*)&nfcvDevList[(*devCnt)].InvRes, - sizeof(rfalNfcvInventoryRes), - &rcvdLen); - } - slotNum++; - - /*******************************************************************************/ - if(ret != ERR_TIMEOUT) { - if(rcvdLen < - rfalConvBytesToBits( - RFAL_NFCV_INV_RES_LEN + - RFAL_NFCV_CRC_LEN)) { /* If only a partial frame was received make sure the FDT_V_INVENT_NORES is fulfilled */ - platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES); - } - - /* Check if response is a correct frame (no TxRx error) Activity 2.1 9.3.7.11 (Symbol 10)*/ - if((ret == ERR_NONE) || (ret == ERR_PROTO)) { - /* Check if the device found is already on the list and its response is a valid INVENTORY_RES */ - if(rfalNfcvCheckInvRes(nfcvDevList[(*devCnt)].InvRes.RES_FLAG, rcvdLen)) { - /* Activity 2.1 9.3.7.12 (Symbol 11) */ - (*devCnt)++; - } - } else /* Treat everything else as collision */ - { - /* Activity 2.1 9.3.7.17 (Symbol 16) */ - colPending = true; - - /*******************************************************************************/ - /* Ensure that this collision still fits on the container */ - if(colCnt < RFAL_NFCV_MAX_COLL_SUPPORTED) { - /* Store this collision on the container to be resolved later */ - /* Activity 2.1 9.3.7.17 (Symbol 16): add the collision information - * (MASK_VAL + SN) to the list containing the collision information */ - ST_MEMCPY( - colFound[colCnt].maskVal, colFound[colIt].maskVal, RFAL_NFCV_UID_LEN); - colPos = colFound[colIt].maskLen; - colFound[colCnt].maskVal[(colPos / RFAL_BITS_IN_BYTE)] &= - (uint8_t)((1U << (colPos % RFAL_BITS_IN_BYTE)) - 1U); - colFound[colCnt].maskVal[(colPos / RFAL_BITS_IN_BYTE)] |= - (uint8_t)((slotNum - 1U) << (colPos % RFAL_BITS_IN_BYTE)); - colFound[colCnt].maskVal[((colPos / RFAL_BITS_IN_BYTE) + 1U)] = - (uint8_t)((slotNum - 1U) >> (RFAL_BITS_IN_BYTE - (colPos % RFAL_BITS_IN_BYTE))); - - colFound[colCnt].maskLen = (colFound[colIt].maskLen + 4U); - - colCnt++; - } - } - } else { - /* Timeout */ - platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES); - } - - /* Check if devices found have reached device limit Activity 2.1 9.3.7.13 (Symbol 12) */ - if(*devCnt >= devLimit) { - return ERR_NONE; - } - - } while(slotNum < RFAL_NFCV_MAX_SLOTS); /* Slot loop */ - colIt++; - } while(colIt < colCnt); /* Collisions found loop */ - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerSleepCollisionResolution( - uint8_t devLimit, - rfalNfcvListenDevice* nfcvDevList, - uint8_t* devCnt) { - uint8_t tmpDevCnt; - ReturnCode ret; - uint8_t i; - - if((nfcvDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = 0; - - do { - tmpDevCnt = 0; - ret = rfalNfcvPollerCollisionResolution( - RFAL_COMPLIANCE_MODE_ISO, (devLimit - *devCnt), &nfcvDevList[*devCnt], &tmpDevCnt); - - for(i = *devCnt; i < (*devCnt + tmpDevCnt); i++) { - rfalNfcvPollerSleep(0x00, nfcvDevList[i].InvRes.UID); - nfcvDevList[i].isSleep = true; - } - *devCnt += tmpDevCnt; - } while((ret == ERR_NONE) && (tmpDevCnt > 0U) && (*devCnt < devLimit)); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerSleep(uint8_t flags, const uint8_t* uid) { - ReturnCode ret; - rfalNfcvSlpvReq slpReq; - uint8_t rxBuf; /* dummy buffer, just to perform Rx */ - - if(uid == NULL) { - return ERR_PARAM; - } - - /* Compute SLPV_REQ */ - slpReq.REQ_FLAG = - (flags | - (uint8_t) - RFAL_NFCV_REQ_FLAG_ADDRESS); /* Should be with UID according Digital 2.0 (Candidate) 9.7.1.1 */ - slpReq.CMD = RFAL_NFCV_CMD_SLPV; - ST_MEMCPY(slpReq.UID, uid, RFAL_NFCV_UID_LEN); - - /* NFC Forum device SHALL wait at least FDTVpp to consider the SLPV acknowledged (FDTVpp = FDTVpoll) Digital 2.0 (Candidate) 9.7 9.8.2 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&slpReq, - sizeof(rfalNfcvSlpvReq), - &rxBuf, - sizeof(rxBuf), - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX1); - if(ret != ERR_TIMEOUT) { - return ret; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerSelect(uint8_t flags, const uint8_t* uid) { - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if(uid == NULL) { - return ERR_PARAM; - } - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_SELECT, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - NULL, - 0U, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t bn; - - bn = blockNum; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - &bn, - sizeof(uint8_t), - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - const uint8_t* wrData, - uint8_t blockLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_LEN + RFAL_NFCV_MAX_BLOCK_LEN)]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - /* Check for valid parameters */ - if((blockLen == 0U) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || (wrData == NULL)) { - return ERR_PARAM; - } - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = blockNum; /* Set Block Number (8 bits) */ - ST_MEMCPY(&data[dataLen], wrData, blockLen); /* Append Block data to write */ - dataLen += blockLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_WRITE_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerLockBlock(uint8_t flags, const uint8_t* uid, uint8_t blockNum) { - uint16_t rcvLen; - rfalNfcvGenericRes res; - uint8_t bn; - - bn = blockNum; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_LOCK_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - &bn, - sizeof(uint8_t), - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_LEN + RFAL_NFCV_BLOCKNUM_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = firstBlockNum; /* Set first Block Number */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_MULTIPLE_BLOCKS, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerWriteMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t blockLen, - const uint8_t* wrData, - uint16_t wrDataLen) { - ReturnCode ret; - uint16_t rcvLen; - uint16_t reqLen; - rfalNfcvGenericRes res; - uint16_t msgIt; - - /* Calculate required buffer length */ - reqLen = - (uint16_t)((uid != NULL) ? (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + RFAL_NFCV_UID_LEN + wrDataLen) : (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + wrDataLen)); - - if((reqLen > txBufLen) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || - ((((uint16_t)numOfBlocks) * (uint16_t)blockLen) != wrDataLen) || (numOfBlocks == 0U) || - (wrData == NULL)) { - return ERR_PARAM; - } - - msgIt = 0; - - /* Compute Request Command */ - txBuf[msgIt++] = (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS))); - txBuf[msgIt++] = RFAL_NFCV_CMD_WRITE_MULTIPLE_BLOCKS; - - /* Check if Request is to be sent in Addressed mode. Select mode flag shall be set by user */ - if(uid != NULL) { - txBuf[RFAL_NFCV_FLAG_POS] |= (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS; - ST_MEMCPY(&txBuf[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += (uint8_t)RFAL_NFCV_UID_LEN; - } - - txBuf[msgIt++] = firstBlockNum; - txBuf[msgIt++] = (numOfBlocks - 1U); - - if(wrDataLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(&txBuf[msgIt], wrData, wrDataLen); - msgIt += wrDataLen; - } - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX); - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if(rcvLen < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((res.RES_FLAG & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return rfalNfcvParseError(*res.data); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_EXTENDED_LEN]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - const uint8_t* wrData, - uint8_t blockLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_EXTENDED_LEN + RFAL_NFCV_MAX_BLOCK_LEN)]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - /* Check for valid parameters */ - if((blockLen == 0U) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN)) { - return ERR_PARAM; - } - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - ST_MEMCPY(&data[dataLen], wrData, blockLen); /* Append Block data to write */ - dataLen += blockLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_WRITE_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode - rfalNfcvPollerExtendedLockSingleBlock(uint8_t flags, const uint8_t* uid, uint16_t blockNum) { - uint8_t data[RFAL_NFCV_BLOCKNUM_EXTENDED_LEN]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_LOCK_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_EXTENDED_LEN + RFAL_NFCV_BLOCKNUM_EXTENDED_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)((firstBlockNum >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((firstBlockNum >> 8U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_READ_MULTIPLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedWriteMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t blockLen, - const uint8_t* wrData, - uint16_t wrDataLen) { - ReturnCode ret; - uint16_t rcvLen; - uint16_t reqLen; - rfalNfcvGenericRes res; - uint16_t msgIt; - uint16_t nBlocks; - - /* Calculate required buffer length */ - reqLen = - ((uid != NULL) ? (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + RFAL_NFCV_UID_LEN + wrDataLen) : - (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + wrDataLen)); - - if((reqLen > txBufLen) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || - (((uint16_t)numOfBlocks * (uint16_t)blockLen) != wrDataLen) || (numOfBlocks == 0U)) { - return ERR_PARAM; - } - - msgIt = 0; - nBlocks = (numOfBlocks - 1U); - - /* Compute Request Command */ - txBuf[msgIt++] = (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS))); - txBuf[msgIt++] = RFAL_NFCV_CMD_EXTENDED_WRITE_MULTIPLE_BLOCK; - - /* Check if Request is to be sent in Addressed mode. Select mode flag shall be set by user */ - if(uid != NULL) { - txBuf[RFAL_NFCV_FLAG_POS] |= (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS; - ST_MEMCPY(&txBuf[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += (uint8_t)RFAL_NFCV_UID_LEN; - } - - txBuf[msgIt++] = (uint8_t)((firstBlockNum >> 0) & 0xFFU); - txBuf[msgIt++] = (uint8_t)((firstBlockNum >> 8) & 0xFFU); - txBuf[msgIt++] = (uint8_t)((nBlocks >> 0) & 0xFFU); - txBuf[msgIt++] = (uint8_t)((nBlocks >> 8) & 0xFFU); - - if(wrDataLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(&txBuf[msgIt], wrData, wrDataLen); - msgIt += wrDataLen; - } - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX); - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if(rcvLen < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((res.RES_FLAG & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return rfalNfcvParseError(*res.data); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerGetSystemInformation( - uint8_t flags, - const uint8_t* uid, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_GET_SYS_INFO, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - NULL, - 0U, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedGetSystemInformation( - uint8_t flags, - const uint8_t* uid, - uint8_t requestField, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_GET_SYS_INFO, - flags, - requestField, - uid, - NULL, - 0U, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerTransceiveReq( - uint8_t cmd, - uint8_t flags, - uint8_t param, - const uint8_t* uid, - const uint8_t* data, - uint16_t dataLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - ReturnCode ret; - rfalNfcvGenericReq req; - uint8_t msgIt; - rfalBitRate rxBR; - bool fastMode; - - msgIt = 0; - fastMode = false; - - /* Check for valid parameters */ - if((rxBuf == NULL) || (rcvLen == NULL) || ((dataLen > 0U) && (data == NULL)) || - (dataLen > ((uid != NULL) ? RFAL_NFCV_MAX_GEN_DATA_LEN : - (RFAL_NFCV_MAX_GEN_DATA_LEN - RFAL_NFCV_UID_LEN)))) { - return ERR_PARAM; - } - - /* Check if the command is an ST's Fast command */ - if((cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_EXTENDED_READ_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_MULTIPLE_BLOCKS) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_EXTENDED_READ_MULTIPLE_BLOCKS) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_WRITE_MESSAGE) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_MESSAGE_LENGTH) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_MESSAGE) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_DYN_CONFIGURATION) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_WRITE_DYN_CONFIGURATION)) { - /* Store current Rx bit rate and move to fast mode */ - rfalGetBitRate(NULL, &rxBR); - rfalSetBitRate(RFAL_BR_KEEP, RFAL_BR_52p97); - - fastMode = true; - } - - /* Compute Request Command */ - req.REQ_FLAG = (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS))); - req.CMD = cmd; - - /* Prepend parameter on ceratin proprietary requests: IC Manuf, Parameters */ - if(param != RFAL_NFCV_PARAM_SKIP) { - req.payload.data[msgIt++] = param; - } - - /* Check if Request is to be sent in Addressed mode. Select mode flag shall be set by user */ - if(uid != NULL) { - req.REQ_FLAG |= (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS; - ST_MEMCPY(&req.payload.data[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += RFAL_NFCV_UID_LEN; - } - - if(dataLen > 0U) { - ST_MEMCPY(&req.payload.data[msgIt], data, dataLen); - msgIt += (uint8_t)dataLen; - } - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&req, - (RFAL_NFCV_CMD_LEN + RFAL_NFCV_FLAG_LEN + (uint16_t)msgIt), - rxBuf, - rxBufLen, - rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX); - - /* If the Option Flag is set in certain commands an EOF needs to be sent after 20ms to retrieve the VICC response ISO15693-3 2009 10.4.2 & 10.4.3 & 10.4.5 */ - if(((flags & (uint8_t)RFAL_NFCV_REQ_FLAG_OPTION) != 0U) && - ((cmd == (uint8_t)RFAL_NFCV_CMD_WRITE_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_WRITE_MULTIPLE_BLOCKS) || - (cmd == (uint8_t)RFAL_NFCV_CMD_LOCK_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_EXTENDED_WRITE_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_EXTENDED_LOCK_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_EXTENDED_WRITE_MULTIPLE_BLOCK))) { - ret = rfalISO15693TransceiveEOF(rxBuf, (uint8_t)rxBufLen, rcvLen); - } - - /* Restore Rx BitRate */ - if(fastMode) { - rfalSetBitRate(RFAL_BR_KEEP, rxBR); - } - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if((*rcvLen) < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((rxBuf[RFAL_NFCV_FLAG_POS] & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return rfalNfcvParseError(rxBuf[RFAL_NFCV_DATASTART_POS]); - } - - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_NFCV */ diff --git a/lib/ST25RFAL002/source/rfal_st25tb.c b/lib/ST25RFAL002/source/rfal_st25tb.c deleted file mode 100644 index 00f699104b..0000000000 --- a/lib/ST25RFAL002/source/rfal_st25tb.c +++ /dev/null @@ -1,563 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25tb.c - * - * \author Gustavo Patricio - * - * \brief Implementation of ST25TB interface - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_st25tb.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ -#ifndef RFAL_FEATURE_ST25TB -#define RFAL_FEATURE_ST25TB false /* ST25TB module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_ST25TB - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_ST25TB_CMD_LEN 1U /*!< ST25TB length of a command */ -#define RFAL_ST25TB_SLOTS 16U /*!< ST25TB number of slots */ -#define RFAL_ST25TB_SLOTNUM_MASK 0x0FU /*!< ST25TB Slot Number bit mask on SlotMarker */ -#define RFAL_ST25TB_SLOTNUM_SHIFT 4U /*!< ST25TB Slot Number shift on SlotMarker */ - -#define RFAL_ST25TB_INITIATE_CMD1 0x06U /*!< ST25TB Initiate command byte1 */ -#define RFAL_ST25TB_INITIATE_CMD2 0x00U /*!< ST25TB Initiate command byte2 */ -#define RFAL_ST25TB_PCALL_CMD1 0x06U /*!< ST25TB Pcall16 command byte1 */ -#define RFAL_ST25TB_PCALL_CMD2 0x04U /*!< ST25TB Pcall16 command byte2 */ -#define RFAL_ST25TB_SELECT_CMD 0x0EU /*!< ST25TB Select command */ -#define RFAL_ST25TB_GET_UID_CMD 0x0BU /*!< ST25TB Get UID command */ -#define RFAL_ST25TB_COMPLETION_CMD 0x0FU /*!< ST25TB Completion command */ -#define RFAL_ST25TB_RESET_INV_CMD 0x0CU /*!< ST25TB Reset to Inventory command */ -#define RFAL_ST25TB_READ_BLOCK_CMD 0x08U /*!< ST25TB Read Block command */ -#define RFAL_ST25TB_WRITE_BLOCK_CMD 0x09U /*!< ST25TB Write Block command */ - -#define RFAL_ST25TB_T0 2157U /*!< ST25TB t0 159 us ST25TB RF characteristics */ -#define RFAL_ST25TB_T1 2048U /*!< ST25TB t1 151 us ST25TB RF characteristics */ - -#define RFAL_ST25TB_FWT \ - (RFAL_ST25TB_T0 + RFAL_ST25TB_T1) /*!< ST25TB FWT = T0 + T1 */ -#define RFAL_ST25TB_TW rfalConvMsTo1fc(7U) /*!< ST25TB TW : Programming time for write max 7ms */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Initiate Request */ -typedef struct { - uint8_t cmd1; /*!< Initiate Request cmd1: 0x06 */ - uint8_t cmd2; /*!< Initiate Request cmd2: 0x00 */ -} rfalSt25tbInitiateReq; - -/*! Pcall16 Request */ -typedef struct { - uint8_t cmd1; /*!< Pcal16 Request cmd1: 0x06 */ - uint8_t cmd2; /*!< Pcal16 Request cmd2: 0x04 */ -} rfalSt25tbPcallReq; - -/*! Select Request */ -typedef struct { - uint8_t cmd; /*!< Select Request cmd: 0x0E */ - uint8_t chipId; /*!< Chip ID */ -} rfalSt25tbSelectReq; - -/*! Read Block Request */ -typedef struct { - uint8_t cmd; /*!< Select Request cmd: 0x08 */ - uint8_t address; /*!< Block address */ -} rfalSt25tbReadBlockReq; - -/*! Write Block Request */ -typedef struct { - uint8_t cmd; /*!< Select Request cmd: 0x09 */ - uint8_t address; /*!< Block address */ - rfalSt25tbBlock data; /*!< Block Data */ -} rfalSt25tbWriteBlockReq; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief ST25TB Poller Do Collision Resolution - * - * This method performs ST25TB Collision resolution loop for each slot - * - * \param[in] devLimit : device limit value, and size st25tbDevList - * \param[out] st25tbDevList : ST35TB listener device info - * \param[out] devCnt : Devices found counter - * - * \return colPending : true if a collision was detected - ***************************************************************************** - */ -static bool rfalSt25tbPollerDoCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt); - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -static bool rfalSt25tbPollerDoCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt) { - uint8_t i; - uint8_t chipId; - ReturnCode ret; - bool col; - - col = false; - - for(i = 0; i < RFAL_ST25TB_SLOTS; i++) { - platformDelay(1); /* Wait t2: Answer to new request delay */ - - if(i == 0U) { - /* Step 2: Send Pcall16 */ - ret = rfalSt25tbPollerPcall(&chipId); - } else { - /* Step 3-17: Send Pcall16 */ - ret = rfalSt25tbPollerSlotMarker(i, &chipId); - } - - if(ret == ERR_NONE) { - /* Found another device */ - st25tbDevList[*devCnt].chipID = chipId; - st25tbDevList[*devCnt].isDeselected = false; - - /* Select Device, retrieve its UID */ - ret = rfalSt25tbPollerSelect(chipId); - - /* By Selecting this device, the previous gets Deselected */ - if((*devCnt) > 0U) { - st25tbDevList[(*devCnt) - 1U].isDeselected = true; - } - - if(ERR_NONE == ret) { - rfalSt25tbPollerGetUID(&st25tbDevList[*devCnt].UID); - } - - if(ERR_NONE == ret) { - (*devCnt)++; - } - } else if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - col = true; - } else { - /* MISRA 15.7 - Empty else */ - } - - if(*devCnt >= devLimit) { - break; - } - } - return col; -} - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerInitialize(void) { - return rfalNfcbPollerInitialize(); -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerCheckPresence(uint8_t* chipId) { - ReturnCode ret; - uint8_t chipIdRes; - - chipIdRes = 0x00; - - /* Send Initiate Request */ - ret = rfalSt25tbPollerInitiate(&chipIdRes); - - /* Check if a transmission error was detected */ - if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - return ERR_NONE; - } - - /* Copy chip ID if requested */ - if(chipId != NULL) { - *chipId = chipIdRes; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerInitiate(uint8_t* chipId) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbInitiateReq initiateReq; - uint8_t rxBuf - [RFAL_ST25TB_CHIP_ID_LEN + - RFAL_ST25TB_CRC_LEN]; /* In case we receive less data that CRC, RF layer will not remove the CRC from buffer */ - - /* Compute Initiate Request */ - initiateReq.cmd1 = RFAL_ST25TB_INITIATE_CMD1; - initiateReq.cmd2 = RFAL_ST25TB_INITIATE_CMD2; - - /* Send Initiate Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&initiateReq, - sizeof(rfalSt25tbInitiateReq), - (uint8_t*)rxBuf, - sizeof(rxBuf), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid Select Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_CHIP_ID_LEN)) { - return ERR_PROTO; - } - - /* Copy chip ID if requested */ - if(chipId != NULL) { - *chipId = *rxBuf; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerPcall(uint8_t* chipId) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbPcallReq pcallReq; - - /* Compute Pcal16 Request */ - pcallReq.cmd1 = RFAL_ST25TB_PCALL_CMD1; - pcallReq.cmd2 = RFAL_ST25TB_PCALL_CMD2; - - /* Send Pcal16 Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&pcallReq, - sizeof(rfalSt25tbPcallReq), - (uint8_t*)chipId, - RFAL_ST25TB_CHIP_ID_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid Select Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_CHIP_ID_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerSlotMarker(uint8_t slotNum, uint8_t* chipIdRes) { - ReturnCode ret; - uint16_t rxLen; - uint8_t slotMarker; - - if((slotNum == 0U) || (slotNum > 15U)) { - return ERR_PARAM; - } - - /* Compute SlotMarker */ - slotMarker = - (((slotNum & RFAL_ST25TB_SLOTNUM_MASK) << RFAL_ST25TB_SLOTNUM_SHIFT) | - RFAL_ST25TB_PCALL_CMD1); - - /* Send SlotMarker */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&slotMarker, - RFAL_ST25TB_CMD_LEN, - (uint8_t*)chipIdRes, - RFAL_ST25TB_CHIP_ID_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid ChipID Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_CHIP_ID_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerSelect(uint8_t chipId) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbSelectReq selectReq; - uint8_t chipIdRes; - - /* Compute Select Request */ - selectReq.cmd = RFAL_ST25TB_SELECT_CMD; - selectReq.chipId = chipId; - - /* Send Select Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&selectReq, - sizeof(rfalSt25tbSelectReq), - (uint8_t*)&chipIdRes, - RFAL_ST25TB_CHIP_ID_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid Select Response */ - if((ret == ERR_NONE) && ((rxLen != RFAL_ST25TB_CHIP_ID_LEN) || (chipIdRes != chipId))) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerGetUID(rfalSt25tbUID* UID) { - ReturnCode ret; - uint16_t rxLen; - uint8_t getUidReq; - - /* Compute Get UID Request */ - getUidReq = RFAL_ST25TB_GET_UID_CMD; - - /* Send Select Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&getUidReq, - RFAL_ST25TB_CMD_LEN, - (uint8_t*)UID, - sizeof(rfalSt25tbUID), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid UID Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_UID_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt) { - uint8_t chipId; - ReturnCode ret; - bool detected; /* collision or device was detected */ - - if((st25tbDevList == NULL) || (devCnt == NULL) || (devLimit == 0U)) { - return ERR_PARAM; - } - - *devCnt = 0; - - /* Step 1: Send Initiate */ - ret = rfalSt25tbPollerInitiate(&chipId); - if(ret == ERR_NONE) { - /* If only 1 answer is detected */ - st25tbDevList[*devCnt].chipID = chipId; - st25tbDevList[*devCnt].isDeselected = false; - - /* Retrieve its UID and keep it Selected*/ - ret = rfalSt25tbPollerSelect(chipId); - - if(ERR_NONE == ret) { - ret = rfalSt25tbPollerGetUID(&st25tbDevList[*devCnt].UID); - } - - if(ERR_NONE == ret) { - (*devCnt)++; - } - } - /* Always proceed to Pcall16 anticollision as phase differences of tags can lead to no tag recognized, even if there is one */ - if(*devCnt < devLimit) { - /* Multiple device responses */ - do { - detected = rfalSt25tbPollerDoCollisionResolution(devLimit, st25tbDevList, devCnt); - } while((detected == true) && (*devCnt < devLimit)); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerReadBlock(uint8_t blockAddress, rfalSt25tbBlock* blockData) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbReadBlockReq readBlockReq; - - /* Compute Read Block Request */ - readBlockReq.cmd = RFAL_ST25TB_READ_BLOCK_CMD; - readBlockReq.address = blockAddress; - - /* Send Read Block Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&readBlockReq, - sizeof(rfalSt25tbReadBlockReq), - (uint8_t*)blockData, - sizeof(rfalSt25tbBlock), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid UID Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_BLOCK_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerWriteBlock(uint8_t blockAddress, const rfalSt25tbBlock* blockData) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbWriteBlockReq writeBlockReq; - rfalSt25tbBlock tmpBlockData; - - /* Compute Write Block Request */ - writeBlockReq.cmd = RFAL_ST25TB_WRITE_BLOCK_CMD; - writeBlockReq.address = blockAddress; - ST_MEMCPY(&writeBlockReq.data, blockData, RFAL_ST25TB_BLOCK_LEN); - - /* Send Write Block Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&writeBlockReq, - sizeof(rfalSt25tbWriteBlockReq), - tmpBlockData, - RFAL_ST25TB_BLOCK_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - (RFAL_ST25TB_FWT + RFAL_ST25TB_TW)); - - /* Check if there was any error besides timeout */ - if(ret != ERR_TIMEOUT) { - /* Check if an unexpected answer was received */ - if(ret == ERR_NONE) { - return ERR_PROTO; - } - - /* Check whether a transmission error occurred */ - if((ret != ERR_CRC) && (ret != ERR_FRAMING) && (ret != ERR_NOMEM) && - (ret != ERR_RF_COLLISION)) { - return ret; - } - - /* If a transmission error occurred (maybe noise while committing data) wait maximum programming time and verify data afterwards */ - rfalSetGT((RFAL_ST25TB_FWT + RFAL_ST25TB_TW)); - rfalFieldOnAndStartGT(); - } - - ret = rfalSt25tbPollerReadBlock(blockAddress, &tmpBlockData); - if(ret == ERR_NONE) { - if(ST_BYTECMP(&tmpBlockData, blockData, RFAL_ST25TB_BLOCK_LEN) == 0) { - return ERR_NONE; - } - return ERR_PROTO; - } - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerCompletion(void) { - uint8_t completionReq; - - /* Compute Completion Request */ - completionReq = RFAL_ST25TB_COMPLETION_CMD; - - /* Send Completion Request, no response is expected */ - return rfalTransceiveBlockingTxRx( - (uint8_t*)&completionReq, - RFAL_ST25TB_CMD_LEN, - NULL, - 0, - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerResetToInventory(void) { - uint8_t resetInvReq; - - /* Compute Completion Request */ - resetInvReq = RFAL_ST25TB_RESET_INV_CMD; - - /* Send Completion Request, no response is expected */ - return rfalTransceiveBlockingTxRx( - (uint8_t*)&resetInvReq, - RFAL_ST25TB_CMD_LEN, - NULL, - 0, - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); -} - -#endif /* RFAL_FEATURE_ST25TB */ diff --git a/lib/ST25RFAL002/source/rfal_st25xv.c b/lib/ST25RFAL002/source/rfal_st25xv.c deleted file mode 100644 index 56c9ccb6fa..0000000000 --- a/lib/ST25RFAL002/source/rfal_st25xv.c +++ /dev/null @@ -1,818 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25xv.c - * - * \author Gustavo Patricio - * - * \brief NFC-V ST25 NFC-V Tag specific features - * - * This module provides support for ST's specific features available on - * NFC-V (ISO15693) tag families: ST25D, ST25TV, M24LR - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_st25xv.h" -#include "rfal_nfcv.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_ST25xV -#define RFAL_FEATURE_ST25xV false /* ST25xV module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_ST25xV - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_ST25xV_READ_CONFIG_LEN \ - 2U /*!< READ CONFIGURATION length */ -#define RFAL_ST25xV_READ_MSG_LEN_LEN \ - 2U /*!< READ MESSAGE LENGTH length */ -#define RFAL_ST25xV_CONF_POINTER_LEN \ - 1U /*!< READ/WRITE CONFIGURATION Pointer length */ -#define RFAL_ST25xV_CONF_REGISTER_LEN \ - 1U /*!< READ/WRITE CONFIGURATION Register length */ -#define RFAL_ST25xV_PWDNUM_LEN \ - 1U /*!< Password Number length */ -#define RFAL_ST25xV_PWD_LEN \ - 8U /*!< Password length */ -#define RFAL_ST25xV_MBPOINTER_LEN \ - 1U /*!< Read Message MBPointer length */ -#define RFAL_ST25xV_NUMBYTES_LEN \ - 1U /*!< Read Message Number of Bytes length */ - -#define RFAL_ST25TV02K_TBOOT_RF \ - 1U /*!< RF Boot time (Minimum time from carrier generation to first data) */ -#define RFAL_ST25TV02K_TRF_OFF \ - 2U /*!< RF OFF time */ - -#define RFAL_ST25xV_FDT_POLL_MAX \ - rfalConvMsTo1fc(20) /*!< Maximum Wait time FDTV,EOF 20 ms Digital 2.1 B.5 */ -#define RFAL_NFCV_FLAG_POS \ - 0U /*!< Flag byte position */ -#define RFAL_NFCV_FLAG_LEN \ - 1U /*!< Flag byte length */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -static ReturnCode rfalST25xVPollerGenericReadConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); -static ReturnCode rfalST25xVPollerGenericWriteConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); -static ReturnCode rfalST25xVPollerGenericReadMessageLength( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t* msgLen); -static ReturnCode rfalST25xVPollerGenericReadMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); -static ReturnCode rfalST25xVPollerGenericWriteMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen); -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericReadConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - ReturnCode ret; - uint8_t p; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if(regValue == NULL) { - return ERR_PARAM; - } - - p = pointer; - - ret = rfalNfcvPollerTransceiveReq( - cmd, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - &p, - sizeof(uint8_t), - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); - if(ret == ERR_NONE) { - if(rcvLen < RFAL_ST25xV_READ_CONFIG_LEN) { - ret = ERR_PROTO; - } else { - *regValue = res.data[0]; - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericWriteConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - uint8_t data[RFAL_ST25xV_CONF_POINTER_LEN + RFAL_ST25xV_CONF_REGISTER_LEN]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - dataLen = 0U; - - data[dataLen++] = pointer; - data[dataLen++] = regValue; - - return rfalNfcvPollerTransceiveReq( - cmd, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericReadMessageLength( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t* msgLen) { - ReturnCode ret; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if(msgLen == NULL) { - return ERR_PARAM; - } - - ret = rfalNfcvPollerTransceiveReq( - cmd, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - NULL, - 0, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); - if(ret == ERR_NONE) { - if(rcvLen < RFAL_ST25xV_READ_MSG_LEN_LEN) { - ret = ERR_PROTO; - } else { - *msgLen = res.data[0]; - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericReadMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_ST25xV_MBPOINTER_LEN + RFAL_ST25xV_NUMBYTES_LEN]; - uint8_t dataLen; - - dataLen = 0; - - /* Compute Request Data */ - data[dataLen++] = mbPointer; - data[dataLen++] = numBytes; - - return rfalNfcvPollerTransceiveReq( - cmd, flags, RFAL_NFCV_ST_IC_MFG_CODE, uid, data, dataLen, rxBuf, rxBufLen, rcvLen); -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericWriteMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen) { - ReturnCode ret; - uint8_t reqFlag; - uint16_t msgIt; - rfalBitRate rxBR; - bool fastMode; - rfalNfcvGenericRes res; - uint16_t rcvLen; - - /* Calculate required Tx buf length: Mfg Code UID MSGLen MSGLen+1 */ - msgIt = - (uint16_t)(msgLen + sizeof(flags) + sizeof(cmd) + 1U + ((uid != NULL) ? RFAL_NFCV_UID_LEN : 0U) + 1U + 1U); - /* Note: MSGlength parameter of the command is the number of Data bytes minus - 1 (00 for 1 byte of data, FFh for 256 bytes of data) */ - - /* Check for valid parameters */ - if((txBuf == NULL) || (msgData == NULL) || (txBufLen < msgIt)) { - return ERR_PARAM; - } - - msgIt = 0; - fastMode = false; - - /* Check if the command is an ST's Fast command */ - if(cmd == (uint8_t)RFAL_NFCV_CMD_FAST_WRITE_MESSAGE) { - /* Store current Rx bit rate and move to fast mode */ - rfalGetBitRate(NULL, &rxBR); - rfalSetBitRate(RFAL_BR_KEEP, RFAL_BR_52p97); - - fastMode = true; - } - - /* Compute Request Command */ - reqFlag = - (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS) & ~((uint32_t)RFAL_NFCV_REQ_FLAG_SELECT))); - reqFlag |= - ((uid != NULL) ? (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS : (uint8_t)RFAL_NFCV_REQ_FLAG_SELECT); - - txBuf[msgIt++] = reqFlag; - txBuf[msgIt++] = cmd; - txBuf[msgIt++] = RFAL_NFCV_ST_IC_MFG_CODE; - - if(uid != NULL) { - ST_MEMCPY(&txBuf[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += RFAL_NFCV_UID_LEN; - } - txBuf[msgIt++] = msgLen; - ST_MEMCPY( - &txBuf[msgIt], - msgData, - (uint16_t)(msgLen + (uint16_t)1U)); /* Message Data contains (MSGLength + 1) bytes */ - msgIt += (uint16_t)(msgLen + (uint16_t)1U); - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25xV_FDT_POLL_MAX); - - /* Restore Rx BitRate */ - if(fastMode) { - rfalSetBitRate(RFAL_BR_KEEP, rxBR); - } - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if(rcvLen < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((res.RES_FLAG & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return ERR_PROTO; - } - - return ERR_NONE; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_M24LR_LEN]; - uint8_t dataLen; - - dataLen = 0; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)blockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(blockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_SINGLE_BLOCK, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - const uint8_t* wrData, - uint8_t blockLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_M24LR_LEN + RFAL_NFCV_MAX_BLOCK_LEN)]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - /* Check for valid parameters */ - if((blockLen == 0U) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || (wrData == NULL)) { - return ERR_PARAM; - } - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)blockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(blockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - ST_MEMCPY(&data[dataLen], wrData, blockLen); /* Append Block data to write */ - dataLen += blockLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_WRITE_SINGLE_BLOCK, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_M24LR_LEN + RFAL_NFCV_BLOCKNUM_M24LR_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)firstBlockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(firstBlockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_MULTIPLE_BLOCKS, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t bn; - - bn = blockNum; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - &bn, - sizeof(uint8_t), - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_M24LR_LEN]; - uint8_t dataLen; - - dataLen = 0; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)blockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(blockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_SINGLE_BLOCK, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_M24LR_LEN + RFAL_NFCV_BLOCKNUM_M24LR_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)firstBlockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(firstBlockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_MULTIPLE_BLOCKS, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_LEN + RFAL_NFCV_BLOCKNUM_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = firstBlockNum; /* Set first Block Number */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_MULTIPLE_BLOCKS, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastExtendedReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_EXTENDED_LEN]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_EXTENDED_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastExtReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_EXTENDED_LEN + RFAL_NFCV_BLOCKNUM_EXTENDED_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)((firstBlockNum >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((firstBlockNum >> 8U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_EXTENDED_READ_MULTIPLE_BLOCKS, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - return rfalST25xVPollerGenericReadConfiguration( - RFAL_NFCV_CMD_READ_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerWriteConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - return rfalST25xVPollerGenericWriteConfiguration( - RFAL_NFCV_CMD_WRITE_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - return rfalST25xVPollerGenericReadConfiguration( - RFAL_NFCV_CMD_READ_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - return rfalST25xVPollerGenericWriteConfiguration( - RFAL_NFCV_CMD_WRITE_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - return rfalST25xVPollerGenericReadConfiguration( - RFAL_NFCV_CMD_FAST_READ_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - return rfalST25xVPollerGenericWriteConfiguration( - RFAL_NFCV_CMD_FAST_WRITE_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerPresentPassword( - uint8_t flags, - const uint8_t* uid, - uint8_t pwdNum, - const uint8_t* pwd, - uint8_t pwdLen) { - uint8_t data[RFAL_ST25xV_PWDNUM_LEN + RFAL_ST25xV_PWD_LEN]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if((pwdLen > RFAL_ST25xV_PWD_LEN) || (pwd == NULL)) { - return ERR_PARAM; - } - - dataLen = 0U; - data[dataLen++] = pwdNum; - if(pwdLen > 0U) { - ST_MEMCPY(&data[dataLen], pwd, pwdLen); - } - dataLen += pwdLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_PRESENT_PASSWORD, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerGetRandomNumber( - uint8_t flags, - const uint8_t* uid, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - rfalFieldOff(); - platformDelay(RFAL_ST25TV02K_TRF_OFF); - rfalNfcvPollerInitialize(); - rfalFieldOnAndStartGT(); - platformDelay(RFAL_ST25TV02K_TBOOT_RF); - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_GET_RANDOM_NUMBER, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - NULL, - 0U, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen) { - return rfalST25xVPollerGenericWriteMessage( - RFAL_NFCV_CMD_WRITE_MESSAGE, flags, uid, msgLen, msgData, txBuf, txBufLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen) { - return rfalST25xVPollerGenericWriteMessage( - RFAL_NFCV_CMD_FAST_WRITE_MESSAGE, flags, uid, msgLen, msgData, txBuf, txBufLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadMessageLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen) { - return rfalST25xVPollerGenericReadMessageLength( - RFAL_NFCV_CMD_READ_MESSAGE_LENGTH, flags, uid, msgLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadMsgLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen) { - return rfalST25xVPollerGenericReadMessageLength( - RFAL_NFCV_CMD_FAST_READ_MESSAGE_LENGTH, flags, uid, msgLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalST25xVPollerGenericReadMessage( - RFAL_NFCV_CMD_READ_MESSAGE, flags, uid, mbPointer, numBytes, rxBuf, rxBufLen, rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalST25xVPollerGenericReadMessage( - RFAL_NFCV_CMD_FAST_READ_MESSAGE, flags, uid, mbPointer, numBytes, rxBuf, rxBufLen, rcvLen); -} - -#endif /* RFAL_FEATURE_ST25xV */ diff --git a/lib/ST25RFAL002/source/rfal_t1t.c b/lib/ST25RFAL002/source/rfal_t1t.c deleted file mode 100644 index be990efcd2..0000000000 --- a/lib/ST25RFAL002/source/rfal_t1t.c +++ /dev/null @@ -1,233 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t1t.c - * - * \author Gustavo Patricio - * - * \brief Provides NFC-A T1T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 1 Tag T1T (Topaz) - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_t1t.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_T1T -#define RFAL_FEATURE_T1T false /* T1T module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_T1T - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_T1T_DRD_READ \ - (1236U * 2U) /*!< DRD for Reads with n=9 => 1236/fc ~= 91 us T1T 1.2 4.4.2 */ -#define RFAL_T1T_DRD_WRITE \ - 36052U /*!< DRD for Write with n=281 => 36052/fc ~= 2659 us T1T 1.2 4.4.2 */ -#define RFAL_T1T_DRD_WRITE_E \ - 70996U /*!< DRD for Write/Erase with n=554 => 70996/fc ~= 5236 us T1T 1.2 4.4.2 */ - -#define RFAL_T1T_RID_RES_HR0_VAL \ - 0x10U /*!< HR0 indicating NDEF support Digital 2.0 (Candidate) 11.6.2.1 */ -#define RFAL_T1T_RID_RES_HR0_MASK \ - 0xF0U /*!< HR0 most significant nibble mask */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A T1T (Topaz) RID_REQ Digital 1.1 10.6.1 & Table 49 */ -typedef struct { - uint8_t cmd; /*!< T1T cmd: RID */ - uint8_t add; /*!< ADD: undefined value */ - uint8_t data; /*!< DATA: undefined value */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< UID-echo: undefined value */ -} rfalT1TRidReq; - -/*! NFC-A T1T (Topaz) RALL_REQ T1T 1.2 Table 4 */ -typedef struct { - uint8_t cmd; /*!< T1T cmd: RALL */ - uint8_t add1; /*!< ADD: 0x00 */ - uint8_t add0; /*!< ADD: 0x00 */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< UID */ -} rfalT1TRallReq; - -/*! NFC-A T1T (Topaz) WRITE_REQ T1T 1.2 Table 4 */ -typedef struct { - uint8_t cmd; /*!< T1T cmd: RALL */ - uint8_t add; /*!< ADD */ - uint8_t data; /*!< DAT */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< UID */ -} rfalT1TWriteReq; - -/*! NFC-A T1T (Topaz) WRITE_RES T1T 1.2 Table 4 */ -typedef struct { - uint8_t add; /*!< ADD */ - uint8_t data; /*!< DAT */ -} rfalT1TWriteRes; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -ReturnCode rfalT1TPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCA_T1T, RFAL_BR_106, RFAL_BR_106)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT( - RFAL_GT_NONE); /* T1T should only be initialized after NFC-A mode, therefore the GT has been fulfilled */ - rfalSetFDTListen( - RFAL_FDT_LISTEN_NFCA_POLLER); /* T1T uses NFC-A FDT Listen with n=9 Digital 1.1 10.7.2 */ - rfalSetFDTPoll(RFAL_FDT_POLL_NFCA_T1T_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalT1TPollerRid(rfalT1TRidRes* ridRes) { - ReturnCode ret; - rfalT1TRidReq ridReq; - uint16_t rcvdLen; - - if(ridRes == NULL) { - return ERR_PARAM; - } - - /* Compute RID command and set Undefined Values to 0x00 Digital 1.1 10.6.1 */ - ST_MEMSET(&ridReq, 0x00, sizeof(rfalT1TRidReq)); - ridReq.cmd = (uint8_t)RFAL_T1T_CMD_RID; - - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&ridReq, - sizeof(rfalT1TRidReq), - (uint8_t*)ridRes, - sizeof(rfalT1TRidRes), - &rcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_T1T_DRD_READ)); - - /* Check expected RID response length and the HR0 Digital 2.0 (Candidate) 11.6.2.1 */ - if((rcvdLen != sizeof(rfalT1TRidRes)) || - ((ridRes->hr0 & RFAL_T1T_RID_RES_HR0_MASK) != RFAL_T1T_RID_RES_HR0_VAL)) { - return ERR_PROTO; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalT1TPollerRall(const uint8_t* uid, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxRcvdLen) { - rfalT1TRallReq rallReq; - - if((rxBuf == NULL) || (uid == NULL) || (rxRcvdLen == NULL)) { - return ERR_PARAM; - } - - /* Compute RALL command and set Add to 0x00 */ - ST_MEMSET(&rallReq, 0x00, sizeof(rfalT1TRallReq)); - rallReq.cmd = (uint8_t)RFAL_T1T_CMD_RALL; - ST_MEMCPY(rallReq.uid, uid, RFAL_T1T_UID_LEN); - - return rfalTransceiveBlockingTxRx( - (uint8_t*)&rallReq, - sizeof(rfalT1TRallReq), - (uint8_t*)rxBuf, - rxBufLen, - rxRcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_T1T_DRD_READ); -} - -/*******************************************************************************/ -ReturnCode rfalT1TPollerWrite(const uint8_t* uid, uint8_t address, uint8_t data) { - rfalT1TWriteReq writeReq; - rfalT1TWriteRes writeRes; - uint16_t rxRcvdLen; - ReturnCode err; - - if(uid == NULL) { - return ERR_PARAM; - } - - writeReq.cmd = (uint8_t)RFAL_T1T_CMD_WRITE_E; - writeReq.add = address; - writeReq.data = data; - ST_MEMCPY(writeReq.uid, uid, RFAL_T1T_UID_LEN); - - err = rfalTransceiveBlockingTxRx( - (uint8_t*)&writeReq, - sizeof(rfalT1TWriteReq), - (uint8_t*)&writeRes, - sizeof(rfalT1TWriteRes), - &rxRcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_T1T_DRD_WRITE_E); - - if(err == ERR_NONE) { - if((writeReq.add != writeRes.add) || (writeReq.data != writeRes.data) || - (rxRcvdLen != sizeof(rfalT1TWriteRes))) { - return ERR_PROTO; - } - } - return err; -} - -#endif /* RFAL_FEATURE_T1T */ diff --git a/lib/ST25RFAL002/source/rfal_t2t.c b/lib/ST25RFAL002/source/rfal_t2t.c deleted file mode 100644 index 2837898a7a..0000000000 --- a/lib/ST25RFAL002/source/rfal_t2t.c +++ /dev/null @@ -1,253 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t2t.c - * - * \author - * - * \brief Provides NFC-A T2T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 2 Tag T2T - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_t2t.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_T2T -#define RFAL_FEATURE_T2T false /* T2T module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_T2T - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_FDT_POLL_READ_MAX \ - rfalConvMsTo1fc( \ - 5U) /*!< Maximum Wait time for Read command as defined in TS T2T 1.0 table 18 */ -#define RFAL_FDT_POLL_WRITE_MAX \ - rfalConvMsTo1fc( \ - 10U) /*!< Maximum Wait time for Write command as defined in TS T2T 1.0 table 18 */ -#define RFAL_FDT_POLL_SL_MAX \ - rfalConvMsTo1fc( \ - 1U) /*!< Maximum Wait time for Sector Select as defined in TS T2T 1.0 table 18 */ -#define RFAL_T2T_ACK_NACK_LEN \ - 1U /*!< Len of NACK in bytes (4 bits) */ -#define RFAL_T2T_ACK \ - 0x0AU /*!< ACK value */ -#define RFAL_T2T_ACK_MASK \ - 0x0FU /*!< ACK value */ - -#define RFAL_T2T_SECTOR_SELECT_P1_BYTE2 \ - 0xFFU /*!< Sector Select Packet 1 byte 2 */ -#define RFAL_T2T_SECTOR_SELECT_P2_RFU_LEN \ - 3U /*!< Sector Select RFU length */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A T2T command set T2T 1.0 5.1 */ -typedef enum { - RFAL_T2T_CMD_READ = 0x30, /*!< T2T Read */ - RFAL_T2T_CMD_WRITE = 0xA2, /*!< T2T Write */ - RFAL_T2T_CMD_SECTOR_SELECT = 0xC2 /*!< T2T Sector Select */ -} rfalT2Tcmds; - -/*! NFC-A T2T READ T2T 1.0 5.2 and table 11 */ -typedef struct { - uint8_t code; /*!< Command code */ - uint8_t blNo; /*!< Block number */ -} rfalT2TReadReq; - -/*! NFC-A T2T WRITE T2T 1.0 5.3 and table 12 */ -typedef struct { - uint8_t code; /*!< Command code */ - uint8_t blNo; /*!< Block number */ - uint8_t data[RFAL_T2T_WRITE_DATA_LEN]; /*!< Data */ -} rfalT2TWriteReq; - -/*! NFC-A T2T SECTOR SELECT Packet 1 T2T 1.0 5.4 and table 13 */ -typedef struct { - uint8_t code; /*!< Command code */ - uint8_t byte2; /*!< Sector Select Packet 1 byte 2 */ -} rfalT2TSectorSelectP1Req; - -/*! NFC-A T2T SECTOR SELECT Packet 2 T2T 1.0 5.4 and table 13 */ -typedef struct { - uint8_t secNo; /*!< Block number */ - uint8_t rfu[RFAL_T2T_SECTOR_SELECT_P2_RFU_LEN]; /*!< Sector Select Packet RFU */ -} rfalT2TSectorSelectP2Req; - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -ReturnCode - rfalT2TPollerRead(uint8_t blockNum, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rcvLen) { - ReturnCode ret; - rfalT2TReadReq req; - - if((rxBuf == NULL) || (rcvLen == NULL)) { - return ERR_PARAM; - } - - req.code = (uint8_t)RFAL_T2T_CMD_READ; - req.blNo = blockNum; - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&req, - sizeof(rfalT2TReadReq), - rxBuf, - rxBufLen, - rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_READ_MAX); - - /* T2T 1.0 5.2.1.7 The Reader/Writer SHALL treat a NACK in response to a READ Command as a Protocol Error */ - if((ret == ERR_INCOMPLETE_BYTE) && (*rcvLen == RFAL_T2T_ACK_NACK_LEN) && - ((*rxBuf & RFAL_T2T_ACK_MASK) != RFAL_T2T_ACK)) { - return ERR_PROTO; - } - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalT2TPollerWrite(uint8_t blockNum, const uint8_t* wrData) { - ReturnCode ret; - rfalT2TWriteReq req; - uint8_t res; - uint16_t rxLen; - - req.code = (uint8_t)RFAL_T2T_CMD_WRITE; - req.blNo = blockNum; - ST_MEMCPY(req.data, wrData, RFAL_T2T_WRITE_DATA_LEN); - - /* Transceive WRITE Command */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&req, - sizeof(rfalT2TWriteReq), - &res, - sizeof(uint8_t), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_READ_MAX); - - /* Check for a valid ACK */ - if((ret == ERR_INCOMPLETE_BYTE) || (ret == ERR_NONE)) { - ret = ERR_PROTO; - - if((rxLen == RFAL_T2T_ACK_NACK_LEN) && ((res & RFAL_T2T_ACK_MASK) == RFAL_T2T_ACK)) { - ret = ERR_NONE; - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalT2TPollerSectorSelect(uint8_t sectorNum) { - rfalT2TSectorSelectP1Req p1Req; - rfalT2TSectorSelectP2Req p2Req; - ReturnCode ret; - uint8_t res; - uint16_t rxLen; - - /* Compute SECTOR SELECT Packet 1 */ - p1Req.code = (uint8_t)RFAL_T2T_CMD_SECTOR_SELECT; - p1Req.byte2 = RFAL_T2T_SECTOR_SELECT_P1_BYTE2; - - /* Transceive SECTOR SELECT Packet 1 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&p1Req, - sizeof(rfalT2TSectorSelectP1Req), - &res, - sizeof(uint8_t), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_SL_MAX); - - /* Check and report any transmission error */ - if((ret != ERR_INCOMPLETE_BYTE) && (ret != ERR_NONE)) { - return ret; - } - - /* Ensure that an ACK was received */ - if((ret != ERR_INCOMPLETE_BYTE) || (rxLen != RFAL_T2T_ACK_NACK_LEN) || - ((res & RFAL_T2T_ACK_MASK) != RFAL_T2T_ACK)) { - return ERR_PROTO; - } - - /* Compute SECTOR SELECT Packet 2 */ - p2Req.secNo = sectorNum; - ST_MEMSET(&p2Req.rfu, 0x00, RFAL_T2T_SECTOR_SELECT_P2_RFU_LEN); - - /* Transceive SECTOR SELECT Packet 2 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&p2Req, - sizeof(rfalT2TSectorSelectP2Req), - &res, - sizeof(uint8_t), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_SL_MAX); - - /* T2T 1.0 5.4.1.14 The Reader/Writer SHALL treat any response received before the end of PATT2T,SL,MAX as a Protocol Error */ - if((ret == ERR_NONE) || (ret == ERR_INCOMPLETE_BYTE)) { - return ERR_PROTO; - } - - /* T2T 1.0 5.4.1.13 The Reader/Writer SHALL treat the transmission of the SECTOR SELECT Command Packet 2 as being successful when it receives no response until PATT2T,SL,MAX. */ - if(ret == ERR_TIMEOUT) { - return ERR_NONE; - } - - return ret; -} - -#endif /* RFAL_FEATURE_T2T */ diff --git a/lib/ST25RFAL002/source/rfal_t4t.c b/lib/ST25RFAL002/source/rfal_t4t.c deleted file mode 100644 index 4c29d79b02..0000000000 --- a/lib/ST25RFAL002/source/rfal_t4t.c +++ /dev/null @@ -1,397 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t4t.h - * - * \author Gustavo Patricio - * - * \brief Provides convenience methods and definitions for T4T (ISO7816-4) - * - * This module provides an interface to exchange T4T APDUs according to - * NFC Forum T4T and ISO7816-4 - * - * This implementation was based on the following specs: - * - ISO/IEC 7816-4 3rd Edition 2013-04-15 - * - NFC Forum T4T Technical Specification 1.0 2017-08-28 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_t4t.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_T4T -#define RFAL_FEATURE_T4T false /* T4T module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_T4T - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_T4T_OFFSET_DO 0x54U /*!< Tag value for offset BER-TLV data object */ -#define RFAL_T4T_LENGTH_DO 0x03U /*!< Len value for offset BER-TLV data object */ -#define RFAL_T4T_DATA_DO 0x53U /*!< Tag value for data BER-TLV data object */ - -#define RFAL_T4T_MAX_LC 255U /*!< Maximum Lc value for short Lc coding */ -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeCAPDU(const rfalT4tCApduParam* apduParam) { - uint8_t hdrLen; - uint16_t msgIt; - - if((apduParam == NULL) || (apduParam->cApduBuf == NULL) || (apduParam->cApduLen == NULL)) { - return ERR_PARAM; - } - - msgIt = 0; - *(apduParam->cApduLen) = 0; - - /*******************************************************************************/ - /* Compute Command-APDU according to the format T4T 1.0 5.1.2 & ISO7816-4 2013 Table 1 */ - - /* Check if Data is present */ - if(apduParam->LcFlag) { - if(apduParam->Lc == 0U) { - /* Extended field coding not supported */ - return ERR_PARAM; - } - - /* Check whether requested Lc fits */ -#pragma GCC diagnostic ignored "-Wtype-limits" - if((uint16_t)apduParam->Lc > - (uint16_t)(RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN - RFAL_T4T_LE_LEN)) { - return ERR_PARAM; /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset */ - } - - /* Calculate the header length a place the data/body where it should be */ - hdrLen = RFAL_T4T_MAX_CAPDU_PROLOGUE_LEN + RFAL_T4T_LC_LEN; - - /* make sure not to exceed buffer size */ - if(((uint16_t)hdrLen + (uint16_t)apduParam->Lc + - (apduParam->LeFlag ? RFAL_T4T_LC_LEN : 0U)) > RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN) { - return ERR_NOMEM; /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset */ - } - ST_MEMMOVE(&apduParam->cApduBuf->apdu[hdrLen], apduParam->cApduBuf->apdu, apduParam->Lc); - } - - /* Prepend the ADPDU's header */ - apduParam->cApduBuf->apdu[msgIt++] = apduParam->CLA; - apduParam->cApduBuf->apdu[msgIt++] = apduParam->INS; - apduParam->cApduBuf->apdu[msgIt++] = apduParam->P1; - apduParam->cApduBuf->apdu[msgIt++] = apduParam->P2; - - /* Check if Data field length is to be added */ - if(apduParam->LcFlag) { - apduParam->cApduBuf->apdu[msgIt++] = apduParam->Lc; - msgIt += apduParam->Lc; - } - - /* Check if Expected Response Length is to be added */ - if(apduParam->LeFlag) { - apduParam->cApduBuf->apdu[msgIt++] = apduParam->Le; - } - - *(apduParam->cApduLen) = msgIt; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerParseRAPDU(rfalT4tRApduParam* apduParam) { - if((apduParam == NULL) || (apduParam->rApduBuf == NULL)) { - return ERR_PARAM; - } - - if(apduParam->rcvdLen < RFAL_T4T_MAX_RAPDU_SW1SW2_LEN) { - return ERR_PROTO; - } - - apduParam->rApduBodyLen = (apduParam->rcvdLen - (uint16_t)RFAL_T4T_MAX_RAPDU_SW1SW2_LEN); - apduParam->statusWord = GETU16((&apduParam->rApduBuf->apdu[apduParam->rApduBodyLen])); - - /* Check SW1 SW2 T4T 1.0 5.1.3 NOTE */ - if(apduParam->statusWord == RFAL_T4T_ISO7816_STATUS_COMPLETE) { - return ERR_NONE; - } - - return ERR_REQUEST; -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeSelectAppl( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* aid, - uint8_t aidLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h A4h 00h 00h 07h AID 00h */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_SELECT; - cAPDU.P1 = RFAL_T4T_ISO7816_P1_SELECT_BY_DF_NAME; - cAPDU.P2 = RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE | - RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE; - cAPDU.Lc = aidLen; - cAPDU.Le = 0x00; - cAPDU.LcFlag = true; - cAPDU.LeFlag = true; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(aidLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, aid, aidLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeSelectFile( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h A4h 00h 0Ch 02h FID - */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_SELECT; - cAPDU.P1 = RFAL_T4T_ISO7816_P1_SELECT_BY_FILEID; - cAPDU.P2 = RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE | - RFAL_T4T_ISO7816_P2_SELECT_NO_RESPONSE_DATA; - cAPDU.Lc = fidLen; - cAPDU.Le = 0x00; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(fidLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, fid, fidLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeSelectFileV1Mapping( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h A4h 00h 00h 02h FID - */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_SELECT; - cAPDU.P1 = RFAL_T4T_ISO7816_P1_SELECT_BY_FILEID; - cAPDU.P2 = RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE | - RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE; - cAPDU.Lc = fidLen; - cAPDU.Le = 0x00; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(fidLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, fid, fidLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeReadData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - uint8_t expLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h B0h [Offset] - - len */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_READBINARY; - cAPDU.P1 = (uint8_t)((offset >> 8U) & 0xFFU); - cAPDU.P2 = (uint8_t)((offset >> 0U) & 0xFFU); - cAPDU.Le = expLen; - cAPDU.LcFlag = false; - cAPDU.LeFlag = true; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeReadDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - uint8_t expLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - uint8_t dataIt; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h B1h 00h 00h Lc 54 03 xxyyzz len */ - /* [Offset] */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_READBINARY_ODO; - cAPDU.P1 = 0x00U; - cAPDU.P2 = 0x00U; - cAPDU.Le = expLen; - cAPDU.LcFlag = true; - cAPDU.LeFlag = true; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - dataIt = 0U; - cApduBuf->apdu[dataIt++] = RFAL_T4T_OFFSET_DO; - cApduBuf->apdu[dataIt++] = RFAL_T4T_LENGTH_DO; - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 16U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 8U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset); - cAPDU.Lc = dataIt; - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeWriteData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h D6h [Offset] len Data - */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_UPDATEBINARY; - cAPDU.P1 = (uint8_t)((offset >> 8U) & 0xFFU); - cAPDU.P2 = (uint8_t)((offset >> 0U) & 0xFFU); - cAPDU.Lc = dataLen; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(dataLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, data, dataLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeWriteDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - uint8_t dataIt; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h D7h 00h 00h len 54 03 xxyyzz 53 Ld data - */ - /* [offset] [data] */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_UPDATEBINARY_ODO; - cAPDU.P1 = 0x00U; - cAPDU.P2 = 0x00U; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - dataIt = 0U; - cApduBuf->apdu[dataIt++] = RFAL_T4T_OFFSET_DO; - cApduBuf->apdu[dataIt++] = RFAL_T4T_LENGTH_DO; - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 16U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 8U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset); - cApduBuf->apdu[dataIt++] = RFAL_T4T_DATA_DO; - cApduBuf->apdu[dataIt++] = dataLen; - - if((((uint32_t)dataLen + (uint32_t)dataIt) >= RFAL_T4T_MAX_LC) || - (((uint32_t)dataLen + (uint32_t)dataIt) >= RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN)) { - return (ERR_NOMEM); - } - - if(dataLen > 0U) { - ST_MEMCPY(&cAPDU.cApduBuf->apdu[dataIt], data, dataLen); - } - dataIt += dataLen; - cAPDU.Lc = dataIt; - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -#endif /* RFAL_FEATURE_T4T */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h b/lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h deleted file mode 100644 index 554d6539a6..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h +++ /dev/null @@ -1,1477 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_analogConfig.h - * - * \author bkam - * - * \brief ST25R3916 Analog Configuration Settings - * - */ - -#ifndef ST25R3916_ANALOGCONFIG_H -#define ST25R3916_ANALOGCONFIG_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_analogConfig.h" -#include "st25r3916_com.h" - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Macro for Configuration Setting with only one register-mask-value set: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1] */ -#define MODE_ENTRY_1_REG(MODE, R0, M0, V0) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 1, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0) - -/*! Macro for Configuration Setting with only two register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1] */ -#define MODE_ENTRY_2_REG(MODE, R0, M0, V0, R1, M1, V1) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 2, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1) - -/*! Macro for Configuration Setting with only three register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_3_REG(MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 3, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2) - -/*! Macro for Configuration Setting with only four register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_4_REG(MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 4, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3) - -/*! Macro for Configuration Setting with only five register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_5_REG(MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3, R4, M4, V4) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 5, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4) - -/*! Macro for Configuration Setting with only six register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_6_REG( \ - MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3, R4, M4, V4, R5, M5, V5) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 6, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5) - -/*! Macro for Configuration Setting with only seven register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_7_REG( \ - MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3, R4, M4, V4, R5, M5, V5, R6, M6, V6) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 7, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6) - -/*! Macro for Configuration Setting with only eight register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_8_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 8, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7) - -/*! Macro for Configuration Setting with only nine register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_9_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 9, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8) - -/*! Macro for Configuration Setting with only ten register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_10_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 10, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9) - -/*! Macro for Configuration Setting with eleven register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_11_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 11, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8U), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10) - -/*! Macro for Configuration Setting with twelve register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_12_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 12, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8U), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8U), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11) - -/*! Macro for Configuration Setting with thirteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_13_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 13, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8U), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8U), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8U), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12) - -/*! Macro for Configuration Setting with fourteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_14_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 14, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13) - -/*! Macro for Configuration Setting with fifteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_15_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 15, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13), \ - (uint8_t)((uint16_t)(R14) >> 8), (uint8_t)((R14)&0xFFU), (uint8_t)(M14), (uint8_t)(V14) - -/*! Macro for Configuration Setting with sixteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_16_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 16, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13), \ - (uint8_t)((uint16_t)(R14) >> 8), (uint8_t)((R14)&0xFFU), (uint8_t)(M14), (uint8_t)(V14), \ - (uint8_t)((uint16_t)(R15) >> 8), (uint8_t)((R15)&0xFFU), (uint8_t)(M15), (uint8_t)(V15) - -/*! Macro for Configuration Setting with seventeen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_17_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15, \ - R16, \ - M16, \ - V16) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 17, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13), \ - (uint8_t)((uint16_t)(R14) >> 8), (uint8_t)((R14)&0xFFU), (uint8_t)(M14), (uint8_t)(V14), \ - (uint8_t)((uint16_t)(R15) >> 8), (uint8_t)((R15)&0xFFU), (uint8_t)(M15), (uint8_t)(V15), \ - (uint8_t)((uint16_t)(R16) >> 8), (uint8_t)((R16)&0xFFU), (uint8_t)(M16), (uint8_t)(V16) -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ -/* PRQA S 3406 1 # MISRA 8.6 - Externally generated table included by the library */ /* PRQA S 1514 1 # MISRA 8.9 - Externally generated table included by the library */ -const uint8_t rfalAnalogConfigDefaultSettings[] = { - - /****** Default Analog Configuration for Chip-Specific Reset ******/ - MODE_ENTRY_17_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_INIT), - ST25R3916_REG_IO_CONF1, - (ST25R3916_REG_IO_CONF1_out_cl_mask | ST25R3916_REG_IO_CONF1_lf_clk_off), - 0x07 /* Disable MCU_CLK */ - , - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2), - 0x18 /* SPI Pull downs */ - , - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_aat_en, - ST25R3916_REG_IO_CONF2_aat_en /* Enable AAT */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_d_res_mask, - 0x00 /* Set RFO resistance Active Tx */ - , - ST25R3916_REG_RES_AM_MOD, - 0xFF, - 0x80 /* Use minimum non-overlap */ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_ext, - 0x00 /* Disable External Load Modulation */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_dri, - ST25R3916_REG_AUX_MOD_lm_dri /* Use internal Load Modulation */ - , - ST25R3916_REG_PASSIVE_TARGET, - ST25R3916_REG_PASSIVE_TARGET_fdel_mask, - (5U - << ST25R3916_REG_PASSIVE_TARGET_fdel_shift) /* Adjust the FDT to be aligned with the bitgrid */ - , - ST25R3916_REG_PT_MOD, - (ST25R3916_REG_PT_MOD_ptm_res_mask | ST25R3916_REG_PT_MOD_pt_res_mask), - 0x5f /* Reduce RFO resistance in Modulated state */ - , - ST25R3916_REG_EMD_SUP_CONF, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv_on /* Enable start on first 4 bits */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - 0x84U, - 0x10, - 0x10 /* Avoid chip internal overheat protection */ - ) - - /****** Default Analog Configuration for Chip-Specific Poll Common ******/ - , - MODE_ENTRY_9_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) - - /****** Default Analog Configuration for Poll NFC-A Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x08, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x51, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 212 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 424 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 848 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_40percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Anticolision setting ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL), - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s6, - 0x00 /* Set collision detection level different from data */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Coherent Receiver */ - ) -#else - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll NFC-B Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x04, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x1B, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#ifdef RFAL_USE_COHE - - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#else - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#endif /*RFAL_USE_COHE*/ - - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | RFAL_ANALOG_CONFIG_BITRATE_1OF4 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#else - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Chip-Specific Listen On ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x00 /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0xff /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Rx Common ******/ - , - MODE_ENTRY_3_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_lp_mask, - ST25R3916_REG_RX_CONF1_lp_1200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_hz_mask, - ST25R3916_REG_RX_CONF1_hz_12_200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF2, - ST25R3916_REG_RX_CONF2_amd_sel, - ST25R3916_REG_RX_CONF2_amd_sel_mixer /* AM demodulator: mixer */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_106 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_212 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_424 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - -}; - -#endif /* ST25R3916_ANALOGCONFIG_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h b/lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h deleted file mode 100644 index 3075776c55..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h +++ /dev/null @@ -1,68 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Martin Zechleitner - * - * \brief RF Dynamic Power Table default values - */ - -#ifndef ST25R3916_DPO_H -#define ST25R3916_DPO_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_dpo.h" - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -/*! Default DPO table */ -const uint8_t rfalDpoDefaultSettings[] = { - 0x00, - 255, - 200, - 0x01, - 210, - 150, - 0x02, - 160, - 100, - 0x03, - 110, - 50, -}; - -#endif /* ST25R3916_DPO_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_features.h b/lib/ST25RFAL002/source/st25r3916/rfal_features.h deleted file mode 100644 index 92f26acf57..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_features.h +++ /dev/null @@ -1,109 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief RFAL Features/Capabilities Definition for ST25R3916 - */ - -#ifndef RFAL_FEATURES_H -#define RFAL_FEATURES_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define RFAL_SUPPORT_MODE_POLL_NFCA true /*!< RFAL Poll NFCA mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_NFCB true /*!< RFAL Poll NFCB mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_NFCF true /*!< RFAL Poll NFCF mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_NFCV true /*!< RFAL Poll NFCV mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_ACTIVE_P2P true /*!< RFAL Poll AP2P mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_NFCA true /*!< RFAL Listen NFCA mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_NFCB false /*!< RFAL Listen NFCB mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_NFCF true /*!< RFAL Listen NFCF mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_ACTIVE_P2P true /*!< RFAL Listen AP2P mode support switch */ - -/*******************************************************************************/ -/*! RFAL supported Card Emulation (CE) */ -#define RFAL_SUPPORT_CE \ - (RFAL_SUPPORT_MODE_LISTEN_NFCA || RFAL_SUPPORT_MODE_LISTEN_NFCB || \ - RFAL_SUPPORT_MODE_LISTEN_NFCF) - -/*! RFAL supported Reader/Writer (RW) */ -#define RFAL_SUPPORT_RW \ - (RFAL_SUPPORT_MODE_POLL_NFCA || RFAL_SUPPORT_MODE_POLL_NFCB || RFAL_SUPPORT_MODE_POLL_NFCF || \ - RFAL_SUPPORT_MODE_POLL_NFCV) - -/*! RFAL support for Active P2P (AP2P) */ -#define RFAL_SUPPORT_AP2P \ - (RFAL_SUPPORT_MODE_POLL_ACTIVE_P2P || RFAL_SUPPORT_MODE_LISTEN_ACTIVE_P2P) - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_RW_106 true /*!< RFAL RW 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_212 true /*!< RFAL RW 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_424 true /*!< RFAL RW 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_848 true /*!< RFAL RW 848 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_1695 false /*!< RFAL RW 1695 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_3390 false /*!< RFAL RW 3390 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_6780 false /*!< RFAL RW 6780 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_13560 false /*!< RFAL RW 6780 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_AP2P_106 true /*!< RFAL AP2P 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_AP2P_212 true /*!< RFAL AP2P 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_AP2P_424 true /*!< RFAL AP2P 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_AP2P_848 false /*!< RFAL AP2P 848 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_CE_A_106 true /*!< RFAL CE A 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_A_212 false /*!< RFAL CE A 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_A_424 false /*!< RFAL CE A 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_A_848 false /*!< RFAL CE A 848 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_CE_B_106 false /*!< RFAL CE B 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_B_212 false /*!< RFAL CE B 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_B_424 false /*!< RFAL CE B 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_B_848 false /*!< RFAL CE B 848 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_CE_F_212 true /*!< RFAL CE F 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_F_424 true /*!< RFAL CE F 424 Bit Rate support switch */ - -#endif /* RFAL_FEATURES_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c b/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c deleted file mode 100644 index 7b8c243b13..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c +++ /dev/null @@ -1,4815 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief RF Abstraction Layer (RFAL) - * - * RFAL implementation for ST25R3916 - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "rfal_chip.h" -#include "utils.h" -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "st25r3916_irq.h" -#include "rfal_analogConfig.h" -#include "rfal_iso15693_2.h" -#include "rfal_crc.h" - -/* - ****************************************************************************** - * ENABLE SWITCHES - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_LISTEN_MODE -#define RFAL_FEATURE_LISTEN_MODE false /* Listen Mode configuration missing. Disabled by default */ -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#ifndef RFAL_FEATURE_WAKEUP_MODE -#define RFAL_FEATURE_WAKEUP_MODE \ - false /* Wake-Up mode configuration missing. Disabled by default */ -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#ifndef RFAL_FEATURE_LOWPOWER_MODE -#define RFAL_FEATURE_LOWPOWER_MODE \ - false /* Low Power mode configuration missing. Disabled by default */ -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Struct that holds all involved on a Transceive including the context passed by the caller */ -typedef struct { - rfalTransceiveState state; /*!< Current transceive state */ - rfalTransceiveState lastState; /*!< Last transceive state (debug purposes) */ - ReturnCode status; /*!< Current status/error of the transceive */ - - rfalTransceiveContext ctx; /*!< The transceive context given by the caller */ -} rfalTxRx; - -/*! Struct that holds all context for the Listen Mode */ -typedef struct { - rfalLmState state; /*!< Current Listen Mode state */ - uint32_t mdMask; /*!< Listen Mode mask used */ - uint32_t mdReg; /*!< Listen Mode register value used */ - uint32_t mdIrqs; /*!< Listen Mode IRQs used */ - rfalBitRate brDetected; /*!< Last bit rate detected */ - - uint8_t* rxBuf; /*!< Location to store incoming data in Listen Mode */ - uint16_t rxBufLen; /*!< Length of rxBuf */ - uint16_t* rxLen; /*!< Pointer to write the data length placed into rxBuf */ - bool dataFlag; /*!< Listen Mode current Data Flag */ - bool iniFlag; /*!< Listen Mode initialized Flag (FeliCa slots) */ -} rfalLm; - -/*! Struct that holds all context for the Wake-Up Mode */ -typedef struct { - rfalWumState state; /*!< Current Wake-Up Mode state */ - rfalWakeUpConfig cfg; /*!< Current Wake-Up Mode context */ -} rfalWum; - -/*! Struct that holds all context for the Low Power Mode */ -typedef struct { - bool isRunning; -} rfalLpm; - -/*! Struct that holds the timings GT and FDTs */ -typedef struct { - uint32_t GT; /*!< GT in 1/fc */ - uint32_t FDTListen; /*!< FDTListen in 1/fc */ - uint32_t FDTPoll; /*!< FDTPoll in 1/fc */ - uint8_t nTRFW; /*!< n*TRFW used during RF CA */ -} rfalTimings; - -/*! Struct that holds the software timers */ -typedef struct { - uint32_t GT; /*!< RFAL's GT timer */ - uint32_t RXE; /*!< Timer between RXS and RXE */ - uint32_t txRx; /*!< Transceive sanity timer */ -} rfalTimers; - -/*! Struct that holds the RFAL's callbacks */ -typedef struct { - rfalPreTxRxCallback preTxRx; /*!< RFAL's Pre TxRx callback */ - rfalPostTxRxCallback postTxRx; /*!< RFAL's Post TxRx callback */ - RfalStateChangedCallback state_changed_cb; - void* ctx; -} rfalCallbacks; - -/*! Struct that holds counters to control the FIFO on Tx and Rx */ -typedef struct { - uint16_t - expWL; /*!< The amount of bytes expected to be Tx when a WL interrupt occurs */ - uint16_t - bytesTotal; /*!< Total bytes to be transmitted OR the total bytes received */ - uint16_t - bytesWritten; /*!< Amount of bytes already written on FIFO (Tx) OR read (RX) from FIFO and written on rxBuffer*/ - uint8_t status - [ST25R3916_FIFO_STATUS_LEN]; /*!< FIFO Status Registers */ -} rfalFIFO; - -/*! Struct that holds RFAL's configuration settings */ -typedef struct { - uint8_t obsvModeTx; /*!< RFAL's config of the ST25R3916's observation mode while Tx */ - uint8_t obsvModeRx; /*!< RFAL's config of the ST25R3916's observation mode while Rx */ - rfalEHandling eHandling; /*!< RFAL's error handling config/mode */ -} rfalConfigs; - -/*! Struct that holds NFC-F data - Used only inside rfalFelicaPoll() (static to avoid adding it into stack) */ -typedef struct { - rfalFeliCaPollRes - pollResponses[RFAL_FELICA_POLL_MAX_SLOTS]; /* FeliCa Poll response container for 16 slots */ -} rfalNfcfWorkingData; - -/*! Struct that holds NFC-V current context - * - * This buffer has to be big enough for coping with maximum response size (hamming coded) - * - inventory requests responses: 14*2+2 bytes - * - read single block responses: (32+4)*2+2 bytes - * - read multiple block could be very long... -> not supported - * - current implementation expects it be written in one bulk into FIFO - * - needs to be above FIFO water level of ST25R3916 (200) - * - the coding function needs to be able to - * put more than FIFO water level bytes into it (n*64+1)>200 */ -typedef struct { - uint8_t codingBuffer[( - (2 + 255 + 3) * 2)]; /*!< Coding buffer, length MUST be above 257: [257; ...] */ - uint16_t - nfcvOffset; /*!< Offset needed for ISO15693 coding function */ - rfalTransceiveContext - origCtx; /*!< context provided by user */ - uint16_t - ignoreBits; /*!< Number of bits at the beginning of a frame to be ignored when decoding */ -} rfalNfcvWorkingData; - -/*! RFAL instance */ -typedef struct { - rfalState state; /*!< RFAL's current state */ - rfalMode mode; /*!< RFAL's current mode */ - rfalBitRate txBR; /*!< RFAL's current Tx Bit Rate */ - rfalBitRate rxBR; /*!< RFAL's current Rx Bit Rate */ - bool field; /*!< Current field state (On / Off) */ - - rfalConfigs conf; /*!< RFAL's configuration settings */ - rfalTimings timings; /*!< RFAL's timing setting */ - rfalTxRx TxRx; /*!< RFAL's transceive management */ - rfalFIFO fifo; /*!< RFAL's FIFO management */ - rfalTimers tmr; /*!< RFAL's Software timers */ - rfalCallbacks callbacks; /*!< RFAL's callbacks */ - -#if RFAL_FEATURE_LISTEN_MODE - rfalLm Lm; /*!< RFAL's listen mode management */ -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#if RFAL_FEATURE_WAKEUP_MODE - rfalWum wum; /*!< RFAL's Wake-up mode management */ -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#if RFAL_FEATURE_LOWPOWER_MODE - rfalLpm lpm; /*!< RFAL's Low power mode management */ -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - -#if RFAL_FEATURE_NFCF - rfalNfcfWorkingData nfcfData; /*!< RFAL's working data when supporting NFC-F */ -#endif /* RFAL_FEATURE_NFCF */ - -#if RFAL_FEATURE_NFCV - rfalNfcvWorkingData nfcvData; /*!< RFAL's working data when performing NFC-V */ -#endif /* RFAL_FEATURE_NFCV */ - -} rfal; - -/*! Felica's command set */ -typedef enum { - FELICA_CMD_POLLING = - 0x00, /*!< Felica Poll/REQC command (aka SENSF_REQ) to identify a card */ - FELICA_CMD_POLLING_RES = - 0x01, /*!< Felica Poll/REQC command (aka SENSF_RES) response */ - FELICA_CMD_REQUEST_SERVICE = - 0x02, /*!< verify the existence of Area and Service */ - FELICA_CMD_REQUEST_RESPONSE = - 0x04, /*!< verify the existence of a card */ - FELICA_CMD_READ_WITHOUT_ENCRYPTION = - 0x06, /*!< read Block Data from a Service that requires no authentication */ - FELICA_CMD_WRITE_WITHOUT_ENCRYPTION = - 0x08, /*!< write Block Data to a Service that requires no authentication */ - FELICA_CMD_REQUEST_SYSTEM_CODE = - 0x0C, /*!< acquire the System Code registered to a card */ - FELICA_CMD_AUTHENTICATION1 = - 0x10, /*!< authenticate a card */ - FELICA_CMD_AUTHENTICATION2 = - 0x12, /*!< allow a card to authenticate a Reader/Writer */ - FELICA_CMD_READ = 0x14, /*!< read Block Data from a Service that requires authentication */ - FELICA_CMD_WRITE = 0x16, /*!< write Block Data to a Service that requires authentication */ -} t_rfalFeliCaCmd; - -/*! Union representing all PTMem sections */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Both members are of the same type, just different names. Thus no problem can occur. */ - uint8_t PTMem_A - [ST25R3916_PTM_A_LEN]; /*!< PT_Memory area allocated for NFC-A configuration */ - uint8_t PTMem_F - [ST25R3916_PTM_F_LEN]; /*!< PT_Memory area allocated for NFC-F configuration */ - uint8_t - TSN[ST25R3916_PTM_TSN_LEN]; /*!< PT_Memory area allocated for TSN - Random numbers */ -} t_rfalPTMem; - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define RFAL_FIFO_IN_WL \ - 200U /*!< Number of bytes in the FIFO when WL interrupt occurs while Tx */ -#define RFAL_FIFO_OUT_WL \ - (ST25R3916_FIFO_DEPTH - \ - RFAL_FIFO_IN_WL) /*!< Number of bytes sent/out of the FIFO when WL interrupt occurs while Tx */ - -#define RFAL_FIFO_STATUS_REG1 \ - 0U /*!< Location of FIFO status register 1 in local copy */ -#define RFAL_FIFO_STATUS_REG2 \ - 1U /*!< Location of FIFO status register 2 in local copy */ -#define RFAL_FIFO_STATUS_INVALID \ - 0xFFU /*!< Value indicating that the local FIFO status in invalid|cleared */ - -#define RFAL_ST25R3916_GPT_MAX_1FC \ - rfalConv8fcTo1fc( \ - 0xFFFFU) /*!< Max GPT steps in 1fc (0xFFFF steps of 8/fc => 0xFFFF * 590ns = 38,7ms) */ -#define RFAL_ST25R3916_NRT_MAX_1FC \ - rfalConv4096fcTo1fc( \ - 0xFFFFU) /*!< Max NRT steps in 1fc (0xFFFF steps of 4096/fc => 0xFFFF * 302us = 19.8s ) */ -#define RFAL_ST25R3916_NRT_DISABLED \ - 0U /*!< NRT Disabled: All 0 No-response timer is not started, wait forever */ -#define RFAL_ST25R3916_MRT_MAX_1FC \ - rfalConv64fcTo1fc( \ - 0x00FFU) /*!< Max MRT steps in 1fc (0x00FF steps of 64/fc => 0x00FF * 4.72us = 1.2ms ) */ -#define RFAL_ST25R3916_MRT_MIN_1FC \ - rfalConv64fcTo1fc( \ - 0x0004U) /*!< Min MRT steps in 1fc ( 0<=mrt<=4 ; 4 (64/fc) => 0x0004 * 4.72us = 18.88us ) */ -#define RFAL_ST25R3916_GT_MAX_1FC \ - rfalConvMsTo1fc( \ - 6000U) /*!< Max GT value allowed in 1/fc (SFGI=14 => SFGT + dSFGT = 5.4s) */ -#define RFAL_ST25R3916_GT_MIN_1FC \ - rfalConvMsTo1fc( \ - RFAL_ST25R3916_SW_TMR_MIN_1MS) /*!< Min GT value allowed in 1/fc */ -#define RFAL_ST25R3916_SW_TMR_MIN_1MS \ - 1U /*!< Min value of a SW timer in ms */ - -#define RFAL_OBSMODE_DISABLE \ - 0x00U /*!< Observation Mode disabled */ - -#define RFAL_RX_INCOMPLETE_MAXLEN \ - (uint8_t)1U /*!< Threshold value where incoming rx may be considered as incomplete */ -#define RFAL_EMVCO_RX_MAXLEN \ - (uint8_t)4U /*!< Maximum value where EMVCo to apply special error handling */ - -#define RFAL_NORXE_TOUT \ - 50U /*!< Timeout to be used on a potential missing RXE - Silicon ST25R3916 Errata #TBD */ - -#define RFAL_ISO14443A_SDD_RES_LEN \ - 5U /*!< SDD_RES | Anticollision (UID CLn) length - rfalNfcaSddRes */ -#define RFAL_ISO14443A_CRC_INTVAL \ - 0x6363 /*!< ISO14443 CRC Initial Value|Register */ - -#define RFAL_FELICA_POLL_DELAY_TIME \ - 512U /*!< FeliCa Poll Processing time is 2.417 ms ~512*64/fc Digital 1.1 A4 */ -#define RFAL_FELICA_POLL_SLOT_TIME \ - 256U /*!< FeliCa Poll Time Slot duration is 1.208 ms ~256*64/fc Digital 1.1 A4 */ - -#define RFAL_LM_SENSF_RD0_POS \ - 17U /*!< FeliCa SENSF_RES Request Data RD0 position */ -#define RFAL_LM_SENSF_RD1_POS \ - 18U /*!< FeliCa SENSF_RES Request Data RD1 position */ - -#define RFAL_LM_NFCID_INCOMPLETE \ - 0x04U /*!< NFCA NFCID not complete bit in SEL_RES (SAK) */ - -#define RFAL_ISO15693_IGNORE_BITS \ - rfalConvBytesToBits( \ - 2U) /*!< Ignore collisions before the UID (RES_FLAG + DSFID) */ -#define RFAL_ISO15693_INV_RES_LEN \ - 12U /*!< ISO15693 Inventory response length with CRC (bytes) */ -#define RFAL_ISO15693_INV_RES_DUR \ - 4U /*!< ISO15693 Inventory response duration @ 26 kbps (ms) */ - -#define RFAL_WU_MIN_WEIGHT_VAL \ - 4U /*!< ST25R3916 minimum Wake-up weight value */ - -/*******************************************************************************/ - -#define RFAL_LM_GT \ - rfalConvUsTo1fc( \ - 100U) /*!< Listen Mode Guard Time enforced (GT - Passive; TIRFG - Active) */ -#define RFAL_FDT_POLL_ADJUSTMENT \ - rfalConvUsTo1fc( \ - 80U) /*!< FDT Poll adjustment: Time between the expiration of GPT to the actual Tx */ -#define RFAL_FDT_LISTEN_MRT_ADJUSTMENT \ - 64U /*!< MRT jitter adjustment: timeout will be between [ tout ; tout + 64 cycles ] */ -#define RFAL_AP2P_FIELDOFF_TRFW \ - rfalConv8fcTo1fc( \ - 64U) /*!< Time after TXE and Field Off in AP2P Trfw: 37.76us -> 64 (8/fc) */ - -#ifndef RFAL_ST25R3916_AAT_SETTLE -#define RFAL_ST25R3916_AAT_SETTLE \ - 5U /*!< Time in ms required for AAT pins and Osc to settle after en bit set */ -#endif /* RFAL_ST25R3916_AAT_SETTLE */ - -/*! FWT adjustment: - * 64 : NRT jitter between TXE and NRT start */ -#define RFAL_FWT_ADJUSTMENT 64U - -/*! FWT ISO14443A adjustment: - * 512 : 4bit length - * 64 : Half a bit duration due to ST25R3916 Coherent receiver (1/fc) */ -#define RFAL_FWT_A_ADJUSTMENT (512U + 64U) - -/*! FWT ISO14443B adjustment: - * SOF (14etu) + 1Byte (10etu) + 1etu (IRQ comes 1etu after first byte) - 3etu (ST25R3916 sends TXE 3etu after) */ -#define RFAL_FWT_B_ADJUSTMENT ((14U + 10U + 1U - 3U) * 128U) - -/*! FWT FeliCa 212 adjustment: - * 1024 : Length of the two Sync bytes at 212kbps */ -#define RFAL_FWT_F_212_ADJUSTMENT 1024U - -/*! FWT FeliCa 424 adjustment: - * 512 : Length of the two Sync bytes at 424kbps */ -#define RFAL_FWT_F_424_ADJUSTMENT 512U - -/*! Time between our field Off and other peer field On : Tadt + (n x Trfw) - * Ecma 340 11.1.2 - Tadt: [56.64 , 188.72] us ; n: [0 , 3] ; Trfw = 37.76 us - * Should be: 189 + (3*38) = 303us ; we'll use a more relaxed setting: 605 us */ -#define RFAL_AP2P_FIELDON_TADTTRFW rfalConvUsTo1fc(605U) - -/*! FDT Listen adjustment for ISO14443A EMVCo 2.6 4.8.1.3 ; Digital 1.1 6.10 - * - * 276: Time from the rising pulse of the pause of the logic '1' (i.e. the time point to measure the deaftime from), - * to the actual end of the EOF sequence (the point where the MRT starts). Please note that the ST25R391x uses the - * ISO14443-2 definition where the EOF consists of logic '0' followed by sequence Y. - * -64: Further adjustment for receiver to be ready just before first bit - */ -#define RFAL_FDT_LISTEN_A_ADJUSTMENT (276U - 64U) - -/*! FDT Listen adjustment for ISO14443B EMVCo 2.6 4.8.1.6 ; Digital 1.1 7.9 - * - * 340: Time from the rising edge of the EoS to the starting point of the MRT timer (sometime after the final high - * part of the EoS is completed) - */ -#define RFAL_FDT_LISTEN_B_ADJUSTMENT 340U - -/*! FDT Listen adjustment for ISO15693 - * ISO15693 2000 8.4 t1 MIN = 4192/fc - * ISO15693 2009 9.1 t1 MIN = 4320/fc - * Digital 2.1 B.5 FDTV,LISTEN,MIN = 4310/fc - * Set FDT Listen one step earlier than on the more recent spec versions for greater interoperability - */ -#define RFAL_FDT_LISTEN_V_ADJUSTMENT 64U - -/*! FDT Poll adjustment for ISO14443B Correlator - sst 5 etu */ -#define RFAL_FDT_LISTEN_B_ADJT_CORR 128U - -/*! FDT Poll adjustment for ISO14443B Correlator sst window - 5 etu */ -#define RFAL_FDT_LISTEN_B_ADJT_CORR_SST 20U - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Calculates Transceive Sanity Timer. It accounts for the slowest bit rate and the longest data format - * 1s for transmission and reception of a 4K message at 106kpbs (~425ms each direction) - * plus TxRx preparation and FIFO load over Serial Interface */ -#define rfalCalcSanityTmr(fwt) (uint16_t)(1000U + rfalConv1fcToMs((fwt))) - -#define rfalGennTRFW(n) \ - (((n) + 1U) & \ - ST25R3916_REG_AUX_nfc_n_mask) /*!< Generates the next n*TRRW used for RFCA */ - -#define rfalCalcNumBytes(nBits) \ - (((uint32_t)(nBits) + 7U) / \ - 8U) /*!< Returns the number of bytes required to fit given the number of bits */ - -#define rfalTimerStart(timer, time_ms) \ - do { \ - platformTimerDestroy(timer); \ - (timer) = platformTimerCreate((uint16_t)(time_ms)); \ - } while(0) /*!< Configures and starts timer */ -#define rfalTimerisExpired(timer) \ - platformTimerIsExpired( \ - timer) /*!< Checks if timer has expired */ -#define rfalTimerDestroy(timer) \ - platformTimerDestroy( \ - timer) /*!< Destroys timer */ - -#define rfalST25R3916ObsModeDisable() \ - st25r3916WriteTestRegister( \ - 0x01U, \ - (0x40U)) /*!< Disable ST25R3916 Observation mode */ -#define rfalST25R3916ObsModeTx() \ - st25r3916WriteTestRegister( \ - 0x01U, \ - (0x40U | \ - gRFAL.conf \ - .obsvModeTx)) /*!< Enable Tx Observation mode */ -#define rfalST25R3916ObsModeRx() \ - st25r3916WriteTestRegister( \ - 0x01U, \ - (0x40U | \ - gRFAL.conf \ - .obsvModeRx)) /*!< Enable Rx Observation mode */ - -#define rfalCheckDisableObsMode() \ - if(gRFAL.conf.obsvModeRx != 0U) { \ - rfalST25R3916ObsModeDisable(); \ - } /*!< Checks if the observation mode is enabled, and applies on ST25R3916 */ -#define rfalCheckEnableObsModeTx() \ - if(gRFAL.conf.obsvModeTx != 0U) { \ - rfalST25R3916ObsModeTx(); \ - } /*!< Checks if the observation mode is enabled, and applies on ST25R3916 */ -#define rfalCheckEnableObsModeRx() \ - if(gRFAL.conf.obsvModeRx != 0U) { \ - rfalST25R3916ObsModeRx(); \ - } /*!< Checks if the observation mode is enabled, and applies on ST25R3916 */ - -#define rfalGetIncmplBits(FIFOStatus2) \ - (((FIFOStatus2) >> 1) & \ - 0x07U) /*!< Returns the number of bits from fifo status */ -#define rfalIsIncompleteByteError(error) \ - (((error) >= ERR_INCOMPLETE_BYTE) && \ - ((error) <= \ - ERR_INCOMPLETE_BYTE_07)) /*!< Checks if given error is a Incomplete error */ - -#define rfalAdjACBR(b) \ - (((uint16_t)(b) >= (uint16_t)RFAL_BR_52p97) ? \ - (uint16_t)(b) : \ - ((uint16_t)(b) + \ - 1U)) /*!< Adjusts ST25R391x Bit rate to Analog Configuration */ -#define rfalConvBR2ACBR(b) \ - (((rfalAdjACBR((b))) << RFAL_ANALOG_CONFIG_BITRATE_SHIFT) & \ - RFAL_ANALOG_CONFIG_BITRATE_MASK) /*!< Converts ST25R391x Bit rate to Analog Configuration bit rate id */ - -#define rfalConvTDFormat(v) \ - ((uint16_t)(v) << 8U) /*!< Converts a uint8_t to the format used in SW Tag Detection */ - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -static rfal gRFAL; /*!< RFAL module instance */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -static void rfalTransceiveTx(void); -static void rfalTransceiveRx(void); -static ReturnCode rfalTransceiveRunBlockingTx(void); -static void rfalPrepareTransceive(void); -static void rfalCleanupTransceive(void); -static void rfalErrorHandling(void); - -static ReturnCode rfalRunTransceiveWorker(void); -#if RFAL_FEATURE_LISTEN_MODE -static ReturnCode rfalRunListenModeWorker(void); -#endif /* RFAL_FEATURE_LISTEN_MODE */ -#if RFAL_FEATURE_WAKEUP_MODE -static void rfalRunWakeUpModeWorker(void); -static uint16_t rfalWakeUpModeFilter(uint16_t curRef, uint16_t curVal, uint8_t weight); -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -static void rfalFIFOStatusUpdate(void); -static void rfalFIFOStatusClear(void); -static bool rfalFIFOStatusIsMissingPar(void); -static bool rfalFIFOStatusIsIncompleteByte(void); -static uint16_t rfalFIFOStatusGetNumBytes(void); -static uint8_t rfalFIFOGetNumIncompleteBits(void); - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalInitialize(void) { - ReturnCode err; - - EXIT_ON_ERR(err, st25r3916Initialize()); - - st25r3916ClearInterrupts(); - - /* Disable any previous observation mode */ - rfalST25R3916ObsModeDisable(); - - /*******************************************************************************/ - /* Apply RF Chip generic initialization */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_INIT)); - - // TODO: - // I don't want to mess with config table ("Default Analog Configuration for Chip-Specific Reset", rfal_analogConfigTbl.h) - // so with every rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_CHIP_INIT)) currently we need to clear pulldown bits - // luckily for us this is done only here - - // disable pulldowns - st25r3916ClrRegisterBits( - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2)); - - /*******************************************************************************/ - /* Enable External Field Detector as: Automatics */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en_fd_mask, - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - - /* Clear FIFO status local copy */ - rfalFIFOStatusClear(); - - /*******************************************************************************/ - gRFAL.state = RFAL_STATE_INIT; - gRFAL.mode = RFAL_MODE_NONE; - gRFAL.field = false; - - /* Set RFAL default configs */ - gRFAL.conf.obsvModeRx = RFAL_OBSMODE_DISABLE; - gRFAL.conf.obsvModeTx = RFAL_OBSMODE_DISABLE; - gRFAL.conf.eHandling = RFAL_ERRORHANDLING_NONE; - - /* Transceive set to IDLE */ - gRFAL.TxRx.lastState = RFAL_TXRX_STATE_IDLE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - - /* Disable all timings */ - gRFAL.timings.FDTListen = RFAL_TIMING_NONE; - gRFAL.timings.FDTPoll = RFAL_TIMING_NONE; - gRFAL.timings.GT = RFAL_TIMING_NONE; - gRFAL.timings.nTRFW = 0U; - - /* Destroy any previous pending timers */ - rfalTimerDestroy(gRFAL.tmr.GT); - rfalTimerDestroy(gRFAL.tmr.txRx); - rfalTimerDestroy(gRFAL.tmr.RXE); - gRFAL.tmr.GT = RFAL_TIMING_NONE; - gRFAL.tmr.txRx = RFAL_TIMING_NONE; - gRFAL.tmr.RXE = RFAL_TIMING_NONE; - - gRFAL.callbacks.preTxRx = NULL; - gRFAL.callbacks.postTxRx = NULL; - gRFAL.callbacks.state_changed_cb = NULL; - gRFAL.callbacks.ctx = NULL; - -#if RFAL_FEATURE_NFCV - /* Initialize NFC-V Data */ - gRFAL.nfcvData.ignoreBits = 0; -#endif /* RFAL_FEATURE_NFCV */ - -#if RFAL_FEATURE_LISTEN_MODE - /* Initialize Listen Mode */ - gRFAL.Lm.state = RFAL_LM_STATE_NOT_INIT; - gRFAL.Lm.brDetected = RFAL_BR_KEEP; - gRFAL.Lm.iniFlag = false; -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#if RFAL_FEATURE_WAKEUP_MODE - /* Initialize Wake-Up Mode */ - gRFAL.wum.state = RFAL_WUM_STATE_NOT_INIT; -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#if RFAL_FEATURE_LOWPOWER_MODE - /* Initialize Low Power Mode */ - gRFAL.lpm.isRunning = false; -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - - /*******************************************************************************/ - /* Perform Automatic Calibration (if configured to do so). * - * Registers set by rfalSetAnalogConfig will tell rfalCalibrate what to perform*/ - rfalCalibrate(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalCalibrate(void) { - uint16_t resValue; - - /* Check if RFAL is not initialized */ - if(gRFAL.state == RFAL_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Perform ST25R3916 regulators and antenna calibration */ - /*******************************************************************************/ - - /* Automatic regulator adjustment only performed if not set manually on Analog Configs */ - if(st25r3916CheckReg( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s, 0x00)) { - /* Adjust the regulators so that Antenna Calibrate has better Regulator values */ - st25r3916AdjustRegulators(&resValue); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalAdjustRegulators(uint16_t* result) { - return st25r3916AdjustRegulators(result); -} - -/*******************************************************************************/ -void rfalSetUpperLayerCallback(rfalUpperLayerCallback pFunc) { - st25r3916IRQCallbackSet(pFunc); -} - -/*******************************************************************************/ -void rfalSetPreTxRxCallback(rfalPreTxRxCallback pFunc) { - gRFAL.callbacks.preTxRx = pFunc; -} - -/*******************************************************************************/ -void rfalSetPostTxRxCallback(rfalPostTxRxCallback pFunc) { - gRFAL.callbacks.postTxRx = pFunc; -} - -void rfal_set_state_changed_callback(RfalStateChangedCallback callback) { - gRFAL.callbacks.state_changed_cb = callback; -} - -void rfal_set_callback_context(void* context) { - gRFAL.callbacks.ctx = context; -} - -/*******************************************************************************/ -ReturnCode rfalDeinitialize(void) { - /* Deinitialize chip */ - st25r3916Deinitialize(); - - /* Set Analog configurations for deinitialization */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_DEINIT)); - - gRFAL.state = RFAL_STATE_IDLE; - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalSetObsvMode(uint8_t txMode, uint8_t rxMode) { - gRFAL.conf.obsvModeTx = txMode; - gRFAL.conf.obsvModeRx = rxMode; -} - -/*******************************************************************************/ -void rfalGetObsvMode(uint8_t* txMode, uint8_t* rxMode) { - if(txMode != NULL) { - *txMode = gRFAL.conf.obsvModeTx; - } - - if(rxMode != NULL) { - *rxMode = gRFAL.conf.obsvModeRx; - } -} - -/*******************************************************************************/ -void rfalDisableObsvMode(void) { - gRFAL.conf.obsvModeTx = RFAL_OBSMODE_DISABLE; - gRFAL.conf.obsvModeRx = RFAL_OBSMODE_DISABLE; -} - -/*******************************************************************************/ -ReturnCode rfalSetMode(rfalMode mode, rfalBitRate txBR, rfalBitRate rxBR) { - /* Check if RFAL is not initialized */ - if(gRFAL.state == RFAL_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Check allowed bit rate value */ - if((txBR == RFAL_BR_KEEP) || (rxBR == RFAL_BR_KEEP)) { - return ERR_PARAM; - } - - switch(mode) { - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCA: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443A mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443a); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCA_T1T: - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable Topaz mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_topaz); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCB: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443B mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443b); - - /* Set the EGT, SOF, EOF and EOF */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_1, - (ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | - ST25R3916_REG_ISO14443B_1_eof), - ((0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | - ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu)); - - /* Set the minimum TR1, SOF, EOF and EOF12 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_2, - (ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof), - (ST25R3916_REG_ISO14443B_2_tr1_80fs80fs)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_B_PRIME: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443B mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443b); - - /* Set the EGT, SOF, EOF and EOF */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_1, - (ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | - ST25R3916_REG_ISO14443B_1_eof), - ((0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | - ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu)); - - /* Set the minimum TR1, EOF and EOF12 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_2, - (ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof), - (ST25R3916_REG_ISO14443B_2_tr1_80fs80fs | ST25R3916_REG_ISO14443B_2_no_sof)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_B_CTS: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443B mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443b); - - /* Set the EGT, SOF, EOF and EOF */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_1, - (ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | - ST25R3916_REG_ISO14443B_1_eof), - ((0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | - ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu)); - - /* Set the minimum TR1, clear SOF, EOF and EOF12 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_2, - (ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof), - (ST25R3916_REG_ISO14443B_2_tr1_80fs80fs | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCF: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable FeliCa mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_felica); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCV: - case RFAL_MODE_POLL_PICOPASS: - -#if !RFAL_FEATURE_NFCV - return ERR_DISABLED; -#else - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - case RFAL_MODE_POLL_ACTIVE_P2P: - - /* Set NFCIP1 active communication Initiator mode and Automatic Response RF Collision Avoidance to always after EOF */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ_init | ST25R3916_REG_MODE_om_nfc | - ST25R3916_REG_MODE_nfc_ar_eof)); - - /* External Field Detector enabled as Automatics on rfalInitialize() */ - - /* Set NRT to start at end of TX (own) field */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc_off); - - /* Set GPT to start after end of TX, as GPT is used in active communication mode to timeout the field switching off */ - /* The field is turned off 37.76us after the end of the transmission Trfw */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc(RFAL_AP2P_FIELDOFF_TRFW), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_etx_nfc); - - /* Set PPon2 timer with the max time between our field Off and other peer field On : Tadt + (n x Trfw) */ - st25r3916WriteRegister( - ST25R3916_REG_PPON2, (uint8_t)rfalConv1fcTo64fc(RFAL_AP2P_FIELDON_TADTTRFW)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_ACTIVE_P2P: - - /* Set NFCIP1 active communication Target mode and Automatic Response RF Collision Avoidance to always after EOF */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om_targ_nfcip | - ST25R3916_REG_MODE_nfc_ar_eof)); - - /* Set TARFG: 0 (75us+0ms=75us), as Target no Guard time needed */ - st25r3916WriteRegister(ST25R3916_REG_FIELD_ON_GT, 0U); - - /* External Field Detector enabled as Automatics on rfalInitialize() */ - - /* Set NRT to start at end of TX (own) field */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc_off); - - /* Set GPT to start after end of TX, as GPT is used in active communication mode to timeout the field switching off */ - /* The field is turned off 37.76us after the end of the transmission Trfw */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc(RFAL_AP2P_FIELDOFF_TRFW), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_etx_nfc); - - /* Set PPon2 timer with the max time between our field Off and other peer field On : Tadt + (n x Trfw) */ - st25r3916WriteRegister( - ST25R3916_REG_PPON2, (uint8_t)rfalConv1fcTo64fc(RFAL_AP2P_FIELDON_TADTTRFW)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCA: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable Passive Target NFC-A mode, disable any Collision Avoidance */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_targ_nfca | - ST25R3916_REG_MODE_nfc_ar_off)); - - /* Set Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCF: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable Passive Target NFC-F mode, disable any Collision Avoidance */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_targ_nfcf | - ST25R3916_REG_MODE_nfc_ar_off)); - - /* Set Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCB: - return ERR_NOTSUPP; - - /*******************************************************************************/ - default: - return ERR_NOT_IMPLEMENTED; - } - - /* Set state as STATE_MODE_SET only if not initialized yet (PSL) */ - gRFAL.state = ((gRFAL.state < RFAL_STATE_MODE_SET) ? RFAL_STATE_MODE_SET : gRFAL.state); - gRFAL.mode = mode; - - /* Apply the given bit rate */ - return rfalSetBitRate(txBR, rxBR); -} - -/*******************************************************************************/ -rfalMode rfalGetMode(void) { - return gRFAL.mode; -} - -/*******************************************************************************/ -ReturnCode rfalSetBitRate(rfalBitRate txBR, rfalBitRate rxBR) { - ReturnCode ret; - - /* Check if RFAL is not initialized */ - if(gRFAL.state == RFAL_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Store the new Bit Rates */ - gRFAL.txBR = ((txBR == RFAL_BR_KEEP) ? gRFAL.txBR : txBR); - gRFAL.rxBR = ((rxBR == RFAL_BR_KEEP) ? gRFAL.rxBR : rxBR); - - /* Update the bitrate reg if not in NFCV mode (streaming) */ - if((RFAL_MODE_POLL_NFCV != gRFAL.mode) && (RFAL_MODE_POLL_PICOPASS != gRFAL.mode)) { - /* Set bit rate register */ - EXIT_ON_ERR(ret, st25r3916SetBitrate((uint8_t)gRFAL.txBR, (uint8_t)gRFAL.rxBR)); - } - - switch(gRFAL.mode) { - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCA: - case RFAL_MODE_POLL_NFCA_T1T: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCB: - case RFAL_MODE_POLL_B_PRIME: - case RFAL_MODE_POLL_B_CTS: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCF: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCV: - case RFAL_MODE_POLL_PICOPASS: - -#if !RFAL_FEATURE_NFCV - return ERR_DISABLED; -#else - - if(((gRFAL.rxBR != RFAL_BR_26p48) && (gRFAL.rxBR != RFAL_BR_52p97)) || - ((gRFAL.txBR != RFAL_BR_1p66) && (gRFAL.txBR != RFAL_BR_26p48))) { - return ERR_PARAM; - } - - { - const struct iso15693StreamConfig* isoStreamConfig; - struct st25r3916StreamConfig streamConf; - iso15693PhyConfig_t config; - - config.coding = - ((gRFAL.txBR == RFAL_BR_1p66) ? ISO15693_VCD_CODING_1_256 : - ISO15693_VCD_CODING_1_4); - switch(gRFAL.rxBR) { - case RFAL_BR_52p97: - config.speedMode = 1; - break; - default: - config.speedMode = 0; - break; - } - - iso15693PhyConfigure(&config, &isoStreamConfig); - - /* MISRA 11.3 - Cannot point directly into different object type, copy to local var */ - streamConf.din = isoStreamConfig->din; - streamConf.dout = isoStreamConfig->dout; - streamConf.report_period_length = isoStreamConfig->report_period_length; - streamConf.useBPSK = isoStreamConfig->useBPSK; - st25r3916StreamConfigure(&streamConf); - } - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - case RFAL_MODE_POLL_ACTIVE_P2P: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_ACTIVE_P2P: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCA: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCF: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCB: - case RFAL_MODE_NONE: - return ERR_WRONG_STATE; - - /*******************************************************************************/ - default: - return ERR_NOT_IMPLEMENTED; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalGetBitRate(rfalBitRate* txBR, rfalBitRate* rxBR) { - if((gRFAL.state == RFAL_STATE_IDLE) || (gRFAL.mode == RFAL_MODE_NONE)) { - return ERR_WRONG_STATE; - } - - if(txBR != NULL) { - *txBR = gRFAL.txBR; - } - - if(rxBR != NULL) { - *rxBR = gRFAL.rxBR; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalSetErrorHandling(rfalEHandling eHandling) { - switch(eHandling) { - case RFAL_ERRORHANDLING_NFC: - case RFAL_ERRORHANDLING_NONE: - st25r3916ClrRegisterBits(ST25R3916_REG_EMD_SUP_CONF, ST25R3916_REG_EMD_SUP_CONF_emd_emv); - break; - - case RFAL_ERRORHANDLING_EMVCO: - /* MISRA 16.4: no empty default statement (in case RFAL_SW_EMD is defined) */ -#ifndef RFAL_SW_EMD - st25r3916ModifyRegister( - ST25R3916_REG_EMD_SUP_CONF, - (ST25R3916_REG_EMD_SUP_CONF_emd_emv | ST25R3916_REG_EMD_SUP_CONF_emd_thld_mask), - (ST25R3916_REG_EMD_SUP_CONF_emd_emv_on | RFAL_EMVCO_RX_MAXLEN)); -#endif /* RFAL_SW_EMD */ - break; - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - gRFAL.conf.eHandling = eHandling; -} - -/*******************************************************************************/ -rfalEHandling rfalGetErrorHandling(void) { - return gRFAL.conf.eHandling; -} - -/*******************************************************************************/ -void rfalSetFDTPoll(uint32_t FDTPoll) { - gRFAL.timings.FDTPoll = MIN(FDTPoll, RFAL_ST25R3916_GPT_MAX_1FC); -} - -/*******************************************************************************/ -uint32_t rfalGetFDTPoll(void) { - return gRFAL.timings.FDTPoll; -} - -/*******************************************************************************/ -void rfalSetFDTListen(uint32_t FDTListen) { - gRFAL.timings.FDTListen = MIN(FDTListen, RFAL_ST25R3916_MRT_MAX_1FC); -} - -/*******************************************************************************/ -uint32_t rfalGetFDTListen(void) { - return gRFAL.timings.FDTListen; -} - -/*******************************************************************************/ -void rfalSetGT(uint32_t GT) { - gRFAL.timings.GT = MIN(GT, RFAL_ST25R3916_GT_MAX_1FC); -} - -/*******************************************************************************/ -uint32_t rfalGetGT(void) { - return gRFAL.timings.GT; -} - -/*******************************************************************************/ -bool rfalIsGTExpired(void) { - if(gRFAL.tmr.GT != RFAL_TIMING_NONE) { - if(!rfalTimerisExpired(gRFAL.tmr.GT)) { - return false; - } - } - return true; -} - -/*******************************************************************************/ -ReturnCode rfalFieldOnAndStartGT(void) { - ReturnCode ret; - - /* Check if RFAL has been initialized (Oscillator should be running) and also - * if a direct register access has been performed and left the Oscillator Off */ - if(!st25r3916IsOscOn() || (gRFAL.state < RFAL_STATE_INIT)) { - return ERR_WRONG_STATE; - } - - ret = ERR_NONE; - - /* Set Analog configurations for Field On event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_FIELD_ON)); - - /*******************************************************************************/ - /* Perform collision avoidance and turn field On if not already On */ - if(!st25r3916IsTxEnabled() || !gRFAL.field) { - /* Set TARFG: 0 (75us+0ms=75us), GT is fulfilled using a SW timer */ - st25r3916WriteRegister(ST25R3916_REG_FIELD_ON_GT, 0U); - - /* Use Thresholds set by AnalogConfig */ - ret = st25r3916PerformCollisionAvoidance( - ST25R3916_CMD_INITIAL_RF_COLLISION, - ST25R3916_THRESHOLD_DO_NOT_SET, - ST25R3916_THRESHOLD_DO_NOT_SET, - gRFAL.timings.nTRFW); - - /* n * TRFW timing shall vary Activity 2.1 3.3.1.1 */ - gRFAL.timings.nTRFW = rfalGennTRFW(gRFAL.timings.nTRFW); - - gRFAL.field = st25r3916IsTxEnabled(); //(ret == ERR_NONE); - - /* Only turn on Receiver and Transmitter if field was successfully turned On */ - if(gRFAL.field) { - st25r3916TxRxOn(); /* Enable Tx and Rx (Tx is already On)*/ - } - } - - /*******************************************************************************/ - /* Start GT timer in case the GT value is set */ - if((gRFAL.timings.GT != RFAL_TIMING_NONE)) { - /* Ensure that a SW timer doesn't have a lower value then the minimum */ - rfalTimerStart( - gRFAL.tmr.GT, rfalConv1fcToMs(MAX((gRFAL.timings.GT), RFAL_ST25R3916_GT_MIN_1FC))); - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalFieldOff(void) { - /* Check whether a TxRx is not yet finished */ - if(gRFAL.TxRx.state != RFAL_TXRX_STATE_IDLE) { - rfalCleanupTransceive(); - } - - /* Disable Tx and Rx */ - st25r3916TxRxOff(); - - /* Set Analog configurations for Field Off event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_FIELD_OFF)); - gRFAL.field = false; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalStartTransceive(const rfalTransceiveContext* ctx) { - uint32_t FxTAdj; /* FWT or FDT adjustment calculation */ - - /* Check for valid parameters */ - if(ctx == NULL) { - return ERR_PARAM; - } - - /* Ensure that RFAL is already Initialized and the mode has been set */ - if((gRFAL.state >= RFAL_STATE_MODE_SET) /*&& (gRFAL.TxRx.state == RFAL_TXRX_STATE_INIT )*/) { - /*******************************************************************************/ - /* Check whether the field is already On, otherwise no TXE will be received */ - if(!st25r3916IsTxEnabled() && - (!rfalIsModePassiveListen(gRFAL.mode) && (ctx->txBuf != NULL))) { - return ERR_WRONG_STATE; - } - - gRFAL.TxRx.ctx = *ctx; - - /*******************************************************************************/ - if(gRFAL.timings.FDTListen != RFAL_TIMING_NONE) { - /* Calculate MRT adjustment accordingly to the current mode */ - FxTAdj = RFAL_FDT_LISTEN_MRT_ADJUSTMENT; - if(gRFAL.mode == RFAL_MODE_POLL_NFCA) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCA_T1T) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCB) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_B_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCV) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_V_ADJUSTMENT; - } - - /* Ensure that MRT is using 64/fc steps */ - st25r3916ClrRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_mrt_step); - - /* If Correlator is being used further adjustment is required for NFCB */ - if((st25r3916CheckReg(ST25R3916_REG_AUX, ST25R3916_REG_AUX_dis_corr, 0x00U)) && - (gRFAL.mode == RFAL_MODE_POLL_NFCB)) { - FxTAdj += (uint32_t) - RFAL_FDT_LISTEN_B_ADJT_CORR; /* Reduce FDT(Listen) */ - st25r3916SetRegisterBits( - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s3); /* Ensure BPSK start to 33 pilot pulses */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_SUBC_START_TIME, - ST25R3916_REG_SUBC_START_TIME_sst_mask, - RFAL_FDT_LISTEN_B_ADJT_CORR_SST); /* Set sst */ - } - - /* Set Minimum FDT(Listen) in which PICC is not allowed to send a response */ - st25r3916WriteRegister( - ST25R3916_REG_MASK_RX_TIMER, - (uint8_t)rfalConv1fcTo64fc( - (FxTAdj > gRFAL.timings.FDTListen) ? RFAL_ST25R3916_MRT_MIN_1FC : - (gRFAL.timings.FDTListen - FxTAdj))); - } - - /*******************************************************************************/ - /* FDT Poll will be loaded in rfalPrepareTransceive() once the previous was expired */ - - /*******************************************************************************/ - if((gRFAL.TxRx.ctx.fwt != RFAL_FWT_NONE) && (gRFAL.TxRx.ctx.fwt != 0U)) { - /* Ensure proper timing configuration */ - if(gRFAL.timings.FDTListen >= gRFAL.TxRx.ctx.fwt) { - return ERR_PARAM; - } - - FxTAdj = RFAL_FWT_ADJUSTMENT; - if(gRFAL.mode == RFAL_MODE_POLL_NFCA) { - FxTAdj += (uint32_t)RFAL_FWT_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCA_T1T) { - FxTAdj += (uint32_t)RFAL_FWT_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCB) { - FxTAdj += (uint32_t)RFAL_FWT_B_ADJUSTMENT; - } - if((gRFAL.mode == RFAL_MODE_POLL_NFCF) || (gRFAL.mode == RFAL_MODE_POLL_ACTIVE_P2P)) { - FxTAdj += - (uint32_t)((gRFAL.txBR == RFAL_BR_212) ? RFAL_FWT_F_212_ADJUSTMENT : RFAL_FWT_F_424_ADJUSTMENT); - } - - /* Ensure that the given FWT doesn't exceed NRT maximum */ - gRFAL.TxRx.ctx.fwt = MIN((gRFAL.TxRx.ctx.fwt + FxTAdj), RFAL_ST25R3916_NRT_MAX_1FC); - - /* Set FWT in the NRT */ - st25r3916SetNoResponseTime(rfalConv1fcTo64fc(gRFAL.TxRx.ctx.fwt)); - } else { - /* Disable NRT, no NRE will be triggered, therefore wait endlessly for Rx */ - st25r3916SetNoResponseTime(RFAL_ST25R3916_NRT_DISABLED); - } - - gRFAL.state = RFAL_STATE_TXRX; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_IDLE; - gRFAL.TxRx.status = ERR_BUSY; - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - if((RFAL_MODE_POLL_NFCV == gRFAL.mode) || - (RFAL_MODE_POLL_PICOPASS == - gRFAL.mode)) { /* Exchange receive buffer with internal buffer */ - gRFAL.nfcvData.origCtx = gRFAL.TxRx.ctx; - - gRFAL.TxRx.ctx.rxBuf = - ((gRFAL.nfcvData.origCtx.rxBuf != NULL) ? gRFAL.nfcvData.codingBuffer : NULL); - gRFAL.TxRx.ctx.rxBufLen = - (uint16_t)rfalConvBytesToBits(sizeof(gRFAL.nfcvData.codingBuffer)); - gRFAL.TxRx.ctx.flags = - (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | - (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | - (uint32_t)(gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_AGC_OFF) | - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE; - - /* In NFCV a TxRx with a valid txBuf and txBufSize==0 indicates to send an EOF */ - /* Skip logic below that would go directly into receive */ - if(gRFAL.TxRx.ctx.txBuf != NULL) { - return ERR_NONE; - } - } -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* Check if the Transceive start performing Tx or goes directly to Rx */ - if((gRFAL.TxRx.ctx.txBuf == NULL) || (gRFAL.TxRx.ctx.txBufLen == 0U)) { - /* Clear FIFO, Clear and Enable the Interrupts */ - rfalPrepareTransceive(); - - /* In AP2P check the field status */ - if(rfalIsModeActiveComm(gRFAL.mode)) { - /* Disable our field upon a Rx reEnable, and start PPON2 manually */ - st25r3916TxOff(); - st25r3916ExecuteCommand(ST25R3916_CMD_START_PPON2_TIMER); - } - - /* No Tx done, enable the Receiver */ - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Start NRT manually, if FWT = 0 (wait endlessly for Rx) chip will ignore anyhow */ - st25r3916ExecuteCommand(ST25R3916_CMD_START_NO_RESPONSE_TIMER); - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - } - - return ERR_NONE; - } - - return ERR_WRONG_STATE; -} - -/*******************************************************************************/ -bool rfalIsTransceiveInTx(void) { - return ( - (gRFAL.TxRx.state >= RFAL_TXRX_STATE_TX_IDLE) && - (gRFAL.TxRx.state < RFAL_TXRX_STATE_RX_IDLE)); -} - -/*******************************************************************************/ -bool rfalIsTransceiveInRx(void) { - return (gRFAL.TxRx.state >= RFAL_TXRX_STATE_RX_IDLE); -} - -/*******************************************************************************/ -ReturnCode rfalTransceiveBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - rfalTransceiveContext ctx; - - rfalCreateByteFlagsTxRxContext(ctx, txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); - EXIT_ON_ERR(ret, rfalStartTransceive(&ctx)); - - return rfalTransceiveRunBlockingTx(); -} - -ReturnCode rfalTransceiveBitsBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - rfalTransceiveContext ctx = { - .rxBuf = rxBuf, - .rxBufLen = rxBufLen, - .rxRcvdLen = actLen, - .txBuf = txBuf, - .txBufLen = txBufLen, - .flags = flags, - .fwt = fwt, - }; - - EXIT_ON_ERR(ret, rfalStartTransceive(&ctx)); - - return rfalTransceiveRunBlockingTx(); -} - -/*******************************************************************************/ -static ReturnCode rfalTransceiveRunBlockingTx(void) { - ReturnCode ret; - - do { - rfalWorker(); - ret = rfalGetTransceiveStatus(); - } while(rfalIsTransceiveInTx() && (ret == ERR_BUSY)); - - if(rfalIsTransceiveInRx()) { - return ERR_NONE; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalTransceiveBlockingRx(void) { - ReturnCode ret; - - do { - rfalWorker(); - ret = rfalGetTransceiveStatus(); - } while(rfalIsTransceiveInRx() && (ret == ERR_BUSY)); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalTransceiveBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, rfalTransceiveBlockingTx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt)); - ret = rfalTransceiveBlockingRx(); - - /* Convert received bits to bytes */ - if(actLen != NULL) { - *actLen = rfalConvBitsToBytes(*actLen); - } - - return ret; -} - -ReturnCode rfalTransceiveBitsBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, rfalTransceiveBitsBlockingTx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt)); - ret = rfalTransceiveBlockingRx(); - - return ret; -} - -/*******************************************************************************/ -static ReturnCode rfalRunTransceiveWorker(void) { - if(gRFAL.state == RFAL_STATE_TXRX) { - /*******************************************************************************/ - /* Check Transceive Sanity Timer has expired */ - if(gRFAL.tmr.txRx != RFAL_TIMING_NONE) { - if(rfalTimerisExpired(gRFAL.tmr.txRx)) { - /* If sanity timer has expired abort ongoing transceive and signal error */ - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - } - - /*******************************************************************************/ - /* Run Tx or Rx state machines */ - if(rfalIsTransceiveInTx()) { - rfalTransceiveTx(); - return rfalGetTransceiveStatus(); - } - if(rfalIsTransceiveInRx()) { - rfalTransceiveRx(); - return rfalGetTransceiveStatus(); - } - } - return ERR_WRONG_STATE; -} - -/*******************************************************************************/ -rfalTransceiveState rfalGetTransceiveState(void) { - return gRFAL.TxRx.state; -} - -/*******************************************************************************/ -ReturnCode rfalGetTransceiveStatus(void) { - return ((gRFAL.TxRx.state == RFAL_TXRX_STATE_IDLE) ? gRFAL.TxRx.status : ERR_BUSY); -} - -/*******************************************************************************/ -ReturnCode rfalGetTransceiveRSSI(uint16_t* rssi) { - uint16_t amRSSI; - uint16_t pmRSSI; - bool isSumMode; - - if(rssi == NULL) { - return ERR_PARAM; - } - - st25r3916GetRSSI(&amRSSI, &pmRSSI); - - /* Check if Correlator Summation mode is being used */ - isSumMode = - (st25r3916CheckReg( - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s4, - ST25R3916_REG_CORR_CONF1_corr_s4) ? - st25r3916CheckReg(ST25R3916_REG_AUX, ST25R3916_REG_AUX_dis_corr, 0x00) : - false); - if(isSumMode) { - /*******************************************************************************/ - /* Using SQRT from math.h and float. If due to compiler, resources or performance - * issue this cannot be used, other approaches can be foreseen with less accuracy: - * Use a simpler sqrt algorithm - * *rssi = MAX( amRSSI, pmRSSI ); - * *rssi = ( (amRSSI + pmRSSI) / 2); - */ - *rssi = (uint16_t)sqrt( - ((double)amRSSI * (double)amRSSI) + - ((double)pmRSSI * - (double) - pmRSSI)); /* PRQA S 5209 # MISRA 4.9 - External function (sqrt()) requires double */ - } else { - /* Check which channel was used */ - *rssi = - (st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_a_cha, - ST25R3916_REG_AUX_DISPLAY_a_cha) ? - pmRSSI : - amRSSI); - } - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalWorker(void) { - platformProtectWorker(); /* Protect RFAL Worker/Task/Process */ - - switch(gRFAL.state) { - case RFAL_STATE_TXRX: - rfalRunTransceiveWorker(); - break; - -#if RFAL_FEATURE_LISTEN_MODE - case RFAL_STATE_LM: - rfalRunListenModeWorker(); - break; -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#if RFAL_FEATURE_WAKEUP_MODE - case RFAL_STATE_WUM: - rfalRunWakeUpModeWorker(); - break; -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - - /* Nothing to be done */ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - platformUnprotectWorker(); /* Unprotect RFAL Worker/Task/Process */ -} - -/*******************************************************************************/ -static void rfalErrorHandling(void) { - uint16_t fifoBytesToRead; - - fifoBytesToRead = rfalFIFOStatusGetNumBytes(); - -#ifdef RFAL_SW_EMD - /*******************************************************************************/ - /* EMVCo */ - /*******************************************************************************/ - if(gRFAL.conf.eHandling == RFAL_ERRORHANDLING_EMVCO) { - bool rxHasIncParError; - - /*******************************************************************************/ - /* EMD Handling - NFC Forum Digital 1.1 4.1.1.1 ; EMVCo v2.5 4.9.2 */ - /* ReEnable the receiver on frames with a length < 4 bytes, upon: */ - /* - Collision or Framing error detected */ - /* - Residual bits are detected (hard framing error) */ - /* - Parity error */ - /* - CRC error */ - /*******************************************************************************/ - - /* Check if reception has incomplete bytes or parity error */ - rxHasIncParError = - (rfalFIFOStatusIsIncompleteByte() ? true : - rfalFIFOStatusIsMissingPar()); /* MISRA 13.5 */ - - /* In case there are residual bits decrement FIFO bytes */ - /* Ensure FIFO contains some byte as the FIFO might be empty upon Framing errors */ - if((fifoBytesToRead > 0U) && rxHasIncParError) { - fifoBytesToRead--; - } - - if(((gRFAL.fifo.bytesTotal + fifoBytesToRead) < RFAL_EMVCO_RX_MAXLEN) && - ((gRFAL.TxRx.status == ERR_RF_COLLISION) || (gRFAL.TxRx.status == ERR_FRAMING) || - (gRFAL.TxRx.status == ERR_PAR) || (gRFAL.TxRx.status == ERR_CRC) || - rxHasIncParError)) { - /* Ignore this reception, ReEnable receiver which also clears the FIFO */ - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Ensure that the NRT has not expired meanwhile */ - if(st25r3916CheckReg( - ST25R3916_REG_NFCIP1_BIT_RATE, ST25R3916_REG_NFCIP1_BIT_RATE_nrt_on, 0x00)) { - if(st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, ST25R3916_REG_AUX_DISPLAY_rx_act, 0x00)) { - /* Abort reception */ - st25r3916ExecuteCommand(ST25R3916_CMD_MASK_RECEIVE_DATA); - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - return; - } - } - - rfalFIFOStatusClear(); - gRFAL.fifo.bytesTotal = 0; - gRFAL.TxRx.status = ERR_BUSY; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - } - return; - } -#endif - - /*******************************************************************************/ - /* ISO14443A Mode */ - /*******************************************************************************/ - if(gRFAL.mode == RFAL_MODE_POLL_NFCA) { - /*******************************************************************************/ - /* If we received a frame with a incomplete byte we`ll raise a specific error * - * ( support for T2T 4 bit ACK / NAK, MIFARE and Kovio ) */ - /*******************************************************************************/ - if((gRFAL.TxRx.status == ERR_PAR) || (gRFAL.TxRx.status == ERR_CRC)) { - if(rfalFIFOStatusIsIncompleteByte()) { - st25r3916ReadFifo((uint8_t*)(gRFAL.TxRx.ctx.rxBuf), fifoBytesToRead); - if((gRFAL.TxRx.ctx.rxRcvdLen) != NULL) { - *gRFAL.TxRx.ctx.rxRcvdLen = rfalFIFOGetNumIncompleteBits(); - } - - gRFAL.TxRx.status = ERR_INCOMPLETE_BYTE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - } - } -} - -/*******************************************************************************/ -static void rfalCleanupTransceive(void) { - /*******************************************************************************/ - /* Transceive flags */ - /*******************************************************************************/ - - /* Restore default settings on NFCIP1 mode, Receiving parity + CRC bits and manual Tx Parity*/ - st25r3916ClrRegisterBits( - ST25R3916_REG_ISO14443A_NFC, - (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par | - ST25R3916_REG_ISO14443A_NFC_nfc_f0)); - - /* Restore AGC enabled */ - st25r3916SetRegisterBits(ST25R3916_REG_RX_CONF2, ST25R3916_REG_RX_CONF2_agc_en); - - /*******************************************************************************/ - - /*******************************************************************************/ - /* Transceive timers */ - /*******************************************************************************/ - rfalTimerDestroy(gRFAL.tmr.txRx); - rfalTimerDestroy(gRFAL.tmr.RXE); - gRFAL.tmr.txRx = RFAL_TIMING_NONE; - gRFAL.tmr.RXE = RFAL_TIMING_NONE; - /*******************************************************************************/ - - /*******************************************************************************/ - /* Execute Post Transceive Callback */ - /*******************************************************************************/ - if(gRFAL.callbacks.postTxRx != NULL) { - gRFAL.callbacks.postTxRx(gRFAL.callbacks.ctx); - } - /*******************************************************************************/ -} - -/*******************************************************************************/ -static void rfalPrepareTransceive(void) { - uint32_t maskInterrupts; - uint8_t reg; - - /* If we are in RW or AP2P mode */ - if(!rfalIsModePassiveListen(gRFAL.mode)) { - /* Reset receive logic with STOP command */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - - /* Reset Rx Gain */ - st25r3916ExecuteCommand(ST25R3916_CMD_RESET_RXGAIN); - } else { - /* In Passive Listen Mode do not use STOP as it stops FDT timer */ - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - } - - /*******************************************************************************/ - /* FDT Poll */ - /*******************************************************************************/ - if(rfalIsModePassiveComm(gRFAL.mode)) /* Passive Comms */ - { - /* In Passive communications General Purpose Timer is used to measure FDT Poll */ - if(gRFAL.timings.FDTPoll != RFAL_TIMING_NONE) { - /* Configure GPT to start at RX end */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc(MIN( - gRFAL.timings.FDTPoll, (gRFAL.timings.FDTPoll - RFAL_FDT_POLL_ADJUSTMENT))), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_erx); - } - } - - /*******************************************************************************/ - /* Execute Pre Transceive Callback */ - /*******************************************************************************/ - if(gRFAL.callbacks.preTxRx != NULL) { - gRFAL.callbacks.preTxRx(gRFAL.callbacks.ctx); - } - /*******************************************************************************/ - - maskInterrupts = - (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | - ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | - ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); - - /*******************************************************************************/ - /* Transceive flags */ - /*******************************************************************************/ - - reg = - (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off | - ST25R3916_REG_ISO14443A_NFC_nfc_f0_off); - - /* Check if NFCIP1 mode is to be enabled */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_NFCIP1_ON) != 0U) { - reg |= ST25R3916_REG_ISO14443A_NFC_nfc_f0; - } - - /* Check if Parity check is to be skipped and to keep the parity + CRC bits in FIFO */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP) != 0U) { - reg |= ST25R3916_REG_ISO14443A_NFC_no_rx_par; - } - - /* Check if automatic Parity bits is to be disabled */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) != 0U) { - reg |= ST25R3916_REG_ISO14443A_NFC_no_tx_par; - } - - /* Apply current TxRx flags on ISO14443A and NFC 106kb/s Settings Register */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443A_NFC, - (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par | - ST25R3916_REG_ISO14443A_NFC_nfc_f0), - reg); - - /* Check if AGC is to be disabled */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_AGC_OFF) != 0U) { - st25r3916ClrRegisterBits(ST25R3916_REG_RX_CONF2, ST25R3916_REG_RX_CONF2_agc_en); - } else { - st25r3916SetRegisterBits(ST25R3916_REG_RX_CONF2, ST25R3916_REG_RX_CONF2_agc_en); - } - /*******************************************************************************/ - - /*******************************************************************************/ - /* EMVCo NRT mode */ - /*******************************************************************************/ - if(gRFAL.conf.eHandling == RFAL_ERRORHANDLING_EMVCO) { - st25r3916SetRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); - maskInterrupts |= ST25R3916_IRQ_MASK_RX_REST; - } else { - st25r3916ClrRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); - } - /*******************************************************************************/ - - /* In Passive Listen mode additionally enable External Field interrupts */ - if(rfalIsModePassiveListen(gRFAL.mode)) { - maskInterrupts |= - (ST25R3916_IRQ_MASK_EOF | - ST25R3916_IRQ_MASK_WU_F); /* Enable external Field interrupts to detect Link Loss and SENF_REQ auto responses */ - } - - /* In Active comms enable also External Field interrupts and set RF Collision Avoindance */ - if(rfalIsModeActiveComm(gRFAL.mode)) { - maskInterrupts |= - (ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_PPON2 | - ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_CAC); - - /* Set n=0 for subsequent RF Collision Avoidance */ - st25r3916ChangeRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, 0); - } - - /*******************************************************************************/ - /* Start transceive Sanity Timer if a FWT is used */ - if((gRFAL.TxRx.ctx.fwt != RFAL_FWT_NONE) && (gRFAL.TxRx.ctx.fwt != 0U)) { - rfalTimerStart(gRFAL.tmr.txRx, rfalCalcSanityTmr(gRFAL.TxRx.ctx.fwt)); - } - /*******************************************************************************/ - - /*******************************************************************************/ - /* Clear and enable these interrupts */ - st25r3916GetInterrupt(maskInterrupts); - st25r3916EnableInterrupts(maskInterrupts); - - /* Clear FIFO status local copy */ - rfalFIFOStatusClear(); -} - -/*******************************************************************************/ -static void rfalTransceiveTx(void) { - volatile uint32_t irqs; - uint16_t tmp; - ReturnCode ret; - - /* Suppress warning in case NFC-V feature is disabled */ - ret = ERR_NONE; - NO_WARNING(ret); - - irqs = ST25R3916_IRQ_MASK_NONE; - - if(gRFAL.TxRx.state != gRFAL.TxRx.lastState) { - /* rfalLogD( "RFAL: lastSt: %d curSt: %d \r\n", gRFAL.TxRx.lastState, gRFAL.TxRx.state ); */ - gRFAL.TxRx.lastState = gRFAL.TxRx.state; - } - - switch(gRFAL.TxRx.state) { - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_IDLE: - - /* Nothing to do */ - - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_WAIT_GT; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_GT: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - if(!rfalIsGTExpired()) { - break; - } - - rfalTimerDestroy(gRFAL.tmr.GT); - gRFAL.tmr.GT = RFAL_TIMING_NONE; - - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_WAIT_FDT; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_FDT: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Only in Passive communications GPT is used to measure FDT Poll */ - if(rfalIsModePassiveComm(gRFAL.mode)) { - if(st25r3916IsGPTRunning()) { - break; - } - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_TRANSMIT; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_TRANSMIT: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Clear FIFO, Clear and Enable the Interrupts */ - rfalPrepareTransceive(); - - /* ST25R3916 has a fixed FIFO water level */ - gRFAL.fifo.expWL = RFAL_FIFO_OUT_WL; - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - /* In NFC-V streaming mode, the FIFO needs to be loaded with the coded bits */ - if((RFAL_MODE_POLL_NFCV == gRFAL.mode) || (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)) { -#if 0 - /* Debugging code: output the payload bits by writing into the FIFO and subsequent clearing */ - st25r3916WriteFifo(gRFAL.TxRx.ctx.txBuf, rfalConvBitsToBytes(gRFAL.TxRx.ctx.txBufLen)); - st25r3916ExecuteCommand( ST25R3916_CMD_CLEAR_FIFO ); -#endif - /* Calculate the bytes needed to be Written into FIFO (a incomplete byte will be added as 1byte) */ - gRFAL.nfcvData.nfcvOffset = 0; - ret = iso15693VCDCode( - gRFAL.TxRx.ctx.txBuf, - rfalConvBitsToBytes(gRFAL.TxRx.ctx.txBufLen), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL) != 0U) ? - false : - true), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL) != - 0U) ? - false : - true), - (RFAL_MODE_POLL_PICOPASS == gRFAL.mode), - &gRFAL.fifo.bytesTotal, - &gRFAL.nfcvData.nfcvOffset, - gRFAL.nfcvData.codingBuffer, - MIN((uint16_t)ST25R3916_FIFO_DEPTH, (uint16_t)sizeof(gRFAL.nfcvData.codingBuffer)), - &gRFAL.fifo.bytesWritten); - - if((ret != ERR_NONE) && (ret != ERR_AGAIN)) { - gRFAL.TxRx.status = ret; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - /* Set the number of full bytes and bits to be transmitted */ - st25r3916SetNumTxBits((uint16_t)rfalConvBytesToBits(gRFAL.fifo.bytesTotal)); - - /* Load FIFO with coded bytes */ - st25r3916WriteFifo(gRFAL.nfcvData.codingBuffer, gRFAL.fifo.bytesWritten); - - } - /*******************************************************************************/ - else -#endif /* RFAL_FEATURE_NFCV */ - { - /* Calculate the bytes needed to be Written into FIFO (a incomplete byte will be added as 1byte) */ - gRFAL.fifo.bytesTotal = (uint16_t)rfalCalcNumBytes(gRFAL.TxRx.ctx.txBufLen); - - /* Set the number of full bytes and bits to be transmitted */ - st25r3916SetNumTxBits(gRFAL.TxRx.ctx.txBufLen); - - /* Load FIFO with total length or FIFO's maximum */ - gRFAL.fifo.bytesWritten = MIN(gRFAL.fifo.bytesTotal, ST25R3916_FIFO_DEPTH); - st25r3916WriteFifo(gRFAL.TxRx.ctx.txBuf, gRFAL.fifo.bytesWritten); - } - - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeTx(); - - /*******************************************************************************/ - /* If we're in Passive Listen mode ensure that the external field is still On */ - if(rfalIsModePassiveListen(gRFAL.mode)) { - if(!rfalIsExtFieldOn()) { - gRFAL.TxRx.status = ERR_LINK_LOSS; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - } - - /*******************************************************************************/ - /* Trigger/Start transmission */ - if((gRFAL.TxRx.ctx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL) != 0U) { - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); - } else { - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSMIT_WITH_CRC); - } - - /* Check if a WL level is expected or TXE should come */ - gRFAL.TxRx.state = - ((gRFAL.fifo.bytesWritten < gRFAL.fifo.bytesTotal) ? RFAL_TXRX_STATE_TX_WAIT_WL : - RFAL_TXRX_STATE_TX_WAIT_TXE); - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_WL: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if(((irqs & ST25R3916_IRQ_MASK_FWL) != 0U) && ((irqs & ST25R3916_IRQ_MASK_TXE) == 0U)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_RELOAD_FIFO; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_RELOAD_FIFO: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - /* In NFC-V streaming mode, the FIFO needs to be loaded with the coded bits */ - if((RFAL_MODE_POLL_NFCV == gRFAL.mode) || (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)) { - uint16_t maxLen; - - /* Load FIFO with the remaining length or maximum available (which fit on the coding buffer) */ - maxLen = - (uint16_t)MIN((gRFAL.fifo.bytesTotal - gRFAL.fifo.bytesWritten), gRFAL.fifo.expWL); - maxLen = (uint16_t)MIN(maxLen, sizeof(gRFAL.nfcvData.codingBuffer)); - tmp = 0; - - /* Calculate the bytes needed to be Written into FIFO (a incomplete byte will be added as 1byte) */ - ret = iso15693VCDCode( - gRFAL.TxRx.ctx.txBuf, - rfalConvBitsToBytes(gRFAL.TxRx.ctx.txBufLen), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL) != 0U) ? - false : - true), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL) != - 0U) ? - false : - true), - (RFAL_MODE_POLL_PICOPASS == gRFAL.mode), - &gRFAL.fifo.bytesTotal, - &gRFAL.nfcvData.nfcvOffset, - gRFAL.nfcvData.codingBuffer, - maxLen, - &tmp); - - if((ret != ERR_NONE) && (ret != ERR_AGAIN)) { - gRFAL.TxRx.status = ret; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - - /* Load FIFO with coded bytes */ - st25r3916WriteFifo(gRFAL.nfcvData.codingBuffer, tmp); - } - /*******************************************************************************/ - else -#endif /* RFAL_FEATURE_NFCV */ - { - /* Load FIFO with the remaining length or maximum available */ - tmp = MIN( - (gRFAL.fifo.bytesTotal - gRFAL.fifo.bytesWritten), - gRFAL.fifo.expWL); /* tmp holds the number of bytes written on this iteration */ - st25r3916WriteFifo(&gRFAL.TxRx.ctx.txBuf[gRFAL.fifo.bytesWritten], tmp); - } - - /* Update total written bytes to FIFO */ - gRFAL.fifo.bytesWritten += tmp; - - /* Check if a WL level is expected or TXE should come */ - gRFAL.TxRx.state = - ((gRFAL.fifo.bytesWritten < gRFAL.fifo.bytesTotal) ? RFAL_TXRX_STATE_TX_WAIT_WL : - RFAL_TXRX_STATE_TX_WAIT_TXE); - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_TXE: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_TXE) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_DONE; - } else if((irqs & ST25R3916_IRQ_MASK_FWL) != 0U) { - break; /* Ignore ST25R3916 FIFO WL if total TxLen is already on the FIFO */ - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_DONE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* If no rxBuf is provided do not wait/expect Rx */ - if(gRFAL.TxRx.ctx.rxBuf == NULL) { - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - gRFAL.TxRx.status = ERR_NONE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - } - - rfalCheckEnableObsModeRx(); - - /* Goto Rx */ - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_FAIL: - - /* Error should be assigned by previous state */ - if(gRFAL.TxRx.status == ERR_BUSY) { - gRFAL.TxRx.status = ERR_SYSTEM; - } - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - - /*******************************************************************************/ - default: - gRFAL.TxRx.status = ERR_SYSTEM; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } -} - -/*******************************************************************************/ -static void rfalTransceiveRx(void) { - volatile uint32_t irqs; - uint16_t tmp; - uint16_t aux; - - irqs = ST25R3916_IRQ_MASK_NONE; - - if(gRFAL.TxRx.state != gRFAL.TxRx.lastState) { - /* rfalLogD( "RFAL: lastSt: %d curSt: %d \r\n", gRFAL.TxRx.lastState, gRFAL.TxRx.state ); */ - gRFAL.TxRx.lastState = gRFAL.TxRx.state; - } - - switch(gRFAL.TxRx.state) { - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_IDLE: - - /* Clear rx counters */ - gRFAL.fifo.bytesWritten = 0; /* Total bytes written on RxBuffer */ - gRFAL.fifo.bytesTotal = 0; /* Total bytes in FIFO will now be from Rx */ - if(gRFAL.TxRx.ctx.rxRcvdLen != NULL) { - *gRFAL.TxRx.ctx.rxRcvdLen = 0; - } - - gRFAL.TxRx.state = - (rfalIsModeActiveComm(gRFAL.mode) ? RFAL_TXRX_STATE_RX_WAIT_EON : - RFAL_TXRX_STATE_RX_WAIT_RXS); - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_RXS: - - /*******************************************************************************/ - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_NRE | ST25R3916_IRQ_MASK_EOF)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - /* Only raise Timeout if NRE is detected with no Rx Start (NRT EMV mode) */ - if(((irqs & ST25R3916_IRQ_MASK_NRE) != 0U) && ((irqs & ST25R3916_IRQ_MASK_RXS) == 0U)) { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* Only raise Link Loss if EOF is detected with no Rx Start */ - if(((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) && ((irqs & ST25R3916_IRQ_MASK_RXS) == 0U)) { - /* In AP2P a Field On has already occurred - treat this as timeout | mute */ - gRFAL.TxRx.status = (rfalIsModeActiveComm(gRFAL.mode) ? ERR_TIMEOUT : ERR_LINK_LOSS); - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - if((irqs & ST25R3916_IRQ_MASK_RXS) != 0U) { - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* Rarely on corrupted frames I_rxs gets signaled but I_rxe is not signaled */ - /* Use a SW timer to handle an eventual missing RXE */ - rfalTimerStart(gRFAL.tmr.RXE, RFAL_NORXE_TOUT); - /*******************************************************************************/ - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXE; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* remove NRE that might appear together (NRT EMV mode), and remove RXS, but keep EOF if present for next state */ - irqs &= ~(ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_NRE); - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_RXE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* ST25R396 may indicate RXS without RXE afterwards, this happens rarely on */ - /* corrupted frames. */ - /* SW timer is used to timeout upon a missing RXE */ - if(rfalTimerisExpired(gRFAL.tmr.RXE)) { - gRFAL.TxRx.status = ERR_FRAMING; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - /*******************************************************************************/ - - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_EOF | - ST25R3916_IRQ_MASK_RX_REST | ST25R3916_IRQ_MASK_WU_F)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_RX_REST) != 0U) { - /* RX_REST indicates that Receiver has been reset due to EMD, therefore a RXS + RXE should * - * follow if a good reception is followed within the valid initial timeout */ - - /* Check whether NRT has expired already, if so signal a timeout */ - if(st25r3916GetInterrupt(ST25R3916_IRQ_MASK_NRE) != 0U) { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - if(st25r3916CheckReg( - ST25R3916_REG_NFCIP1_BIT_RATE, - ST25R3916_REG_NFCIP1_BIT_RATE_nrt_on, - 0)) /* MISRA 13.5 */ - { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* Discard any previous RXS */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_RXS); - - /* Check whether a following reception has already started */ - if(st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_rx_act, - ST25R3916_REG_AUX_DISPLAY_rx_act)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXE; - break; - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - break; - } - - if(((irqs & ST25R3916_IRQ_MASK_FWL) != 0U) && ((irqs & ST25R3916_IRQ_MASK_RXE) == 0U)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_FIFO; - break; - } - - /* Automatic responses allowed during TxRx only for the SENSF_REQ */ - if((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - break; - } - - /* After RXE retrieve and check for any error irqs */ - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_ERR1 | - ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_COL)); - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_ERR_CHECK; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_ERR_CHECK: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - if((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U) { - gRFAL.TxRx.status = ERR_FRAMING; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } - /* Discard Soft Framing errors in AP2P and CE */ - else if(rfalIsModePassivePoll(gRFAL.mode) && ((irqs & ST25R3916_IRQ_MASK_ERR2) != 0U)) { - gRFAL.TxRx.status = ERR_FRAMING; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if((irqs & ST25R3916_IRQ_MASK_PAR) != 0U) { - gRFAL.TxRx.status = ERR_PAR; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) { - gRFAL.TxRx.status = ERR_CRC; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if((irqs & ST25R3916_IRQ_MASK_COL) != 0U) { - gRFAL.TxRx.status = ERR_RF_COLLISION; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if(rfalIsModePassiveListen(gRFAL.mode) && ((irqs & ST25R3916_IRQ_MASK_EOF) != 0U)) { - gRFAL.TxRx.status = ERR_LINK_LOSS; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } else if((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) { - /* Reception ended without any error indication, * - * check FIFO status for malformed or incomplete frames */ - - /* Check if the reception ends with an incomplete byte (residual bits) */ - if(rfalFIFOStatusIsIncompleteByte()) { - gRFAL.TxRx.status = ERR_INCOMPLETE_BYTE; - } - /* Check if the reception ends missing parity bit */ - else if(rfalFIFOStatusIsMissingPar()) { - gRFAL.TxRx.status = ERR_FRAMING; - } else { - /* MISRA 15.7 - Empty else */ - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_READ_DATA: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - tmp = rfalFIFOStatusGetNumBytes(); - - /*******************************************************************************/ - /* Check if CRC should not be placed in rxBuf */ - if(((gRFAL.TxRx.ctx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP) == 0U)) { - /* if received frame was bigger than CRC */ - if((uint16_t)(gRFAL.fifo.bytesTotal + tmp) > 0U) { - /* By default CRC will not be placed into the rxBuffer */ - if((tmp > RFAL_CRC_LEN)) { - tmp -= RFAL_CRC_LEN; - } - /* If the CRC was already placed into rxBuffer (due to WL interrupt where CRC was already in FIFO Read) - * cannot remove it from rxBuf. Can only remove it from rxBufLen not indicate the presence of CRC */ - else if(gRFAL.fifo.bytesTotal > RFAL_CRC_LEN) { - gRFAL.fifo.bytesTotal -= RFAL_CRC_LEN; - } else { - /* MISRA 15.7 - Empty else */ - } - } - } - - gRFAL.fifo.bytesTotal += tmp; /* add to total bytes counter */ - - /*******************************************************************************/ - /* Check if remaining bytes fit on the rxBuf available */ - if(gRFAL.fifo.bytesTotal > rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen)) { - tmp = - (uint16_t)(rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen) - gRFAL.fifo.bytesWritten); - - /* Transmission errors have precedence over buffer error */ - if(gRFAL.TxRx.status == ERR_BUSY) { - gRFAL.TxRx.status = ERR_NOMEM; - } - } - - /*******************************************************************************/ - /* Retrieve remaining bytes from FIFO to rxBuf, and assign total length rcvd */ - st25r3916ReadFifo(&gRFAL.TxRx.ctx.rxBuf[gRFAL.fifo.bytesWritten], tmp); - if(gRFAL.TxRx.ctx.rxRcvdLen != NULL) { - (*gRFAL.TxRx.ctx.rxRcvdLen) = (uint16_t)rfalConvBytesToBits(gRFAL.fifo.bytesTotal); - if(rfalFIFOStatusIsIncompleteByte()) { - (*gRFAL.TxRx.ctx.rxRcvdLen) -= - (RFAL_BITS_IN_BYTE - rfalFIFOGetNumIncompleteBits()); - } - } - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - /* Decode sub bit stream into payload bits for NFCV, if no error found so far */ - if(((RFAL_MODE_POLL_NFCV == gRFAL.mode) || (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)) && - (gRFAL.TxRx.status == ERR_BUSY)) { - ReturnCode ret; - uint16_t offset = 0; /* REMARK offset not currently used */ - - ret = iso15693VICCDecode( - gRFAL.TxRx.ctx.rxBuf, - gRFAL.fifo.bytesTotal, - gRFAL.nfcvData.origCtx.rxBuf, - rfalConvBitsToBytes(gRFAL.nfcvData.origCtx.rxBufLen), - &offset, - gRFAL.nfcvData.origCtx.rxRcvdLen, - gRFAL.nfcvData.ignoreBits, - (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)); - - if(((ERR_NONE == ret) || (ERR_CRC == ret)) && - (((uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP & gRFAL.nfcvData.origCtx.flags) == 0U) && - ((*gRFAL.nfcvData.origCtx.rxRcvdLen % RFAL_BITS_IN_BYTE) == 0U) && - (*gRFAL.nfcvData.origCtx.rxRcvdLen >= rfalConvBytesToBits(RFAL_CRC_LEN))) { - *gRFAL.nfcvData.origCtx.rxRcvdLen -= - (uint16_t)rfalConvBytesToBits(RFAL_CRC_LEN); /* Remove CRC */ - } -#if 0 - /* Debugging code: output the payload bits by writing into the FIFO and subsequent clearing */ - st25r3916WriteFifo(gRFAL.nfcvData.origCtx.rxBuf, rfalConvBitsToBytes( *gRFAL.nfcvData.origCtx.rxRcvdLen)); - st25r3916ExecuteCommand( ST25R3916_CMD_CLEAR_FIFO ); -#endif - - /* Restore original ctx */ - gRFAL.TxRx.ctx = gRFAL.nfcvData.origCtx; - gRFAL.TxRx.status = ((ret != ERR_NONE) ? ret : ERR_BUSY); - } -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* If an error as been marked/detected don't fall into to RX_DONE */ - if(gRFAL.TxRx.status != ERR_BUSY) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - if(rfalIsModeActiveComm(gRFAL.mode)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_EOF; - break; - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_DONE; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_DONE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - gRFAL.TxRx.status = ERR_NONE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_READ_FIFO: - - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* Rarely on corrupted frames I_rxs gets signaled but I_rxe is not signaled */ - /* Use a SW timer to handle an eventual missing RXE */ - rfalTimerStart(gRFAL.tmr.RXE, RFAL_NORXE_TOUT); - /*******************************************************************************/ - - tmp = rfalFIFOStatusGetNumBytes(); - gRFAL.fifo.bytesTotal += tmp; - - /*******************************************************************************/ - /* Calculate the amount of bytes that still fits in rxBuf */ - aux = - ((gRFAL.fifo.bytesTotal > rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen)) ? - (rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen) - gRFAL.fifo.bytesWritten) : - tmp); - - /*******************************************************************************/ - /* Retrieve incoming bytes from FIFO to rxBuf, and store already read amount */ - st25r3916ReadFifo(&gRFAL.TxRx.ctx.rxBuf[gRFAL.fifo.bytesWritten], aux); - gRFAL.fifo.bytesWritten += aux; - - /*******************************************************************************/ - /* If the bytes already read were not the full FIFO WL, dump the remaining * - * FIFO so that ST25R391x can continue with reception */ - if(aux < tmp) { - st25r3916ReadFifo(NULL, (tmp - aux)); - } - - rfalFIFOStatusClear(); - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_FAIL: - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - /* Error should be assigned by previous state */ - if(gRFAL.TxRx.status == ERR_BUSY) { - gRFAL.TxRx.status = ERR_SYSTEM; - } - - /*rfalLogD( "RFAL: curSt: %d Error: %d \r\n", gRFAL.TxRx.state, gRFAL.TxRx.status );*/ - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_EON: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_NRE | ST25R3916_IRQ_MASK_PPON2)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_EON) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - } - - if((irqs & ST25R3916_IRQ_MASK_NRE) != 0U) { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - if((irqs & ST25R3916_IRQ_MASK_PPON2) != 0U) { - gRFAL.TxRx.status = ERR_LINK_LOSS; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_EOF: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_CAC)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_CAT) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_DONE; - } else if((irqs & ST25R3916_IRQ_MASK_CAC) != 0U) { - gRFAL.TxRx.status = ERR_RF_COLLISION; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - break; - - /*******************************************************************************/ - default: - gRFAL.TxRx.status = ERR_SYSTEM; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } -} - -/*******************************************************************************/ -static void rfalFIFOStatusUpdate(void) { - if(gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] == RFAL_FIFO_STATUS_INVALID) { - st25r3916ReadMultipleRegisters( - ST25R3916_REG_FIFO_STATUS1, gRFAL.fifo.status, ST25R3916_FIFO_STATUS_LEN); - } -} - -/*******************************************************************************/ -static void rfalFIFOStatusClear(void) { - gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] = RFAL_FIFO_STATUS_INVALID; -} - -/*******************************************************************************/ -static uint16_t rfalFIFOStatusGetNumBytes(void) { - uint16_t result; - - rfalFIFOStatusUpdate(); - - result = - ((((uint16_t)gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & - ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) - << RFAL_BITS_IN_BYTE); - result |= (((uint16_t)gRFAL.fifo.status[RFAL_FIFO_STATUS_REG1]) & 0x00FFU); - return result; -} - -/*******************************************************************************/ -static bool rfalFIFOStatusIsIncompleteByte(void) { - rfalFIFOStatusUpdate(); - return ( - (gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) != - 0U); -} - -/*******************************************************************************/ -static bool rfalFIFOStatusIsMissingPar(void) { - rfalFIFOStatusUpdate(); - return ((gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & ST25R3916_REG_FIFO_STATUS2_np_lb) != 0U); -} - -/*******************************************************************************/ -static uint8_t rfalFIFOGetNumIncompleteBits(void) { - rfalFIFOStatusUpdate(); - return ( - (gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_lb_shift); -} - -#if RFAL_FEATURE_NFCA - -/*******************************************************************************/ -ReturnCode rfalISO14443ATransceiveShortFrame( - rfal14443AShortFrameCmd txCmd, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* rxRcvdLen, - uint32_t fwt) { - ReturnCode ret; - uint8_t directCmd; - - /* Check if RFAL is properly initialized */ - if(!st25r3916IsTxEnabled() || (gRFAL.state < RFAL_STATE_MODE_SET) || - ((gRFAL.mode != RFAL_MODE_POLL_NFCA) && (gRFAL.mode != RFAL_MODE_POLL_NFCA_T1T))) { - return ERR_WRONG_STATE; - } - - /* Check for valid parameters */ - if((rxBuf == NULL) || (rxRcvdLen == NULL) || (fwt == RFAL_FWT_NONE)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Select the Direct Command to be performed */ - switch(txCmd) { - case RFAL_14443A_SHORTFRAME_CMD_WUPA: - directCmd = ST25R3916_CMD_TRANSMIT_WUPA; - break; - - case RFAL_14443A_SHORTFRAME_CMD_REQA: - directCmd = ST25R3916_CMD_TRANSMIT_REQA; - break; - - default: - return ERR_PARAM; - } - - /* Disable CRC while receiving since ATQA has no CRC included */ - st25r3916SetRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - - /*******************************************************************************/ - /* Wait for GT and FDT */ - while(!rfalIsGTExpired()) { /* MISRA 15.6: mandatory brackets */ - }; - while(st25r3916IsGPTRunning()) { /* MISRA 15.6: mandatory brackets */ - }; - - rfalTimerDestroy(gRFAL.tmr.GT); - gRFAL.tmr.GT = RFAL_TIMING_NONE; - - /*******************************************************************************/ - /* Prepare for Transceive, Receive only (bypass Tx states) */ - gRFAL.TxRx.ctx.flags = - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP); - gRFAL.TxRx.ctx.rxBuf = rxBuf; - gRFAL.TxRx.ctx.rxBufLen = rxBufLen; - gRFAL.TxRx.ctx.rxRcvdLen = rxRcvdLen; - gRFAL.TxRx.ctx.fwt = fwt; - - /*******************************************************************************/ - /* Load NRT with FWT */ - st25r3916SetNoResponseTime(rfalConv1fcTo64fc( - MIN((fwt + RFAL_FWT_ADJUSTMENT + RFAL_FWT_A_ADJUSTMENT), RFAL_ST25R3916_NRT_MAX_1FC))); - - if(gRFAL.timings.FDTListen != RFAL_TIMING_NONE) { - /* Ensure that MRT is using 64/fc steps */ - st25r3916ClrRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_mrt_step); - - /* Set Minimum FDT(Listen) in which PICC is not allowed to send a response */ - st25r3916WriteRegister( - ST25R3916_REG_MASK_RX_TIMER, - (uint8_t)rfalConv1fcTo64fc( - ((RFAL_FDT_LISTEN_MRT_ADJUSTMENT + RFAL_FDT_LISTEN_A_ADJUSTMENT) > - gRFAL.timings.FDTListen) ? - RFAL_ST25R3916_MRT_MIN_1FC : - (gRFAL.timings.FDTListen - - (RFAL_FDT_LISTEN_MRT_ADJUSTMENT + RFAL_FDT_LISTEN_A_ADJUSTMENT)))); - } - - /* In Passive communications General Purpose Timer is used to measure FDT Poll */ - if(gRFAL.timings.FDTPoll != RFAL_TIMING_NONE) { - /* Configure GPT to start at RX end */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc( - MIN(gRFAL.timings.FDTPoll, (gRFAL.timings.FDTPoll - RFAL_FDT_POLL_ADJUSTMENT))), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_erx); - } - - /*******************************************************************************/ - rfalPrepareTransceive(); - - /* Also enable bit collision interrupt */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_COL); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_COL); - - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeTx(); - - /*******************************************************************************/ - /* Clear nbtx bits before sending WUPA/REQA - otherwise ST25R3916 will report parity error, Note2 of the register */ - st25r3916WriteRegister(ST25R3916_REG_NUM_TX_BYTES2, 0); - - /* Send either WUPA or REQA. All affected tags will backscatter ATQA and change to READY state */ - st25r3916ExecuteCommand(directCmd); - - /* Wait for TXE */ - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_TXE, - (uint16_t)MAX(rfalConv1fcToMs(fwt), RFAL_ST25R3916_SW_TMR_MIN_1MS)) == 0U) { - ret = ERR_IO; - } else { - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeRx(); - - /* Jump into a transceive Rx state for reception (bypass Tx states) */ - gRFAL.state = RFAL_STATE_TXRX; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - gRFAL.TxRx.status = ERR_BUSY; - - /* Execute Transceive Rx blocking */ - ret = rfalTransceiveBlockingRx(); - } - - /* Disable Collision interrupt */ - st25r3916DisableInterrupts((ST25R3916_IRQ_MASK_COL)); - - /* ReEnable CRC on Rx */ - st25r3916ClrRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalISO14443ATransceiveAnticollisionFrame( - uint8_t* buf, - uint8_t* bytesToSend, - uint8_t* bitsToSend, - uint16_t* rxLength, - uint32_t fwt) { - ReturnCode ret; - rfalTransceiveContext ctx; - uint8_t collByte; - uint8_t collData; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCA)) { - return ERR_WRONG_STATE; - } - - /* Check for valid parameters */ - if((buf == NULL) || (bytesToSend == NULL) || (bitsToSend == NULL) || (rxLength == NULL)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Set specific Analog Config for Anticolission if needed */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL)); - - /*******************************************************************************/ - /* Enable anti collision to recognise collision in first byte of SENS_REQ */ - st25r3916SetRegisterBits(ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_antcl); - - /* Disable CRC while receiving */ - st25r3916SetRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - - /*******************************************************************************/ - /* Prepare for Transceive */ - ctx.flags = ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP); - ctx.txBuf = buf; - ctx.txBufLen = (uint16_t)(rfalConvBytesToBits(*bytesToSend) + *bitsToSend); - ctx.rxBuf = &buf[*bytesToSend]; - ctx.rxBufLen = (uint16_t)rfalConvBytesToBits(RFAL_ISO14443A_SDD_RES_LEN); - ctx.rxRcvdLen = rxLength; - ctx.fwt = fwt; - - /* Disable Automatic Gain Control (AGC) for better detection of collisions if using Coherent Receiver */ - ctx.flags |= - (st25r3916CheckReg( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_dis_corr, ST25R3916_REG_AUX_dis_corr) ? - (uint32_t)RFAL_TXRX_FLAGS_AGC_OFF : - 0x00U); - - rfalStartTransceive(&ctx); - - /* Additionally enable bit collision interrupt */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_COL); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_COL); - - /*******************************************************************************/ - collByte = 0; - - /* save the collision byte */ - if((*bitsToSend) > 0U) { - buf[(*bytesToSend)] <<= (RFAL_BITS_IN_BYTE - (*bitsToSend)); - buf[(*bytesToSend)] >>= (RFAL_BITS_IN_BYTE - (*bitsToSend)); - collByte = buf[(*bytesToSend)]; - } - - /*******************************************************************************/ - /* Run Transceive blocking */ - ret = rfalTransceiveRunBlockingTx(); - if(ret == ERR_NONE) { - ret = rfalTransceiveBlockingRx(); - - /*******************************************************************************/ - if((*bitsToSend) > 0U) { - buf[(*bytesToSend)] >>= (*bitsToSend); - buf[(*bytesToSend)] <<= (*bitsToSend); - buf[(*bytesToSend)] |= collByte; - } - - if((ERR_RF_COLLISION == ret)) { - /* read out collision register */ - st25r3916ReadRegister(ST25R3916_REG_COLLISION_STATUS, &collData); - - (*bytesToSend) = - ((collData >> ST25R3916_REG_COLLISION_STATUS_c_byte_shift) & - 0x0FU); // 4-bits Byte information - (*bitsToSend) = - ((collData >> ST25R3916_REG_COLLISION_STATUS_c_bit_shift) & - 0x07U); // 3-bits bit information - } - } - - /*******************************************************************************/ - /* Disable Collision interrupt */ - st25r3916DisableInterrupts((ST25R3916_IRQ_MASK_COL)); - - /* Disable anti collision again */ - st25r3916ClrRegisterBits(ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_antcl); - - /* ReEnable CRC on Rx */ - st25r3916ClrRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - /*******************************************************************************/ - - /* Restore common Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.txBR) | - RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.rxBR) | - RFAL_ANALOG_CONFIG_RX)); - - return ret; -} - -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCV - -/*******************************************************************************/ -ReturnCode rfalISO15693TransceiveAnticollisionFrame( - uint8_t* txBuf, - uint8_t txBufLen, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* actLen) { - ReturnCode ret; - rfalTransceiveContext ctx; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCV)) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Set specific Analog Config for Anticolission if needed */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL)); - - /* Ignoring collisions before the UID (RES_FLAG + DSFID) */ - gRFAL.nfcvData.ignoreBits = (uint16_t)RFAL_ISO15693_IGNORE_BITS; - - /*******************************************************************************/ - /* Prepare for Transceive */ - ctx.flags = - ((txBufLen == 0U) ? (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL : - (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO) | - (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_AGC_OFF | - ((txBufLen == 0U) ? - (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL : - (uint32_t) - RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO); /* Disable Automatic Gain Control (AGC) for better detection of collision */ - ctx.txBuf = txBuf; - ctx.txBufLen = (uint16_t)rfalConvBytesToBits(txBufLen); - ctx.rxBuf = rxBuf; - ctx.rxBufLen = (uint16_t)rfalConvBytesToBits(rxBufLen); - ctx.rxRcvdLen = actLen; - ctx.fwt = rfalConv64fcTo1fc(ISO15693_FWT); - - rfalStartTransceive(&ctx); - - /*******************************************************************************/ - /* Run Transceive blocking */ - ret = rfalTransceiveRunBlockingTx(); - if(ret == ERR_NONE) { - ret = rfalTransceiveBlockingRx(); - } - - /* Check if a Transmission error and received data is less then expected */ - if(((ret == ERR_RF_COLLISION) || (ret == ERR_CRC) || (ret == ERR_FRAMING)) && - (rfalConvBitsToBytes(*ctx.rxRcvdLen) < RFAL_ISO15693_INV_RES_LEN)) { - /* If INVENTORY_RES is shorter than expected, tag is still modulating * - * Ensure that response is complete before next frame */ - platformDelay(( - uint8_t)((RFAL_ISO15693_INV_RES_LEN - rfalConvBitsToBytes(*ctx.rxRcvdLen)) / ((RFAL_ISO15693_INV_RES_LEN / RFAL_ISO15693_INV_RES_DUR) + 1U))); - } - - /* Restore common Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.txBR) | - RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.rxBR) | - RFAL_ANALOG_CONFIG_RX)); - - gRFAL.nfcvData.ignoreBits = 0; - return ret; -} - -/*******************************************************************************/ -ReturnCode - rfalISO15693TransceiveEOFAnticollision(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen) { - uint8_t dummy; - - return rfalISO15693TransceiveAnticollisionFrame(&dummy, 0, rxBuf, rxBufLen, actLen); -} - -/*******************************************************************************/ -ReturnCode rfalISO15693TransceiveEOF(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen) { - ReturnCode ret; - uint8_t dummy; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCV)) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Run Transceive blocking */ - ret = rfalTransceiveBlockingTxRx( - &dummy, - 0, - rxBuf, - rxBufLen, - actLen, - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | - (uint32_t)RFAL_TXRX_FLAGS_AGC_ON), - rfalConv64fcTo1fc(ISO15693_FWT)); - return ret; -} - -#endif /* RFAL_FEATURE_NFCV */ - -#if RFAL_FEATURE_NFCF - -/*******************************************************************************/ -ReturnCode rfalFeliCaPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* pollResList, - uint8_t pollResListSize, - uint8_t* devicesDetected, - uint8_t* collisionsDetected) { - ReturnCode ret; - uint8_t frame - [RFAL_FELICA_POLL_REQ_LEN - RFAL_FELICA_LEN_LEN]; // LEN is added by ST25R391x automatically - uint16_t actLen; - uint8_t frameIdx; - uint8_t devDetected; - uint8_t colDetected; - rfalEHandling curHandling; - uint8_t nbSlots; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCF)) { - return ERR_WRONG_STATE; - } - - frameIdx = 0; - colDetected = 0; - devDetected = 0; - nbSlots = (uint8_t)slots; - - /*******************************************************************************/ - /* Compute SENSF_REQ frame */ - frame[frameIdx++] = (uint8_t)FELICA_CMD_POLLING; /* CMD: SENF_REQ */ - frame[frameIdx++] = (uint8_t)(sysCode >> 8); /* System Code (SC) */ - frame[frameIdx++] = (uint8_t)(sysCode & 0xFFU); /* System Code (SC) */ - frame[frameIdx++] = reqCode; /* Communication Parameter Request (RC)*/ - frame[frameIdx++] = nbSlots; /* TimeSlot (TSN) */ - - /*******************************************************************************/ - /* NRT should not stop on reception - Use EMVCo mode to run NRT in nrt_emv * - * ERRORHANDLING_EMVCO has no special handling for NFC-F mode */ - curHandling = gRFAL.conf.eHandling; - rfalSetErrorHandling(RFAL_ERRORHANDLING_EMVCO); - - /*******************************************************************************/ - /* Run transceive blocking, - * Calculate Total Response Time in(64/fc): - * 512 PICC process time + (n * 256 Time Slot duration) */ - ret = rfalTransceiveBlockingTx( - frame, - (uint16_t)frameIdx, - (uint8_t*)gRFAL.nfcfData.pollResponses, - RFAL_FELICA_POLL_RES_LEN, - &actLen, - (RFAL_TXRX_FLAGS_DEFAULT), - rfalConv64fcTo1fc( - RFAL_FELICA_POLL_DELAY_TIME + - (RFAL_FELICA_POLL_SLOT_TIME * ((uint32_t)nbSlots + 1U)))); - - /*******************************************************************************/ - /* If Tx OK, Wait for all responses, store them as soon as they appear */ - if(ret == ERR_NONE) { - bool timeout; - - do { - ret = rfalTransceiveBlockingRx(); - if(ret == ERR_TIMEOUT) { - /* Upon timeout the full Poll Delay + (Slot time)*(nbSlots) has expired */ - timeout = true; - } else { - /* Reception done, reEnabled Rx for following Slot */ - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - st25r3916ExecuteCommand(ST25R3916_CMD_RESET_RXGAIN); - - /* If the reception was OK, new device found */ - if(ret == ERR_NONE) { - devDetected++; - - /* Overwrite the Transceive context for the next reception */ - gRFAL.TxRx.ctx.rxBuf = (uint8_t*)gRFAL.nfcfData.pollResponses[devDetected]; - } - /* If the reception was not OK, mark as collision */ - else { - colDetected++; - } - - /* Check whether NRT has expired meanwhile */ - timeout = st25r3916CheckReg( - ST25R3916_REG_NFCIP1_BIT_RATE, ST25R3916_REG_NFCIP1_BIT_RATE_nrt_on, 0x00); - if(!timeout) { - /* Jump again into transceive Rx state for the following reception */ - gRFAL.TxRx.status = ERR_BUSY; - gRFAL.state = RFAL_STATE_TXRX; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - } - } - } while(((nbSlots--) != 0U) && !timeout); - } - - /*******************************************************************************/ - /* Restore NRT to normal mode - back to previous error handling */ - rfalSetErrorHandling(curHandling); - - /*******************************************************************************/ - /* Assign output parameters if requested */ - - if((pollResList != NULL) && (pollResListSize > 0U) && (devDetected > 0U)) { - ST_MEMCPY( - pollResList, - gRFAL.nfcfData.pollResponses, - (RFAL_FELICA_POLL_RES_LEN * (uint32_t)MIN(pollResListSize, devDetected))); - } - - if(devicesDetected != NULL) { - *devicesDetected = devDetected; - } - - if(collisionsDetected != NULL) { - *collisionsDetected = colDetected; - } - - return (((colDetected != 0U) || (devDetected != 0U)) ? ERR_NONE : ret); -} - -#endif /* RFAL_FEATURE_NFCF */ - -/***************************************************************************** - * Listen Mode * - *****************************************************************************/ - -/*******************************************************************************/ -bool rfalIsExtFieldOn(void) { - return st25r3916IsExtFieldOn(); -} - -#if RFAL_FEATURE_LISTEN_MODE - -/*******************************************************************************/ -ReturnCode rfalListenStart( - uint32_t lmMask, - const rfalLmConfPA* confA, - const rfalLmConfPB* confB, - const rfalLmConfPF* confF, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxLen) { - t_rfalPTMem - PTMem; /* PRQA S 0759 # MISRA 19.2 - Allocating Union where members are of the same type, just different names. Thus no problem can occur. */ - uint8_t* pPTMem; - uint8_t autoResp; - - /* Check if RFAL is initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - gRFAL.Lm.state = RFAL_LM_STATE_NOT_INIT; - gRFAL.Lm.mdIrqs = ST25R3916_IRQ_MASK_NONE; - gRFAL.Lm.mdReg = - (ST25R3916_REG_MODE_targ_init | ST25R3916_REG_MODE_om_nfc | ST25R3916_REG_MODE_nfc_ar_off); - - /* By default disable all automatic responses */ - autoResp = - (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_rfu | - ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p); - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_NFCA) != 0U) { - /* Check if the conf has been provided */ - if(confA == NULL) { - return ERR_PARAM; - } - - pPTMem = (uint8_t*)PTMem.PTMem_A; - - /*******************************************************************************/ - /* Check and set supported NFCID Length */ - switch(confA->nfcidLen) { - case RFAL_LM_NFCID_LEN_04: - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_4bytes); - break; - - case RFAL_LM_NFCID_LEN_07: - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_7bytes); - break; - - default: - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Set NFCID */ - ST_MEMCPY(pPTMem, confA->nfcid, RFAL_NFCID1_TRIPLE_LEN); - pPTMem = &pPTMem[RFAL_NFCID1_TRIPLE_LEN]; /* MISRA 18.4 */ - - /* Set SENS_RES */ - ST_MEMCPY(pPTMem, confA->SENS_RES, RFAL_LM_SENS_RES_LEN); - pPTMem = &pPTMem[RFAL_LM_SENS_RES_LEN]; /* MISRA 18.4 */ - - /* Set SEL_RES */ - *pPTMem++ = - ((confA->nfcidLen == RFAL_LM_NFCID_LEN_04) ? - (confA->SEL_RES & ~RFAL_LM_NFCID_INCOMPLETE) : - (confA->SEL_RES | RFAL_LM_NFCID_INCOMPLETE)); - *pPTMem++ = (confA->SEL_RES & ~RFAL_LM_NFCID_INCOMPLETE); - *pPTMem++ = (confA->SEL_RES & ~RFAL_LM_NFCID_INCOMPLETE); - - /* Write into PTMem-A */ - st25r3916WritePTMem(PTMem.PTMem_A, ST25R3916_PTM_A_LEN); - - /*******************************************************************************/ - /* Enable automatic responses for A */ - autoResp &= ~ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a; - - /* Set Target mode, Bit Rate detection and Listen Mode for NFC-F */ - gRFAL.Lm.mdReg |= - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om0 | - ST25R3916_REG_MODE_nfc_ar_off); - - gRFAL.Lm.mdIrqs |= - (ST25R3916_IRQ_MASK_WU_A | ST25R3916_IRQ_MASK_WU_A_X | ST25R3916_IRQ_MASK_RXE_PTA); - } - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_NFCB) != 0U) { - /* Check if the conf has been provided */ - if(confB == NULL) { - return ERR_PARAM; - } - - return ERR_NOTSUPP; - } - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_NFCF) != 0U) { - pPTMem = (uint8_t*)PTMem.PTMem_F; - - /* Check if the conf has been provided */ - if(confF == NULL) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Set System Code */ - ST_MEMCPY(pPTMem, confF->SC, RFAL_LM_SENSF_SC_LEN); - pPTMem = &pPTMem[RFAL_LM_SENSF_SC_LEN]; /* MISRA 18.4 */ - - /* Set SENSF_RES */ - ST_MEMCPY(pPTMem, confF->SENSF_RES, RFAL_LM_SENSF_RES_LEN); - - /* Set RD bytes to 0x00 as ST25R3916 cannot support advances features */ - pPTMem[RFAL_LM_SENSF_RD0_POS] = - 0x00; /* NFC Forum Digital 1.1 Table 46: 0x00 */ - pPTMem[RFAL_LM_SENSF_RD1_POS] = - 0x00; /* NFC Forum Digital 1.1 Table 47: No automatic bit rates */ - - pPTMem = &pPTMem[RFAL_LM_SENS_RES_LEN]; /* MISRA 18.4 */ - - /* Write into PTMem-F */ - st25r3916WritePTMemF(PTMem.PTMem_F, ST25R3916_PTM_F_LEN); - - /*******************************************************************************/ - /* Write 24 TSN "Random" Numbers at first initialization and let it rollover */ - if(!gRFAL.Lm.iniFlag) { - pPTMem = (uint8_t*)PTMem.TSN; - - *pPTMem++ = 0x12; - *pPTMem++ = 0x34; - *pPTMem++ = 0x56; - *pPTMem++ = 0x78; - *pPTMem++ = 0x9A; - *pPTMem++ = 0xBC; - *pPTMem++ = 0xDF; - *pPTMem++ = 0x21; - *pPTMem++ = 0x43; - *pPTMem++ = 0x65; - *pPTMem++ = 0x87; - *pPTMem++ = 0xA9; - - /* Write into PTMem-TSN */ - st25r3916WritePTMemTSN(PTMem.TSN, ST25R3916_PTM_TSN_LEN); - } - - /*******************************************************************************/ - /* Enable automatic responses for F */ - autoResp &= ~(ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r); - - /* Set Target mode, Bit Rate detection and Listen Mode for NFC-F */ - gRFAL.Lm.mdReg |= - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om2 | - ST25R3916_REG_MODE_nfc_ar_off); - - /* In CE NFC-F any data without error will be passed to FIFO, to support CUP */ - gRFAL.Lm.mdIrqs |= - (ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_RXE); - } - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) { - /* Enable Reception of P2P frames */ - autoResp &= ~(ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p); - - /* Set Target mode, Bit Rate detection and Automatic Response RF Collision Avoidance */ - gRFAL.Lm.mdReg |= - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om2 | - ST25R3916_REG_MODE_om0 | ST25R3916_REG_MODE_nfc_ar_auto_rx); - - /* n * TRFW timing shall vary Activity 2.1 3.4.1.1 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, gRFAL.timings.nTRFW); - gRFAL.timings.nTRFW = rfalGennTRFW(gRFAL.timings.nTRFW); - - gRFAL.Lm.mdIrqs |= (ST25R3916_IRQ_MASK_RXE); - } - - /* Check if one of the modes were selected */ - if((gRFAL.Lm.mdReg & ST25R3916_REG_MODE_targ) == ST25R3916_REG_MODE_targ_targ) { - gRFAL.state = RFAL_STATE_LM; - gRFAL.Lm.mdMask = lmMask; - - gRFAL.Lm.rxBuf = rxBuf; - gRFAL.Lm.rxBufLen = rxBufLen; - gRFAL.Lm.rxLen = rxLen; - *gRFAL.Lm.rxLen = 0; - gRFAL.Lm.dataFlag = false; - gRFAL.Lm.iniFlag = true; - - /* Apply the Automatic Responses configuration */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, - (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_rfu | - ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p), - autoResp); - - /* Disable GPT trigger source */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_mask, - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger); - - /* On Bit Rate Detection Mode ST25R391x will filter incoming frames during MRT time starting on External Field On event, use 512/fc steps */ - st25r3916SetRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_mrt_step_512); - st25r3916WriteRegister( - ST25R3916_REG_MASK_RX_TIMER, (uint8_t)rfalConv1fcTo512fc(RFAL_LM_GT)); - - /* Restore default settings on NFCIP1 mode, Receiving parity + CRC bits and manual Tx Parity*/ - st25r3916ClrRegisterBits( - ST25R3916_REG_ISO14443A_NFC, - (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par | - ST25R3916_REG_ISO14443A_NFC_nfc_f0)); - - /* External Field Detector enabled as Automatics on rfalInitialize() */ - - /* Set Analog configurations for generic Listen mode */ - /* Not on SetState(POWER OFF) as otherwise would be applied on every Field Event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON)); - - /* Initialize as POWER_OFF and set proper mode in RF Chip */ - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - return ERR_REQUEST; /* Listen Start called but no mode was enabled */ - } - - return ERR_NONE; -} - -/*******************************************************************************/ -static ReturnCode rfalRunListenModeWorker(void) { - volatile uint32_t irqs; - uint8_t tmp; - - if(gRFAL.state != RFAL_STATE_LM) { - return ERR_WRONG_STATE; - } - - switch(gRFAL.Lm.state) { - /*******************************************************************************/ - case RFAL_LM_STATE_POWER_OFF: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EON)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_EON) != 0U) { - rfalListenSetState(RFAL_LM_STATE_IDLE); - } else { - break; - } - /* fall through */ - - /*******************************************************************************/ - case RFAL_LM_STATE_IDLE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | - ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_RXE_PTA)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_NFCT) != 0U) { - /* Retrieve detected bitrate */ - uint8_t newBr; - st25r3916ReadRegister(ST25R3916_REG_NFCIP1_BIT_RATE, &newBr); - newBr >>= ST25R3916_REG_NFCIP1_BIT_RATE_nfc_rate_shift; - - if(newBr > ST25R3916_REG_BIT_RATE_rxrate_424) { - newBr = ST25R3916_REG_BIT_RATE_rxrate_424; - } - - gRFAL.Lm.brDetected = - (rfalBitRate)(newBr); /* PRQA S 4342 # MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.*/ - } - - if(((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - rfalListenSetState(RFAL_LM_STATE_READY_F); - } else if(((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_EOF | - ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - - if(((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_PAR) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U)) { - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - st25r3916TxOff(); - break; /* A bad reception occurred, remain in same state */ - } - - /* Retrieve received data */ - *gRFAL.Lm.rxLen = st25r3916GetNumFIFOBytes(); - st25r3916ReadFifo( - gRFAL.Lm.rxBuf, MIN(*gRFAL.Lm.rxLen, rfalConvBitsToBytes(gRFAL.Lm.rxBufLen))); - - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* In bitrate detection mode CRC is now checked for NFC-A frames */ - if((*gRFAL.Lm.rxLen > RFAL_CRC_LEN) && (gRFAL.Lm.brDetected == RFAL_BR_106)) { - if(rfalCrcCalculateCcitt( - RFAL_ISO14443A_CRC_INTVAL, gRFAL.Lm.rxBuf, *gRFAL.Lm.rxLen) != 0U) { - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - st25r3916TxOff(); - break; /* A bad reception occurred, remain in same state */ - } - } - /*******************************************************************************/ - - /* Check if the data we got has at least the CRC and remove it, otherwise leave at 0 */ - *gRFAL.Lm.rxLen -= ((*gRFAL.Lm.rxLen > RFAL_CRC_LEN) ? RFAL_CRC_LEN : *gRFAL.Lm.rxLen); - *gRFAL.Lm.rxLen = (uint16_t)rfalConvBytesToBits(*gRFAL.Lm.rxLen); - gRFAL.Lm.dataFlag = true; - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - } else if( - ((irqs & ST25R3916_IRQ_MASK_RXE_PTA) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - if(((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCA) != 0U) && - (gRFAL.Lm.brDetected == RFAL_BR_106)) { - st25r3916ReadRegister(ST25R3916_REG_PASSIVE_TARGET_STATUS, &tmp); - if(tmp > ST25R3916_REG_PASSIVE_TARGET_STATUS_pta_st_idle) { - rfalListenSetState(RFAL_LM_STATE_READY_A); - } - } - } else if(((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) && (!gRFAL.Lm.dataFlag)) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_F: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_EOF)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) { - break; - } else if((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) { - /* Retrieve the error flags/irqs */ - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_ERR1)); - - if(((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U)) { - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - break; /* A bad reception occurred, remain in same state */ - } - - /* Retrieve received data */ - *gRFAL.Lm.rxLen = st25r3916GetNumFIFOBytes(); - st25r3916ReadFifo( - gRFAL.Lm.rxBuf, MIN(*gRFAL.Lm.rxLen, rfalConvBitsToBytes(gRFAL.Lm.rxBufLen))); - - /* Check if the data we got has at least the CRC and remove it, otherwise leave at 0 */ - *gRFAL.Lm.rxLen -= ((*gRFAL.Lm.rxLen > RFAL_CRC_LEN) ? RFAL_CRC_LEN : *gRFAL.Lm.rxLen); - *gRFAL.Lm.rxLen = (uint16_t)rfalConvBytesToBits(*gRFAL.Lm.rxLen); - gRFAL.Lm.dataFlag = true; - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_A: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_WU_A) != 0U) { - rfalListenSetState(RFAL_LM_STATE_ACTIVE_A); - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_ACTIVE_A: - case RFAL_LM_STATE_ACTIVE_Ax: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_EOF)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) { - /* Retrieve the error flags/irqs */ - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - *gRFAL.Lm.rxLen = st25r3916GetNumFIFOBytes(); - - if(((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_PAR) != 0U) || (*gRFAL.Lm.rxLen <= RFAL_CRC_LEN)) { - /* Clear rx context and FIFO */ - *gRFAL.Lm.rxLen = 0; - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Check if we should go to IDLE or Sleep */ - if(gRFAL.Lm.state == RFAL_LM_STATE_ACTIVE_Ax) { - rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, gRFAL.Lm.rxBuf, gRFAL.Lm.rxBufLen, gRFAL.Lm.rxLen); - } else { - rfalListenSetState(RFAL_LM_STATE_IDLE); - } - - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_RXE); - break; - } - - /* Remove CRC from length */ - *gRFAL.Lm.rxLen -= RFAL_CRC_LEN; - - /* Retrieve received data */ - st25r3916ReadFifo( - gRFAL.Lm.rxBuf, MIN(*gRFAL.Lm.rxLen, rfalConvBitsToBytes(gRFAL.Lm.rxBufLen))); - *gRFAL.Lm.rxLen = (uint16_t)rfalConvBytesToBits(*gRFAL.Lm.rxLen); - gRFAL.Lm.dataFlag = true; - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_A: - case RFAL_LM_STATE_SLEEP_B: - case RFAL_LM_STATE_SLEEP_AF: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | - ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_RXE_PTA)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_NFCT) != 0U) { - uint8_t newBr; - /* Retrieve detected bitrate */ - st25r3916ReadRegister(ST25R3916_REG_NFCIP1_BIT_RATE, &newBr); - newBr >>= ST25R3916_REG_NFCIP1_BIT_RATE_nfc_rate_shift; - - if(newBr > ST25R3916_REG_BIT_RATE_rxrate_424) { - newBr = ST25R3916_REG_BIT_RATE_rxrate_424; - } - - gRFAL.Lm.brDetected = - (rfalBitRate)(newBr); /* PRQA S 4342 # MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.*/ - } - - if(((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - rfalListenSetState(RFAL_LM_STATE_READY_F); - } else if(((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - /* Clear rx context and FIFO */ - *gRFAL.Lm.rxLen = 0; - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* REMARK: In order to support CUP or proprietary frames, handling could be added here */ - } else if( - ((irqs & ST25R3916_IRQ_MASK_RXE_PTA) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - if(((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCA) != 0U) && - (gRFAL.Lm.brDetected == RFAL_BR_106)) { - st25r3916ReadRegister(ST25R3916_REG_PASSIVE_TARGET_STATUS, &tmp); - if(tmp > ST25R3916_REG_PASSIVE_TARGET_STATUS_pta_st_halt) { - rfalListenSetState(RFAL_LM_STATE_READY_Ax); - } - } - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_Ax: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_WU_A_X) != 0U) { - rfalListenSetState(RFAL_LM_STATE_ACTIVE_Ax); - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_4A: - case RFAL_LM_STATE_CARDEMU_4B: - case RFAL_LM_STATE_CARDEMU_3: - case RFAL_LM_STATE_TARGET_F: - case RFAL_LM_STATE_TARGET_A: - break; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalListenStop(void) { - /* Check if RFAL is initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - gRFAL.Lm.state = RFAL_LM_STATE_NOT_INIT; - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Re-Enable the Oscillator if not running */ - st25r3916OscOn(); - - /* Disable Receiver and Transmitter */ - rfalFieldOff(); - - /* Disable all automatic responses */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, - (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_rfu | - ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p)); - - /* As there's no Off mode, set default value: ISO14443A with automatic RF Collision Avoidance Off */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_om_iso14443a | ST25R3916_REG_MODE_tr_am_ook | - ST25R3916_REG_MODE_nfc_ar_off)); - - st25r3916DisableInterrupts( - (ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_WU_A | - ST25R3916_IRQ_MASK_WU_A_X | ST25R3916_IRQ_MASK_RFU2 | ST25R3916_IRQ_MASK_OSC)); - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_WU_A | - ST25R3916_IRQ_MASK_WU_A_X | ST25R3916_IRQ_MASK_RFU2)); - - /* Set Analog configurations for Listen Off event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_OFF)); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalListenSleepStart(rfalLmState sleepSt, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxLen) { - /* Check if RFAL is not initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - switch(sleepSt) { - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_A: - - /* Enable automatic responses for A */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Reset NFCA target */ - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); - - /* Set Target mode, Bit Rate detection and Listen Mode for NFC-A */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask | - ST25R3916_REG_MODE_nfc_ar_mask), - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om0 | - ST25R3916_REG_MODE_nfc_ar_off)); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_AF: - - /* Enable automatic responses for A + F */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, - (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Reset NFCA target state */ - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); - - /* Set Target mode, Bit Rate detection, Listen Mode for NFC-A and NFC-F */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask | - ST25R3916_REG_MODE_nfc_ar_mask), - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om2 | - ST25R3916_REG_MODE_om0 | ST25R3916_REG_MODE_nfc_ar_off)); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_B: - /* REMARK: Support for CE-B would be added here */ - return ERR_NOT_IMPLEMENTED; - - /*******************************************************************************/ - default: - return ERR_PARAM; - } - - /* Ensure that the NFCIP1 mode is disabled */ - st25r3916ClrRegisterBits(ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_nfc_f0); - - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Clear and enable required IRQs */ - st25r3916ClearAndEnableInterrupts( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_CRC | - ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_PAR | - ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | gRFAL.Lm.mdIrqs)); - - /* Check whether the field was turn off right after the Sleep request */ - if(!rfalIsExtFieldOn()) { - /*rfalLogD( "RFAL: curState: %02X newState: %02X \r\n", gRFAL.Lm.state, RFAL_LM_STATE_NOT_INIT );*/ - - rfalListenStop(); - return ERR_LINK_LOSS; - } - - /*rfalLogD( "RFAL: curState: %02X newState: %02X \r\n", gRFAL.Lm.state, sleepSt );*/ - - /* Set the new Sleep State*/ - gRFAL.Lm.state = sleepSt; - gRFAL.state = RFAL_STATE_LM; - - gRFAL.Lm.rxBuf = rxBuf; - gRFAL.Lm.rxBufLen = rxBufLen; - gRFAL.Lm.rxLen = rxLen; - *gRFAL.Lm.rxLen = 0; - gRFAL.Lm.dataFlag = false; - - return ERR_NONE; -} - -/*******************************************************************************/ -rfalLmState rfalListenGetState(bool* dataFlag, rfalBitRate* lastBR) { - /* Allow state retrieval even if gRFAL.state != RFAL_STATE_LM so * - * that this Lm state can be used by caller after activation */ - - if(lastBR != NULL) { - *lastBR = gRFAL.Lm.brDetected; - } - - if(dataFlag != NULL) { - *dataFlag = gRFAL.Lm.dataFlag; - } - - return gRFAL.Lm.state; -} - -/*******************************************************************************/ -ReturnCode rfalListenSetState(rfalLmState newSt) { - ReturnCode ret; - rfalLmState newState; - bool reSetState; - - /* Check if RFAL is initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - /* SetState clears the Data flag */ - gRFAL.Lm.dataFlag = false; - newState = newSt; - ret = ERR_NONE; - - do { - reSetState = false; - - /*******************************************************************************/ - switch(newState) { - /*******************************************************************************/ - case RFAL_LM_STATE_POWER_OFF: - - /* Enable the receiver and reset logic */ - st25r3916SetRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_rx_en); - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCA) != 0U) { - /* Enable automatic responses for A */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); - - /* Prepares the NFCIP-1 Passive target logic to wait in the Sense/Idle state */ - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); - } - - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCF) != 0U) { - /* Enable automatic responses for F */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r)); - } - - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) { - /* Ensure automatic response RF Collision Avoidance is back to only after Rx */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_nfc_ar_mask, - ST25R3916_REG_MODE_nfc_ar_auto_rx); - - /* Ensure that our field is Off, as automatic response RF Collision Avoidance may have been triggered */ - st25r3916TxOff(); - } - - /*******************************************************************************/ - /* Ensure that the NFCIP1 mode is disabled */ - st25r3916ClrRegisterBits( - ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_nfc_f0); - - /*******************************************************************************/ - /* Clear and enable required IRQs */ - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - - st25r3916ClearAndEnableInterrupts( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_CRC | - ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_OSC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | - gRFAL.Lm.mdIrqs)); - - /*******************************************************************************/ - /* Clear the bitRate previously detected */ - gRFAL.Lm.brDetected = RFAL_BR_KEEP; - - /*******************************************************************************/ - /* Apply the initial mode */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask | - ST25R3916_REG_MODE_nfc_ar_mask), - (uint8_t)gRFAL.Lm.mdReg); - - /*******************************************************************************/ - /* Check if external Field is already On */ - if(rfalIsExtFieldOn()) { - reSetState = true; - newState = RFAL_LM_STATE_IDLE; /* Set IDLE state */ - } -#if 1 /* Perform bit rate detection in Low power mode */ - else { - st25r3916ClrRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_tx_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_en)); - } -#endif - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_IDLE: - - /*******************************************************************************/ - /* Check if device is coming from Low Power bit rate detection */ - if(!st25r3916CheckReg( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en, - ST25R3916_REG_OP_CONTROL_en)) { - /* Exit Low Power mode and confirm the temporarily enable */ - st25r3916SetRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en)); - - if(!st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_osc_ok, - ST25R3916_REG_AUX_DISPLAY_osc_ok)) { - /* Wait for Oscillator ready */ - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_OSC, ST25R3916_TOUT_OSC_STABLE) == 0U) { - ret = ERR_IO; - break; - } - } - } else { - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_OSC); - } - - /*******************************************************************************/ - /* In Active P2P the Initiator may: Turn its field On; LM goes into IDLE state; - * Initiator sends an unexpected frame raising a Protocol error; Initiator - * turns its field Off and ST25R3916 performs the automatic RF Collision - * Avoidance keeping our field On; upon a Protocol error upper layer sets - * again the state to IDLE to clear dataFlag and wait for next data. - * - * Ensure that when upper layer calls SetState(IDLE), it restores initial - * configuration and that check whether an external Field is still present */ - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) { - /* Ensure nfc_ar is reset and back to only after Rx */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_nfc_ar_mask, - ST25R3916_REG_MODE_nfc_ar_auto_rx); - - /* Ensure that our field is Off, as automatic response RF Collision Avoidance may have been triggered */ - st25r3916TxOff(); - - /* If external Field is no longer detected go back to POWER_OFF */ - if(!st25r3916IsExtFieldOn()) { - reSetState = true; - newState = RFAL_LM_STATE_POWER_OFF; /* Set POWER_OFF state */ - } - } - /*******************************************************************************/ - - /* If we are in ACTIVE_A, reEnable Listen for A before going to IDLE, otherwise do nothing */ - if(gRFAL.Lm.state == RFAL_LM_STATE_ACTIVE_A) { - /* Enable automatic responses for A and Reset NFCA target state */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); - } - - /* ReEnable the receiver */ - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /*******************************************************************************/ - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeRx(); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_F: - - /*******************************************************************************/ - /* If we're coming from BitRate detection mode, the Bit Rate Definition reg - * still has the last bit rate used. - * If a frame is received between setting the mode to Listen NFCA and - * setting Bit Rate Definition reg, it will raise a framing error. - * Set the bitrate immediately, and then the normal SetMode procedure */ - st25r3916SetBitrate((uint8_t)gRFAL.Lm.brDetected, (uint8_t)gRFAL.Lm.brDetected); - /*******************************************************************************/ - - /* Disable automatic responses for NFC-A */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Set Mode NFC-F only */ - ret = rfalSetMode(RFAL_MODE_LISTEN_NFCF, gRFAL.Lm.brDetected, gRFAL.Lm.brDetected); - gRFAL.state = RFAL_STATE_LM; /* Keep in Listen Mode */ - - /* ReEnable the receiver */ - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Clear any previous transmission errors (if Reader polled for other/unsupported technologies) */ - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - - st25r3916EnableInterrupts( - ST25R3916_IRQ_MASK_RXE); /* Start looking for any incoming data */ - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_3: - - /* Set Listen NFCF mode */ - ret = rfalSetMode(RFAL_MODE_LISTEN_NFCF, gRFAL.Lm.brDetected, gRFAL.Lm.brDetected); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_Ax: - case RFAL_LM_STATE_READY_A: - - /*******************************************************************************/ - /* If we're coming from BitRate detection mode, the Bit Rate Definition reg - * still has the last bit rate used. - * If a frame is received between setting the mode to Listen NFCA and - * setting Bit Rate Definition reg, it will raise a framing error. - * Set the bitrate immediately, and then the normal SetMode procedure */ - st25r3916SetBitrate((uint8_t)gRFAL.Lm.brDetected, (uint8_t)gRFAL.Lm.brDetected); - /*******************************************************************************/ - - /* Disable automatic responses for NFC-F */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r)); - - /* Set Mode NFC-A only */ - ret = rfalSetMode(RFAL_MODE_LISTEN_NFCA, gRFAL.Lm.brDetected, gRFAL.Lm.brDetected); - - gRFAL.state = RFAL_STATE_LM; /* Keep in Listen Mode */ - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_ACTIVE_Ax: - case RFAL_LM_STATE_ACTIVE_A: - - /* Disable automatic responses for A */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Clear any previous transmission errors (if Reader polled for other/unsupported technologies) */ - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - - st25r3916EnableInterrupts( - ST25R3916_IRQ_MASK_RXE); /* Start looking for any incoming data */ - break; - - case RFAL_LM_STATE_TARGET_F: - /* Disable Automatic response SENSF_REQ */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r)); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_A: - case RFAL_LM_STATE_SLEEP_B: - case RFAL_LM_STATE_SLEEP_AF: - /* These sleep states have to be set by the rfalListenSleepStart() method */ - return ERR_REQUEST; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_4A: - case RFAL_LM_STATE_CARDEMU_4B: - case RFAL_LM_STATE_TARGET_A: - /* States not handled by the LM, just keep state context */ - break; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - } while(reSetState); - - gRFAL.Lm.state = newState; - - // Call callback on state change - if(gRFAL.callbacks.state_changed_cb) { - gRFAL.callbacks.state_changed_cb(gRFAL.callbacks.ctx); - } - - return ret; -} - -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -/******************************************************************************* - * Wake-Up Mode * - *******************************************************************************/ - -#if RFAL_FEATURE_WAKEUP_MODE - -/*******************************************************************************/ -ReturnCode rfalWakeUpModeStart(const rfalWakeUpConfig* config) { - uint8_t aux; - uint8_t reg; - uint32_t irqs; - - /* Check if RFAL is not initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - /* The Wake-Up procedure is explained in detail in Application Note: AN4985 */ - - if(config == NULL) { - gRFAL.wum.cfg.period = RFAL_WUM_PERIOD_200MS; - gRFAL.wum.cfg.irqTout = false; - gRFAL.wum.cfg.indAmp.enabled = true; - gRFAL.wum.cfg.indPha.enabled = false; - gRFAL.wum.cfg.cap.enabled = false; - gRFAL.wum.cfg.indAmp.delta = 2U; - gRFAL.wum.cfg.indAmp.reference = RFAL_WUM_REFERENCE_AUTO; - gRFAL.wum.cfg.indAmp.autoAvg = false; - - /*******************************************************************************/ - /* Check if AAT is enabled and if so make use of the SW Tag Detection */ - if(st25r3916CheckReg( - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_aat_en, - ST25R3916_REG_IO_CONF2_aat_en)) { - gRFAL.wum.cfg.swTagDetect = true; - gRFAL.wum.cfg.indAmp.autoAvg = true; - gRFAL.wum.cfg.indAmp.aaWeight = RFAL_WUM_AA_WEIGHT_16; - } - } else { - gRFAL.wum.cfg = *config; - } - - /* Check for valid configuration */ - if((!gRFAL.wum.cfg.cap.enabled && !gRFAL.wum.cfg.indAmp.enabled && - !gRFAL.wum.cfg.indPha.enabled) || - (gRFAL.wum.cfg.cap.enabled && - (gRFAL.wum.cfg.indAmp.enabled || gRFAL.wum.cfg.indPha.enabled)) || - (gRFAL.wum.cfg.cap.enabled && gRFAL.wum.cfg.swTagDetect) || - ((gRFAL.wum.cfg.indAmp.reference > RFAL_WUM_REFERENCE_AUTO) || - (gRFAL.wum.cfg.indPha.reference > RFAL_WUM_REFERENCE_AUTO) || - (gRFAL.wum.cfg.cap.reference > RFAL_WUM_REFERENCE_AUTO))) { - return ERR_PARAM; - } - - irqs = ST25R3916_IRQ_MASK_NONE; - - /* Disable Tx, Rx, External Field Detector and set default ISO14443A mode */ - st25r3916TxRxOff(); - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en_fd_mask); - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask), - (ST25R3916_REG_MODE_targ_init | ST25R3916_REG_MODE_om_iso14443a)); - - /* Set Analog configurations for Wake-up On event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_WAKEUP_ON)); - - /*******************************************************************************/ - /* Prepare Wake-Up Timer Control Register */ - reg = - (uint8_t)(((uint8_t)gRFAL.wum.cfg.period & 0x0FU) << ST25R3916_REG_WUP_TIMER_CONTROL_wut_shift); - reg |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.period < (uint8_t)RFAL_WUM_PERIOD_100MS) ? ST25R3916_REG_WUP_TIMER_CONTROL_wur : 0x00U); - - if(gRFAL.wum.cfg.irqTout || gRFAL.wum.cfg.swTagDetect) { - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wto; - irqs |= ST25R3916_IRQ_MASK_WT; - } - - /* Check if HW Wake-up is to be used or SW Tag detection */ - if(gRFAL.wum.cfg.swTagDetect) { - gRFAL.wum.cfg.indAmp.reference = 0U; - gRFAL.wum.cfg.indPha.reference = 0U; - gRFAL.wum.cfg.cap.reference = 0U; - } else { - /*******************************************************************************/ - /* Check if Inductive Amplitude is to be performed */ - if(gRFAL.wum.cfg.indAmp.enabled) { - aux = - (uint8_t)((gRFAL.wum.cfg.indAmp.delta) << ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_d_shift); - aux |= - (uint8_t)(gRFAL.wum.cfg.indAmp.aaInclMeas ? ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_aam : 0x00U); - aux |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.indAmp.aaWeight << ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_aew_shift) & ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_aew_mask); - aux |= - (uint8_t)(gRFAL.wum.cfg.indAmp.autoAvg ? ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_ae : 0x00U); - - st25r3916WriteRegister(ST25R3916_REG_AMPLITUDE_MEASURE_CONF, aux); - - /* Only need to set the reference if not using Auto Average */ - if(!gRFAL.wum.cfg.indAmp.autoAvg) { - if(gRFAL.wum.cfg.indAmp.reference == RFAL_WUM_REFERENCE_AUTO) { - st25r3916MeasureAmplitude(&aux); - gRFAL.wum.cfg.indAmp.reference = aux; - } - st25r3916WriteRegister( - ST25R3916_REG_AMPLITUDE_MEASURE_REF, (uint8_t)gRFAL.wum.cfg.indAmp.reference); - } - - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wam; - irqs |= ST25R3916_IRQ_MASK_WAM; - } - - /*******************************************************************************/ - /* Check if Inductive Phase is to be performed */ - if(gRFAL.wum.cfg.indPha.enabled) { - aux = - (uint8_t)((gRFAL.wum.cfg.indPha.delta) << ST25R3916_REG_PHASE_MEASURE_CONF_pm_d_shift); - aux |= - (uint8_t)(gRFAL.wum.cfg.indPha.aaInclMeas ? ST25R3916_REG_PHASE_MEASURE_CONF_pm_aam : 0x00U); - aux |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.indPha.aaWeight << ST25R3916_REG_PHASE_MEASURE_CONF_pm_aew_shift) & ST25R3916_REG_PHASE_MEASURE_CONF_pm_aew_mask); - aux |= - (uint8_t)(gRFAL.wum.cfg.indPha.autoAvg ? ST25R3916_REG_PHASE_MEASURE_CONF_pm_ae : 0x00U); - - st25r3916WriteRegister(ST25R3916_REG_PHASE_MEASURE_CONF, aux); - - /* Only need to set the reference if not using Auto Average */ - if(!gRFAL.wum.cfg.indPha.autoAvg) { - if(gRFAL.wum.cfg.indPha.reference == RFAL_WUM_REFERENCE_AUTO) { - st25r3916MeasurePhase(&aux); - gRFAL.wum.cfg.indPha.reference = aux; - } - st25r3916WriteRegister( - ST25R3916_REG_PHASE_MEASURE_REF, (uint8_t)gRFAL.wum.cfg.indPha.reference); - } - - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wph; - irqs |= ST25R3916_IRQ_MASK_WPH; - } - - /*******************************************************************************/ - /* Check if Capacitive is to be performed */ - if(gRFAL.wum.cfg.cap.enabled) { - /*******************************************************************************/ - /* Perform Capacitive sensor calibration */ - - /* Disable Oscillator and Field */ - st25r3916ClrRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_tx_en)); - - /* Sensor gain should be configured on Analog Config: RFAL_ANALOG_CONFIG_CHIP_WAKEUP_ON */ - - /* Perform calibration procedure */ - st25r3916CalibrateCapacitiveSensor(NULL); - - /*******************************************************************************/ - aux = - (uint8_t)((gRFAL.wum.cfg.cap.delta) << ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_d_shift); - aux |= - (uint8_t)(gRFAL.wum.cfg.cap.aaInclMeas ? ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_aam : 0x00U); - aux |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.cap.aaWeight << ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_aew_shift) & ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_aew_mask); - aux |= - (uint8_t)(gRFAL.wum.cfg.cap.autoAvg ? ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_ae : 0x00U); - - st25r3916WriteRegister(ST25R3916_REG_CAPACITANCE_MEASURE_CONF, aux); - - /* Only need to set the reference if not using Auto Average */ - if(!gRFAL.wum.cfg.cap.autoAvg || gRFAL.wum.cfg.swTagDetect) { - if(gRFAL.wum.cfg.indPha.reference == RFAL_WUM_REFERENCE_AUTO) { - st25r3916MeasureCapacitance(&aux); - gRFAL.wum.cfg.cap.reference = aux; - } - st25r3916WriteRegister( - ST25R3916_REG_CAPACITANCE_MEASURE_REF, (uint8_t)gRFAL.wum.cfg.cap.reference); - } - - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wcap; - irqs |= ST25R3916_IRQ_MASK_WCAP; - } - } - - /* Disable and clear all interrupts except Wake-Up IRQs */ - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - st25r3916GetInterrupt(irqs); - st25r3916EnableInterrupts(irqs); - - /* Enable Low Power Wake-Up Mode (Disable: Oscilattor, Tx, Rx and External Field Detector) */ - st25r3916WriteRegister(ST25R3916_REG_WUP_TIMER_CONTROL, reg); - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_tx_en | ST25R3916_REG_OP_CONTROL_en_fd_mask | - ST25R3916_REG_OP_CONTROL_wu), - ST25R3916_REG_OP_CONTROL_wu); - - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED; - gRFAL.state = RFAL_STATE_WUM; - - return ERR_NONE; -} - -/*******************************************************************************/ -bool rfalWakeUpModeHasWoke(void) { - return (gRFAL.wum.state >= RFAL_WUM_STATE_ENABLED_WOKE); -} - -/*******************************************************************************/ -static uint16_t rfalWakeUpModeFilter(uint16_t curRef, uint16_t curVal, uint8_t weight) { - uint16_t newRef; - - /* Perform the averaging|filter as describded in ST25R3916 DS */ - - /* Avoid signed arithmetics by splitting in two cases */ - if(curVal > curRef) { - newRef = curRef + ((curVal - curRef) / weight); - - /* In order for the reference to converge to final value * - * increment once the diff is smaller that the weight */ - if((curVal != curRef) && (curRef == newRef)) { - newRef &= 0xFF00U; - newRef += 0x0100U; - } - } else { - newRef = curRef - ((curRef - curVal) / weight); - - /* In order for the reference to converge to final value * - * decrement once the diff is smaller that the weight */ - if((curVal != curRef) && (curRef == newRef)) { - newRef &= 0xFF00U; - } - } - - return newRef; -} - -/*******************************************************************************/ -static void rfalRunWakeUpModeWorker(void) { - uint32_t irqs; - uint8_t reg; - uint16_t value; - uint16_t delta; - - if(gRFAL.state != RFAL_STATE_WUM) { - return; - } - - switch(gRFAL.wum.state) { - case RFAL_WUM_STATE_ENABLED: - case RFAL_WUM_STATE_ENABLED_WOKE: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_WT | ST25R3916_IRQ_MASK_WAM | ST25R3916_IRQ_MASK_WPH | - ST25R3916_IRQ_MASK_WCAP)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - /*******************************************************************************/ - /* Check and mark which measurement(s) cause interrupt */ - if((irqs & ST25R3916_IRQ_MASK_WAM) != 0U) { - st25r3916ReadRegister(ST25R3916_REG_AMPLITUDE_MEASURE_RESULT, ®); - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - } - - if((irqs & ST25R3916_IRQ_MASK_WPH) != 0U) { - st25r3916ReadRegister(ST25R3916_REG_PHASE_MEASURE_RESULT, ®); - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - } - - if((irqs & ST25R3916_IRQ_MASK_WCAP) != 0U) { - st25r3916ReadRegister(ST25R3916_REG_CAPACITANCE_MEASURE_RESULT, ®); - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - } - - if((irqs & ST25R3916_IRQ_MASK_WT) != 0U) { - /*******************************************************************************/ - if(gRFAL.wum.cfg.swTagDetect) { - /* Enable Ready mode and wait the settle time */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_wu), - ST25R3916_REG_OP_CONTROL_en); - platformDelay(RFAL_ST25R3916_AAT_SETTLE); - - /*******************************************************************************/ - if(gRFAL.wum.cfg.indAmp.enabled) { - /* Perform amplitude measurement */ - st25r3916MeasureAmplitude(®); - - /* Convert inputs to TD format */ - value = rfalConvTDFormat(reg); - delta = rfalConvTDFormat(gRFAL.wum.cfg.indAmp.delta); - - /* Set first measurement as reference */ - if(gRFAL.wum.cfg.indAmp.reference == 0U) { - gRFAL.wum.cfg.indAmp.reference = value; - } - - /* Check if device should be woken */ - if((value >= (gRFAL.wum.cfg.indAmp.reference + delta)) || - (value <= (gRFAL.wum.cfg.indAmp.reference - delta))) { - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - break; - } - - /* Update moving reference if enabled */ - if(gRFAL.wum.cfg.indAmp.autoAvg) { - gRFAL.wum.cfg.indAmp.reference = rfalWakeUpModeFilter( - gRFAL.wum.cfg.indAmp.reference, - value, - (RFAL_WU_MIN_WEIGHT_VAL << (uint8_t)gRFAL.wum.cfg.indAmp.aaWeight)); - } - } - - /*******************************************************************************/ - if(gRFAL.wum.cfg.indPha.enabled) { - /* Perform Phase measurement */ - st25r3916MeasurePhase(®); - - /* Convert inputs to TD format */ - value = rfalConvTDFormat(reg); - delta = rfalConvTDFormat(gRFAL.wum.cfg.indPha.delta); - - /* Set first measurement as reference */ - if(gRFAL.wum.cfg.indPha.reference == 0U) { - gRFAL.wum.cfg.indPha.reference = value; - } - - /* Check if device should be woken */ - if((value >= (gRFAL.wum.cfg.indPha.reference + delta)) || - (value <= (gRFAL.wum.cfg.indPha.reference - delta))) { - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - break; - } - - /* Update moving reference if enabled */ - if(gRFAL.wum.cfg.indPha.autoAvg) { - gRFAL.wum.cfg.indPha.reference = rfalWakeUpModeFilter( - gRFAL.wum.cfg.indPha.reference, - value, - (RFAL_WU_MIN_WEIGHT_VAL << (uint8_t)gRFAL.wum.cfg.indPha.aaWeight)); - } - } - - /* Re-Enable low power Wake-Up mode for wto to trigger another measurement(s) */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_wu), - (ST25R3916_REG_OP_CONTROL_wu)); - } - } - break; - - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } -} - -/*******************************************************************************/ -ReturnCode rfalWakeUpModeStop(void) { - /* Check if RFAL is in Wake-up mode */ - if(gRFAL.state != RFAL_STATE_WUM) { - return ERR_WRONG_STATE; - } - - gRFAL.wum.state = RFAL_WUM_STATE_NOT_INIT; - - /* Disable Wake-Up Mode */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - st25r3916DisableInterrupts( - (ST25R3916_IRQ_MASK_WT | ST25R3916_IRQ_MASK_WAM | ST25R3916_IRQ_MASK_WPH | - ST25R3916_IRQ_MASK_WCAP)); - - /* Re-Enable External Field Detector as: Automatics */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en_fd_mask, - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - - /* Re-Enable the Oscillator */ - st25r3916OscOn(); - - /* Set Analog configurations for Wake-up Off event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_WAKEUP_OFF)); - - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -/******************************************************************************* - * Low-Power Mode * - *******************************************************************************/ - -#if RFAL_FEATURE_LOWPOWER_MODE - -/*******************************************************************************/ -ReturnCode rfalLowPowerModeStart(void) { - /* Check if RFAL is not initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - /* Stop any ongoing activity and set the device in low power by disabling oscillator, transmitter, receiver and external field detector */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - st25r3916ClrRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_wu | ST25R3916_REG_OP_CONTROL_tx_en | - ST25R3916_REG_OP_CONTROL_en_fd_mask)); - - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_ON)); - - gRFAL.state = RFAL_STATE_IDLE; - gRFAL.lpm.isRunning = true; - - platformDisableIrqCallback(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalLowPowerModeStop(void) { - ReturnCode ret; - - platformEnableIrqCallback(); - - /* Check if RFAL is on right state */ - if(!gRFAL.lpm.isRunning) { - return ERR_WRONG_STATE; - } - - /* Re-enable device */ - EXIT_ON_ERR(ret, st25r3916OscOn()); - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en_fd_mask, - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_OFF)); - - gRFAL.state = RFAL_STATE_INIT; - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - -/******************************************************************************* - * RF Chip * - *******************************************************************************/ - -/*******************************************************************************/ -ReturnCode rfalChipWriteReg(uint16_t reg, const uint8_t* values, uint8_t len) { - if(!st25r3916IsRegValid((uint8_t)reg)) { - return ERR_PARAM; - } - - return st25r3916WriteMultipleRegisters((uint8_t)reg, values, len); -} - -/*******************************************************************************/ -ReturnCode rfalChipReadReg(uint16_t reg, uint8_t* values, uint8_t len) { - if(!st25r3916IsRegValid((uint8_t)reg)) { - return ERR_PARAM; - } - - return st25r3916ReadMultipleRegisters((uint8_t)reg, values, len); -} - -/*******************************************************************************/ -ReturnCode rfalChipExecCmd(uint16_t cmd) { - if(!st25r3916IsCmdValid((uint8_t)cmd)) { - return ERR_PARAM; - } - - return st25r3916ExecuteCommand((uint8_t)cmd); -} - -/*******************************************************************************/ -ReturnCode rfalChipWriteTestReg(uint16_t reg, uint8_t value) { - return st25r3916WriteTestRegister((uint8_t)reg, value); -} - -/*******************************************************************************/ -ReturnCode rfalChipReadTestReg(uint16_t reg, uint8_t* value) { - return st25r3916ReadTestRegister((uint8_t)reg, value); -} - -/*******************************************************************************/ -ReturnCode rfalChipChangeRegBits(uint16_t reg, uint8_t valueMask, uint8_t value) { - if(!st25r3916IsRegValid((uint8_t)reg)) { - return ERR_PARAM; - } - - return st25r3916ChangeRegisterBits((uint8_t)reg, valueMask, value); -} - -/*******************************************************************************/ -ReturnCode rfalChipChangeTestRegBits(uint16_t reg, uint8_t valueMask, uint8_t value) { - st25r3916ChangeTestRegisterBits((uint8_t)reg, valueMask, value); - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalChipSetRFO(uint8_t rfo) { - return st25r3916ChangeRegisterBits( - ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_d_res_mask, rfo); -} - -/*******************************************************************************/ -ReturnCode rfalChipGetRFO(uint8_t* result) { - ReturnCode ret; - - ret = st25r3916ReadRegister(ST25R3916_REG_TX_DRIVER, result); - - (*result) = ((*result) & ST25R3916_REG_TX_DRIVER_d_res_mask); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasureAmplitude(uint8_t* result) { - ReturnCode err; - uint8_t reg_opc, reg_mode, reg_conf1, reg_conf2; - - /* Save registers which will be adjusted below */ - st25r3916ReadRegister(ST25R3916_REG_OP_CONTROL, ®_opc); - st25r3916ReadRegister(ST25R3916_REG_MODE, ®_mode); - st25r3916ReadRegister(ST25R3916_REG_RX_CONF1, ®_conf1); - st25r3916ReadRegister(ST25R3916_REG_RX_CONF2, ®_conf2); - - /* Set values as per defaults of DS. These regs/bits influence receiver chain and change amplitude */ - /* Doing so achieves an amplitude comparable over a complete polling cylce */ - st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, (reg_opc & ~ST25R3916_REG_OP_CONTROL_rx_chn)); - st25r3916WriteRegister( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_om_iso14443a | ST25R3916_REG_MODE_targ_init | - ST25R3916_REG_MODE_tr_am_ook | ST25R3916_REG_MODE_nfc_ar_off); - st25r3916WriteRegister( - ST25R3916_REG_RX_CONF1, (reg_conf1 & ~ST25R3916_REG_RX_CONF1_ch_sel_AM)); - st25r3916WriteRegister( - ST25R3916_REG_RX_CONF2, - ((reg_conf2 & ~(ST25R3916_REG_RX_CONF2_demod_mode | ST25R3916_REG_RX_CONF2_amd_sel)) | - ST25R3916_REG_RX_CONF2_amd_sel_peak)); - - /* Perform the actual measurement */ - err = st25r3916MeasureAmplitude(result); - - /* Restore values */ - st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, reg_opc); - st25r3916WriteRegister(ST25R3916_REG_MODE, reg_mode); - st25r3916WriteRegister(ST25R3916_REG_RX_CONF1, reg_conf1); - st25r3916WriteRegister(ST25R3916_REG_RX_CONF2, reg_conf2); - - return err; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasurePhase(uint8_t* result) { - st25r3916MeasurePhase(result); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasureCapacitance(uint8_t* result) { - st25r3916MeasureCapacitance(result); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasurePowerSupply(uint8_t param, uint8_t* result) { - *result = st25r3916MeasurePowerSupply(param); - - return ERR_NONE; -} - -/*******************************************************************************/ -extern uint8_t invalid_size_of_stream_configs - [(sizeof(struct st25r3916StreamConfig) == sizeof(struct iso15693StreamConfig)) ? 1 : (-1)]; diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916.c b/lib/ST25RFAL002/source/st25r3916/st25r3916.c deleted file mode 100644 index 9b6c22bff6..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916.c +++ /dev/null @@ -1,792 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 high level interface - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "st25r3916_led.h" -#include "st25r3916_irq.h" -#include "utils.h" - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ - -#define ST25R3916_SUPPLY_THRESHOLD \ - 3600U /*!< Power supply measure threshold between 3.3V or 5V */ -#define ST25R3916_NRT_MAX \ - 0xFFFFU /*!< Max Register value of NRT */ - -#define ST25R3916_TOUT_MEASURE_VDD \ - 100U /*!< Max duration time of Measure Power Supply command Datasheet: 25us */ -#define ST25R3916_TOUT_MEASURE_AMPLITUDE \ - 10U /*!< Max duration time of Measure Amplitude command Datasheet: 25us */ -#define ST25R3916_TOUT_MEASURE_PHASE \ - 10U /*!< Max duration time of Measure Phase command Datasheet: 25us */ -#define ST25R3916_TOUT_MEASURE_CAPACITANCE \ - 10U /*!< Max duration time of Measure Capacitance command Datasheet: 25us */ -#define ST25R3916_TOUT_CALIBRATE_CAP_SENSOR \ - 4U /*!< Max duration Calibrate Capacitive Sensor command Datasheet: 3ms */ -#define ST25R3916_TOUT_ADJUST_REGULATORS \ - 6U /*!< Max duration time of Adjust Regulators command Datasheet: 5ms */ -#define ST25R3916_TOUT_CA \ - 10U /*!< Max duration time of Collision Avoidance command */ - -#define ST25R3916_TEST_REG_PATTERN \ - 0x33U /*!< Register Read Write test pattern used during selftest */ -#define ST25R3916_TEST_WU_TOUT \ - 12U /*!< Timeout used on WU timer during self test */ -#define ST25R3916_TEST_TMR_TOUT \ - 20U /*!< Timeout used during self test */ -#define ST25R3916_TEST_TMR_TOUT_DELTA \ - 2U /*!< Timeout used during self test */ -#define ST25R3916_TEST_TMR_TOUT_8FC \ - (ST25R3916_TEST_TMR_TOUT * 16950U) /*!< Timeout in 8/fc */ - -/* -****************************************************************************** -* LOCAL CONSTANTS -****************************************************************************** -*/ - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -static uint32_t gST25R3916NRT_64fcs; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/* - ****************************************************************************** - * LOCAL FUNCTION - ****************************************************************************** - */ - -ReturnCode st25r3916ExecuteCommandAndGetResult( - uint8_t cmd, - uint8_t resReg, - uint8_t tout, - uint8_t* result) { - /* Clear and enable Direct Command interrupt */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_DCT); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_DCT); - - st25r3916ExecuteCommand(cmd); - - st25r3916WaitForInterruptsTimed(ST25R3916_IRQ_MASK_DCT, tout); - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_DCT); - - /* After execution read out the result if the pointer is not NULL */ - if(result != NULL) { - st25r3916ReadRegister(resReg, result); - } - - return ERR_NONE; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -ReturnCode st25r3916Initialize(void) { - uint16_t vdd_mV; - ReturnCode ret; - - /* Set default state on the ST25R3916 */ - st25r3916ExecuteCommand(ST25R3916_CMD_SET_DEFAULT); - -#ifndef RFAL_USE_I2C - /* Increase MISO driving level as SPI can go up to 10MHz */ - st25r3916WriteRegister(ST25R3916_REG_IO_CONF2, ST25R3916_REG_IO_CONF2_io_drv_lvl); -#endif /* RFAL_USE_I2C */ - - if(!st25r3916CheckChipID(NULL)) { - platformErrorHandle(); - return ERR_HW_MISMATCH; - } - - st25r3916InitInterrupts(); - st25r3916ledInit(); - - gST25R3916NRT_64fcs = 0; - -#ifndef RFAL_USE_I2C - /* Enable pull downs on MISO line */ - st25r3916SetRegisterBits( - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2)); -#endif /* RFAL_USE_I2C */ - - /* Disable internal overheat protection */ - st25r3916ChangeTestRegisterBits(0x04, 0x10, 0x10); - -#ifdef ST25R_SELFTEST - /****************************************************************************** - * Check communication interface: - * - write a pattern in a register - * - reads back the register value - * - return ERR_IO in case the read value is different - */ - st25r3916WriteRegister(ST25R3916_REG_BIT_RATE, ST25R3916_TEST_REG_PATTERN); - if(!st25r3916CheckReg( - ST25R3916_REG_BIT_RATE, - (ST25R3916_REG_BIT_RATE_rxrate_mask | ST25R3916_REG_BIT_RATE_txrate_mask), - ST25R3916_TEST_REG_PATTERN)) { - platformErrorHandle(); - return ERR_IO; - } - - /* Restore default value */ - st25r3916WriteRegister(ST25R3916_REG_BIT_RATE, 0x00); - - /* - * Check IRQ Handling: - * - use the Wake-up timer to trigger an IRQ - * - wait the Wake-up timer interrupt - * - return ERR_TIMEOUT when the Wake-up timer interrupt is not received - */ - st25r3916WriteRegister( - ST25R3916_REG_WUP_TIMER_CONTROL, - ST25R3916_REG_WUP_TIMER_CONTROL_wur | ST25R3916_REG_WUP_TIMER_CONTROL_wto); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_WT); - st25r3916ExecuteCommand(ST25R3916_CMD_START_WUP_TIMER); - if(st25r3916WaitForInterruptsTimed(ST25R3916_IRQ_MASK_WT, ST25R3916_TEST_WU_TOUT) == 0U) { - platformErrorHandle(); - return ERR_TIMEOUT; - } - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_WT); - st25r3916WriteRegister(ST25R3916_REG_WUP_TIMER_CONTROL, 0U); - /*******************************************************************************/ -#endif /* ST25R_SELFTEST */ - - /* Enable Oscillator and wait until it gets stable */ - ret = st25r3916OscOn(); - if(ret != ERR_NONE) { - platformErrorHandle(); - return ret; - } - - /* Measure VDD and set sup3V bit according to Power supplied */ - vdd_mV = st25r3916MeasureVoltage(ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd); - st25r3916ChangeRegisterBits( - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_sup3V, - ((vdd_mV < ST25R3916_SUPPLY_THRESHOLD) ? ST25R3916_REG_IO_CONF2_sup3V_3V : - ST25R3916_REG_IO_CONF2_sup3V_5V)); - - /* Make sure Transmitter and Receiver are disabled */ - st25r3916TxRxOff(); - -#ifdef ST25R_SELFTEST_TIMER - /****************************************************************************** - * Check SW timer operation : - * - use the General Purpose timer to measure an amount of time - * - test whether an interrupt is seen when less time was given - * - test whether an interrupt is seen when sufficient time was given - */ - - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_GPE); - st25r3916SetStartGPTimer( - (uint16_t)ST25R3916_TEST_TMR_TOUT_8FC, ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger); - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_GPE, (ST25R3916_TEST_TMR_TOUT - ST25R3916_TEST_TMR_TOUT_DELTA)) != - 0U) { - platformErrorHandle(); - return ERR_SYSTEM; - } - - /* Stop all activities to stop the GP timer */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_GPE); - st25r3916SetStartGPTimer( - (uint16_t)ST25R3916_TEST_TMR_TOUT_8FC, ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger); - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_GPE, (ST25R3916_TEST_TMR_TOUT + ST25R3916_TEST_TMR_TOUT_DELTA)) == - 0U) { - platformErrorHandle(); - return ERR_SYSTEM; - } - - /* Stop all activities to stop the GP timer */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - /*******************************************************************************/ -#endif /* ST25R_SELFTEST_TIMER */ - - /* After reset all interrupts are enabled, so disable them at first */ - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - - /* And clear them, just to be sure */ - st25r3916ClearInterrupts(); - - return ERR_NONE; -} - -/*******************************************************************************/ -void st25r3916Deinitialize(void) { - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - - /* Disable Tx and Rx, Keep OSC On */ - st25r3916TxRxOff(); - - return; -} - -/*******************************************************************************/ -ReturnCode st25r3916OscOn(void) { - /* Check if oscillator is already turned on and stable */ - /* Use ST25R3916_REG_OP_CONTROL_en instead of ST25R3916_REG_AUX_DISPLAY_osc_ok to be on the safe side */ - if(!st25r3916CheckReg( - ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en, ST25R3916_REG_OP_CONTROL_en)) { - /* Clear any eventual previous oscillator IRQ */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_OSC); - - /* Enable oscillator frequency stable interrupt */ - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_OSC); - - /* Enable oscillator and regulator output */ - st25r3916SetRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en); - - /* Wait for the oscillator interrupt */ - st25r3916WaitForInterruptsTimed(ST25R3916_IRQ_MASK_OSC, ST25R3916_TOUT_OSC_STABLE); - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_OSC); - } - - if(!st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_osc_ok, - ST25R3916_REG_AUX_DISPLAY_osc_ok)) { - return ERR_SYSTEM; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -uint8_t st25r3916MeasurePowerSupply(uint8_t mpsv) { - uint8_t result; - - /* Set the source of direct command: Measure Power Supply Voltage */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_mpsv_mask, mpsv); - - /* Execute command: Measure Power Supply Voltage */ - st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_VDD, ST25R3916_REG_AD_RESULT, ST25R3916_TOUT_MEASURE_VDD, &result); - - return result; -} - -/*******************************************************************************/ -uint16_t st25r3916MeasureVoltage(uint8_t mpsv) { - uint8_t result; - uint16_t mV; - - result = st25r3916MeasurePowerSupply(mpsv); - - /* Convert cmd output into mV (each step represents 23.4 mV )*/ - mV = ((uint16_t)result) * 23U; - mV += (((((uint16_t)result) * 4U) + 5U) / 10U); - - return mV; -} - -/*******************************************************************************/ -ReturnCode st25r3916AdjustRegulators(uint16_t* result_mV) { - uint8_t result; - - /* Reset logic and set regulated voltages to be defined by result of Adjust Regulators command */ - st25r3916SetRegisterBits( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); - st25r3916ClrRegisterBits( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); - - /* Execute Adjust regulators cmd and retrieve result */ - st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_ADJUST_REGULATORS, - ST25R3916_REG_REGULATOR_RESULT, - ST25R3916_TOUT_ADJUST_REGULATORS, - &result); - - /* Calculate result in mV */ - result >>= ST25R3916_REG_REGULATOR_RESULT_reg_shift; - - if(result_mV != NULL) { - if(st25r3916CheckReg( - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_sup3V, - ST25R3916_REG_IO_CONF2_sup3V)) { - result = MIN( - result, - (uint8_t)(result - 5U)); /* In 3.3V mode [0,4] are not used */ - *result_mV = 2400U; /* Minimum regulated voltage 2.4V in case of 3.3V supply */ - } else { - *result_mV = 3600U; /* Minimum regulated voltage 3.6V in case of 5V supply */ - } - - *result_mV += - (uint16_t)result * 100U; /* 100mV steps in both 3.3V and 5V supply */ - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916MeasureAmplitude(uint8_t* result) { - return st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_AMPLITUDE, - ST25R3916_REG_AD_RESULT, - ST25R3916_TOUT_MEASURE_AMPLITUDE, - result); -} - -/*******************************************************************************/ -ReturnCode st25r3916MeasurePhase(uint8_t* result) { - return st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_PHASE, ST25R3916_REG_AD_RESULT, ST25R3916_TOUT_MEASURE_PHASE, result); -} - -/*******************************************************************************/ -ReturnCode st25r3916MeasureCapacitance(uint8_t* result) { - return st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_CAPACITANCE, - ST25R3916_REG_AD_RESULT, - ST25R3916_TOUT_MEASURE_CAPACITANCE, - result); -} - -/*******************************************************************************/ -ReturnCode st25r3916CalibrateCapacitiveSensor(uint8_t* result) { - ReturnCode ret; - uint8_t res; - - /* Clear Manual calibration values to enable automatic calibration mode */ - st25r3916ClrRegisterBits( - ST25R3916_REG_CAP_SENSOR_CONTROL, ST25R3916_REG_CAP_SENSOR_CONTROL_cs_mcal_mask); - - /* Execute automatic calibration */ - ret = st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_CALIBRATE_C_SENSOR, - ST25R3916_REG_CAP_SENSOR_RESULT, - ST25R3916_TOUT_CALIBRATE_CAP_SENSOR, - &res); - - /* Check whether the calibration was successull */ - if(((res & ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_end) != - ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_end) || - ((res & ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_err) == - ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_err) || - (ret != ERR_NONE)) { - return ERR_IO; - } - - if(result != NULL) { - (*result) = (uint8_t)(res >> ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_shift); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916SetBitrate(uint8_t txrate, uint8_t rxrate) { - uint8_t reg; - - st25r3916ReadRegister(ST25R3916_REG_BIT_RATE, ®); - if(rxrate != ST25R3916_BR_DO_NOT_SET) { - if(rxrate > ST25R3916_BR_848) { - return ERR_PARAM; - } - - reg = (uint8_t)(reg & ~ST25R3916_REG_BIT_RATE_rxrate_mask); /* MISRA 10.3 */ - reg |= rxrate << ST25R3916_REG_BIT_RATE_rxrate_shift; - } - if(txrate != ST25R3916_BR_DO_NOT_SET) { - if(txrate > ST25R3916_BR_6780) { - return ERR_PARAM; - } - - reg = (uint8_t)(reg & ~ST25R3916_REG_BIT_RATE_txrate_mask); /* MISRA 10.3 */ - reg |= txrate << ST25R3916_REG_BIT_RATE_txrate_shift; - } - return st25r3916WriteRegister(ST25R3916_REG_BIT_RATE, reg); -} - -/*******************************************************************************/ -ReturnCode st25r3916PerformCollisionAvoidance( - uint8_t FieldONCmd, - uint8_t pdThreshold, - uint8_t caThreshold, - uint8_t nTRFW) { - uint8_t treMask; - uint32_t irqs; - ReturnCode err; - - if((FieldONCmd != ST25R3916_CMD_INITIAL_RF_COLLISION) && - (FieldONCmd != ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) { - return ERR_PARAM; - } - - err = ERR_INTERNAL; - - /* Check if new thresholds are to be applied */ - if((pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) || - (caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET)) { - treMask = 0; - - if(pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) { - treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask; - } - - if(caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) { - treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask; - } - - /* Set Detection Threshold and|or Collision Avoidance Threshold */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - treMask, - (pdThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask) | - (caThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask)); - } - - /* Set n x TRFW */ - st25r3916ChangeRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, nTRFW); - - /*******************************************************************************/ - /* Enable and clear CA specific interrupts and execute command */ - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - st25r3916EnableInterrupts( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - - st25r3916ExecuteCommand(FieldONCmd); - - /*******************************************************************************/ - /* Wait for initial APON interrupt, indicating anticollision avoidance done and ST25R3916's - * field is now on, or a CAC indicating a collision */ - irqs = st25r3916WaitForInterruptsTimed( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_APON), ST25R3916_TOUT_CA); - - if((ST25R3916_IRQ_MASK_CAC & irqs) != 0U) /* Collision occurred */ - { - err = ERR_RF_COLLISION; - } else if((ST25R3916_IRQ_MASK_APON & irqs) != 0U) { - /* After APON wait for CAT interrupt, indication field was switched on minimum guard time has been fulfilled */ - irqs = st25r3916WaitForInterruptsTimed((ST25R3916_IRQ_MASK_CAT), ST25R3916_TOUT_CA); - - if((ST25R3916_IRQ_MASK_CAT & irqs) != 0U) /* No Collision detected, Field On */ - { - err = ERR_NONE; - } - } else { - /* MISRA 15.7 - Empty else */ - } - - /* Clear any previous External Field events and disable CA specific interrupts */ - st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_EON)); - st25r3916DisableInterrupts( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - - return err; -} - -/*******************************************************************************/ -void st25r3916SetNumTxBits(uint16_t nBits) { - st25r3916WriteRegister(ST25R3916_REG_NUM_TX_BYTES2, (uint8_t)((nBits >> 0) & 0xFFU)); - st25r3916WriteRegister(ST25R3916_REG_NUM_TX_BYTES1, (uint8_t)((nBits >> 8) & 0xFFU)); -} - -/*******************************************************************************/ -uint16_t st25r3916GetNumFIFOBytes(void) { - uint8_t reg; - uint16_t result; - - st25r3916ReadRegister(ST25R3916_REG_FIFO_STATUS2, ®); - reg = - ((reg & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_b_shift); - result = ((uint16_t)reg << 8); - - st25r3916ReadRegister(ST25R3916_REG_FIFO_STATUS1, ®); - result |= (((uint16_t)reg) & 0x00FFU); - - return result; -} - -/*******************************************************************************/ -uint8_t st25r3916GetNumFIFOLastBits(void) { - uint8_t reg; - - st25r3916ReadRegister(ST25R3916_REG_FIFO_STATUS2, ®); - - return ( - (reg & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_lb_shift); -} - -/*******************************************************************************/ -uint32_t st25r3916GetNoResponseTime(void) { - return gST25R3916NRT_64fcs; -} - -/*******************************************************************************/ -ReturnCode st25r3916SetNoResponseTime(uint32_t nrt_64fcs) { - ReturnCode err; - uint8_t nrt_step; - uint32_t tmpNRT; - - tmpNRT = nrt_64fcs; /* MISRA 17.8 */ - err = ERR_NONE; - - gST25R3916NRT_64fcs = tmpNRT; /* Store given NRT value in 64/fc into local var */ - nrt_step = - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_step_64fc; /* Set default NRT in steps of 64/fc */ - - if(tmpNRT > ST25R3916_NRT_MAX) /* Check if the given NRT value fits using 64/fc steps */ - { - nrt_step = - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_step_4096_fc; /* If not, change NRT set to 4096/fc */ - tmpNRT = ((tmpNRT + 63U) / 64U); /* Calculate number of steps in 4096/fc */ - - if(tmpNRT > ST25R3916_NRT_MAX) /* Check if the NRT value fits using 64/fc steps */ - { - tmpNRT = ST25R3916_NRT_MAX; /* Assign the maximum possible */ - err = ERR_PARAM; /* Signal parameter error */ - } - gST25R3916NRT_64fcs = (64U * tmpNRT); - } - - /* Set the ST25R3916 NRT step units and the value */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_step, nrt_step); - st25r3916WriteRegister(ST25R3916_REG_NO_RESPONSE_TIMER1, (uint8_t)(tmpNRT >> 8U)); - st25r3916WriteRegister(ST25R3916_REG_NO_RESPONSE_TIMER2, (uint8_t)(tmpNRT & 0xFFU)); - - return err; -} - -/*******************************************************************************/ -ReturnCode st25r3916SetStartNoResponseTimer(uint32_t nrt_64fcs) { - ReturnCode err; - - err = st25r3916SetNoResponseTime(nrt_64fcs); - if(err == ERR_NONE) { - st25r3916ExecuteCommand(ST25R3916_CMD_START_NO_RESPONSE_TIMER); - } - - return err; -} - -/*******************************************************************************/ -void st25r3916SetGPTime(uint16_t gpt_8fcs) { - st25r3916WriteRegister(ST25R3916_REG_GPT1, (uint8_t)(gpt_8fcs >> 8)); - st25r3916WriteRegister(ST25R3916_REG_GPT2, (uint8_t)(gpt_8fcs & 0xFFU)); -} - -/*******************************************************************************/ -ReturnCode st25r3916SetStartGPTimer(uint16_t gpt_8fcs, uint8_t trigger_source) { - st25r3916SetGPTime(gpt_8fcs); - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_mask, - trigger_source); - - /* If there's no trigger source, start GPT immediately */ - if(trigger_source == ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger) { - st25r3916ExecuteCommand(ST25R3916_CMD_START_GP_TIMER); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -bool st25r3916CheckChipID(uint8_t* rev) { - uint8_t ID; - - ID = 0; - st25r3916ReadRegister(ST25R3916_REG_IC_IDENTITY, &ID); - - /* Check if IC Identity Register contains ST25R3916's IC type code */ - if((ID & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != - ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) { - return false; - } - - if(rev != NULL) { - *rev = (ID & ST25R3916_REG_IC_IDENTITY_ic_rev_mask); - } - - return true; -} - -/*******************************************************************************/ -ReturnCode st25r3916GetRegsDump(t_st25r3916Regs* regDump) { - uint8_t regIt; - - if(regDump == NULL) { - return ERR_PARAM; - } - - /* Dump Registers on space A */ - for(regIt = ST25R3916_REG_IO_CONF1; regIt <= ST25R3916_REG_IC_IDENTITY; regIt++) { - st25r3916ReadRegister(regIt, ®Dump->RsA[regIt]); - } - - regIt = 0; - - /* Read non-consecutive Registers on space B */ - st25r3916ReadRegister(ST25R3916_REG_EMD_SUP_CONF, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_SUBC_START_TIME, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_P2P_RX_CONF, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_CORR_CONF1, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_CORR_CONF2, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_SQUELCH_TIMER, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_FIELD_ON_GT, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_AUX_MOD, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_TX_DRIVER_TIMING, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_RES_AM_MOD, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_TX_DRIVER_STATUS, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_REGULATOR_RESULT, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_OVERSHOOT_CONF1, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_OVERSHOOT_CONF2, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_UNDERSHOOT_CONF1, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_UNDERSHOOT_CONF2, ®Dump->RsB[regIt++]); - - return ERR_NONE; -} - -/*******************************************************************************/ -bool st25r3916IsCmdValid(uint8_t cmd) { - if(!((cmd >= ST25R3916_CMD_SET_DEFAULT) && (cmd <= ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) && - !((cmd >= ST25R3916_CMD_GOTO_SENSE) && (cmd <= ST25R3916_CMD_GOTO_SLEEP)) && - !((cmd >= ST25R3916_CMD_MASK_RECEIVE_DATA) && (cmd <= ST25R3916_CMD_MEASURE_AMPLITUDE)) && - !((cmd >= ST25R3916_CMD_RESET_RXGAIN) && (cmd <= ST25R3916_CMD_ADJUST_REGULATORS)) && - !((cmd >= ST25R3916_CMD_CALIBRATE_DRIVER_TIMING) && - (cmd <= ST25R3916_CMD_START_PPON2_TIMER)) && - (cmd != ST25R3916_CMD_SPACE_B_ACCESS) && (cmd != ST25R3916_CMD_STOP_NRT)) { - return false; - } - return true; -} - -/*******************************************************************************/ -ReturnCode st25r3916StreamConfigure(const struct st25r3916StreamConfig* config) { - uint8_t smd; - uint8_t mode; - - smd = 0; - - if(config->useBPSK != 0U) { - mode = ST25R3916_REG_MODE_om_bpsk_stream; - if((config->din < 2U) || (config->din > 4U)) /* not in fc/4 .. fc/16 */ - { - return ERR_PARAM; - } - smd |= ((4U - config->din) << ST25R3916_REG_STREAM_MODE_scf_shift); - } else { - mode = ST25R3916_REG_MODE_om_subcarrier_stream; - if((config->din < 3U) || (config->din > 6U)) /* not in fc/8 .. fc/64 */ - { - return ERR_PARAM; - } - smd |= ((6U - config->din) << ST25R3916_REG_STREAM_MODE_scf_shift); - if(config->report_period_length == 0U) { - return ERR_PARAM; - } - } - - if((config->dout < 1U) || (config->dout > 7U)) /* not in fc/2 .. fc/128 */ - { - return ERR_PARAM; - } - smd |= (7U - config->dout) << ST25R3916_REG_STREAM_MODE_stx_shift; - - if(config->report_period_length > 3U) { - return ERR_PARAM; - } - smd |= (config->report_period_length << ST25R3916_REG_STREAM_MODE_scp_shift); - - st25r3916WriteRegister(ST25R3916_REG_STREAM_MODE, smd); - st25r3916ChangeRegisterBits(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_mask, mode); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916GetRSSI(uint16_t* amRssi, uint16_t* pmRssi) { - /*******************************************************************************/ - /* MISRA 8.9 An object should be defined at block scope if its identifier only appears in a single function */ - /*< ST25R3916 RSSI Display Reg values: 0 1 2 3 4 5 6 7 8 9 a b c d e f */ - static const uint16_t st25r3916Rssi2mV[] = { - 0, 20, 27, 37, 52, 72, 99, 136, 190, 262, 357, 500, 686, 950, 1150, 1150}; - - /* ST25R3916 2/3 stage gain reduction [dB] 0 0 0 0 0 3 6 9 12 15 18 na na na na na */ - static const uint16_t st25r3916Gain2Percent[] = { - 100, 100, 100, 100, 100, 141, 200, 281, 398, 562, 794, 1, 1, 1, 1, 1}; - /*******************************************************************************/ - - uint8_t rssi; - uint8_t gainRed; - - st25r3916ReadRegister(ST25R3916_REG_RSSI_RESULT, &rssi); - st25r3916ReadRegister(ST25R3916_REG_GAIN_RED_STATE, &gainRed); - - if(amRssi != NULL) { - *amRssi = - (uint16_t)(((uint32_t)st25r3916Rssi2mV[(rssi >> ST25R3916_REG_RSSI_RESULT_rssi_am_shift)] * (uint32_t)st25r3916Gain2Percent[(gainRed >> ST25R3916_REG_GAIN_RED_STATE_gs_am_shift)]) / 100U); - } - - if(pmRssi != NULL) { - *pmRssi = - (uint16_t)(((uint32_t)st25r3916Rssi2mV[(rssi & ST25R3916_REG_RSSI_RESULT_rssi_pm_mask)] * (uint32_t)st25r3916Gain2Percent[(gainRed & ST25R3916_REG_GAIN_RED_STATE_gs_pm_mask)]) / 100U); - } - - return ERR_NONE; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916.h b/lib/ST25RFAL002/source/st25r3916/st25r3916.h deleted file mode 100644 index 2ba202d86d..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916.h +++ /dev/null @@ -1,669 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 high level interface - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_Driver - * \brief RFAL ST25R3916 Driver - * @{ - * - */ - -#ifndef ST25R3916_H -#define ST25R3916_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "st25r3916_com.h" - -/* -****************************************************************************** -* GLOBAL DATATYPES -****************************************************************************** -*/ - -/*! Struct to represent all regs on ST25R3916 */ -typedef struct { - uint8_t RsA[( - ST25R3916_REG_IC_IDENTITY + 1U)]; /*!< Registers contained on ST25R3916 space A (Rs-A) */ - uint8_t - RsB[ST25R3916_SPACE_B_REG_LEN]; /*!< Registers contained on ST25R3916 space B (Rs-B) */ -} t_st25r3916Regs; - -/*! Parameters how the stream mode should work */ -struct st25r3916StreamConfig { - uint8_t useBPSK; /*!< 0: subcarrier, 1:BPSK */ - uint8_t din; /*!< Divider for the in subcarrier frequency: fc/2^din */ - uint8_t dout; /*!< Divider for the in subcarrier frequency fc/2^dout */ - uint8_t report_period_length; /*!< Length of the reporting period 2^report_period_length*/ -}; - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/* ST25R3916 direct commands */ -#define ST25R3916_CMD_SET_DEFAULT \ - 0xC1U /*!< Puts the chip in default state (same as after power-up) */ -#define ST25R3916_CMD_STOP 0xC2U /*!< Stops all activities and clears FIFO */ -#define ST25R3916_CMD_TRANSMIT_WITH_CRC \ - 0xC4U /*!< Transmit with CRC */ -#define ST25R3916_CMD_TRANSMIT_WITHOUT_CRC \ - 0xC5U /*!< Transmit without CRC */ -#define ST25R3916_CMD_TRANSMIT_REQA \ - 0xC6U /*!< Transmit REQA */ -#define ST25R3916_CMD_TRANSMIT_WUPA \ - 0xC7U /*!< Transmit WUPA */ -#define ST25R3916_CMD_INITIAL_RF_COLLISION \ - 0xC8U /*!< NFC transmit with Initial RF Collision Avoidance */ -#define ST25R3916_CMD_RESPONSE_RF_COLLISION_N \ - 0xC9U /*!< NFC transmit with Response RF Collision Avoidance */ -#define ST25R3916_CMD_GOTO_SENSE \ - 0xCDU /*!< Passive target logic to Sense/Idle state */ -#define ST25R3916_CMD_GOTO_SLEEP \ - 0xCEU /*!< Passive target logic to Sleep/Halt state */ -#define ST25R3916_CMD_MASK_RECEIVE_DATA \ - 0xD0U /*!< Mask receive data */ -#define ST25R3916_CMD_UNMASK_RECEIVE_DATA \ - 0xD1U /*!< Unmask receive data */ -#define ST25R3916_CMD_AM_MOD_STATE_CHANGE \ - 0xD2U /*!< AM Modulation state change */ -#define ST25R3916_CMD_MEASURE_AMPLITUDE \ - 0xD3U /*!< Measure signal amplitude on RFI inputs */ -#define ST25R3916_CMD_RESET_RXGAIN \ - 0xD5U /*!< Reset RX Gain */ -#define ST25R3916_CMD_ADJUST_REGULATORS \ - 0xD6U /*!< Adjust regulators */ -#define ST25R3916_CMD_CALIBRATE_DRIVER_TIMING \ - 0xD8U /*!< Starts the sequence to adjust the driver timing */ -#define ST25R3916_CMD_MEASURE_PHASE \ - 0xD9U /*!< Measure phase between RFO and RFI signal */ -#define ST25R3916_CMD_CLEAR_RSSI \ - 0xDAU /*!< Clear RSSI bits and restart the measurement */ -#define ST25R3916_CMD_CLEAR_FIFO \ - 0xDBU /*!< Clears FIFO, Collision and IRQ status */ -#define ST25R3916_CMD_TRANSPARENT_MODE \ - 0xDCU /*!< Transparent mode */ -#define ST25R3916_CMD_CALIBRATE_C_SENSOR \ - 0xDDU /*!< Calibrate the capacitive sensor */ -#define ST25R3916_CMD_MEASURE_CAPACITANCE \ - 0xDEU /*!< Measure capacitance */ -#define ST25R3916_CMD_MEASURE_VDD \ - 0xDFU /*!< Measure power supply voltage */ -#define ST25R3916_CMD_START_GP_TIMER \ - 0xE0U /*!< Start the general purpose timer */ -#define ST25R3916_CMD_START_WUP_TIMER \ - 0xE1U /*!< Start the wake-up timer */ -#define ST25R3916_CMD_START_MASK_RECEIVE_TIMER \ - 0xE2U /*!< Start the mask-receive timer */ -#define ST25R3916_CMD_START_NO_RESPONSE_TIMER \ - 0xE3U /*!< Start the no-response timer */ -#define ST25R3916_CMD_START_PPON2_TIMER \ - 0xE4U /*!< Start PPon2 timer */ -#define ST25R3916_CMD_STOP_NRT \ - 0xE8U /*!< Stop No Response Timer */ -#define ST25R3916_CMD_SPACE_B_ACCESS \ - 0xFBU /*!< Enable R/W access to the test registers */ -#define ST25R3916_CMD_TEST_ACCESS \ - 0xFCU /*!< Enable R/W access to the test registers */ - -#define ST25R3916_THRESHOLD_DO_NOT_SET \ - 0xFFU /*!< Indicates not to change this Threshold */ - -#define ST25R3916_BR_DO_NOT_SET \ - 0xFFU /*!< Indicates not to change this Bit Rate */ -#define ST25R3916_BR_106 0x00U /*!< ST25R3916 Bit Rate 106 kbit/s (fc/128) */ -#define ST25R3916_BR_212 0x01U /*!< ST25R3916 Bit Rate 212 kbit/s (fc/64) */ -#define ST25R3916_BR_424 0x02U /*!< ST25R3916 Bit Rate 424 kbit/s (fc/32) */ -#define ST25R3916_BR_848 0x03U /*!< ST25R3916 Bit Rate 848 kbit/s (fc/16) */ -#define ST25R3916_BR_1695 0x04U /*!< ST25R3916 Bit Rate 1696 kbit/s (fc/8) */ -#define ST25R3916_BR_3390 0x05U /*!< ST25R3916 Bit Rate 3390 kbit/s (fc/4) */ -#define ST25R3916_BR_6780 0x07U /*!< ST25R3916 Bit Rate 6780 kbit/s (fc/2) */ - -#define ST25R3916_FIFO_DEPTH 512U /*!< Depth of FIFO */ -#define ST25R3916_TOUT_OSC_STABLE \ - 10U /*!< Max timeout for Oscillator to get stable DS: 700us */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Enables the Transmitter (Field On) and Receiver */ -#define st25r3916TxRxOn() \ - st25r3916SetRegisterBits( \ - ST25R3916_REG_OP_CONTROL, \ - (ST25R3916_REG_OP_CONTROL_rx_en | ST25R3916_REG_OP_CONTROL_tx_en)) - -/*! Disables the Transmitter (Field Off) and Receiver */ -#define st25r3916TxRxOff() \ - st25r3916ClrRegisterBits( \ - ST25R3916_REG_OP_CONTROL, \ - (ST25R3916_REG_OP_CONTROL_rx_en | ST25R3916_REG_OP_CONTROL_tx_en)) - -/*! Disables the Transmitter (Field Off) */ -#define st25r3916TxOff() \ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_tx_en) - -/*! Checks if General Purpose Timer is still running by reading gpt_on flag */ -#define st25r3916IsGPTRunning() \ - st25r3916CheckReg( \ - ST25R3916_REG_NFCIP1_BIT_RATE, \ - ST25R3916_REG_NFCIP1_BIT_RATE_gpt_on, \ - ST25R3916_REG_NFCIP1_BIT_RATE_gpt_on) - -/*! Checks if External Filed is detected by reading ST25R3916 External Field Detector output */ -#define st25r3916IsExtFieldOn() \ - st25r3916CheckReg( \ - ST25R3916_REG_AUX_DISPLAY, \ - ST25R3916_REG_AUX_DISPLAY_efd_o, \ - ST25R3916_REG_AUX_DISPLAY_efd_o) - -/*! Checks if Transmitter is enabled (Field On) */ -#define st25r3916IsTxEnabled() \ - st25r3916CheckReg( \ - ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_tx_en, ST25R3916_REG_OP_CONTROL_tx_en) - -/*! Checks if NRT is in EMV mode */ -#define st25r3916IsNRTinEMV() \ - st25r3916CheckReg( \ - ST25R3916_REG_TIMER_EMV_CONTROL, \ - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv, \ - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv_on) - -/*! Checks if last FIFO byte is complete */ -#define st25r3916IsLastFIFOComplete() \ - st25r3916CheckReg(ST25R3916_REG_FIFO_STATUS2, ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask, 0) - -/*! Checks if the Oscillator is enabled */ -#define st25r3916IsOscOn() \ - st25r3916CheckReg( \ - ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en, ST25R3916_REG_OP_CONTROL_en) - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialise ST25R3916 driver - * - * This function initialises the ST25R3916 driver. - * - * \return ERR_NONE : Operation successful - * \return ERR_HW_MISMATCH : Expected HW do not match or communication error - * \return ERR_IO : Error during communication selftest. Check communication interface - * \return ERR_TIMEOUT : Timeout during IRQ selftest. Check IRQ handling - * \return ERR_SYSTEM : Failure during oscillator activation or timer error - * - ***************************************************************************** - */ -ReturnCode st25r3916Initialize(void); - -/*! - ***************************************************************************** - * \brief Deinitialize ST25R3916 driver - * - * Calling this function deinitializes the ST25R3916 driver. - * - ***************************************************************************** - */ -void st25r3916Deinitialize(void); - -/*! - ***************************************************************************** - * \brief Turn on Oscillator and Regulator - * - * This function turn on oscillator and regulator and waits for the - * oscillator to become stable - * - * \return ERR_SYSTEM : Failure dusring Oscillator activation - * \return ERR_NONE : No error, Oscillator is active and stable, Regulator is on - * - ***************************************************************************** - */ -ReturnCode st25r3916OscOn(void); - -/*! - ***************************************************************************** - * \brief Sets the bitrate - * - * This function sets the bitrates for rx and tx - * - * \param txrate : speed is 2^txrate * 106 kb/s - * 0xff : don't set txrate (ST25R3916_BR_DO_NOT_SET) - * \param rxrate : speed is 2^rxrate * 106 kb/s - * 0xff : don't set rxrate (ST25R3916_BR_DO_NOT_SET) - * - * \return ERR_PARAM: At least one bit rate was invalid - * \return ERR_NONE : No error, both bit rates were set - * - ***************************************************************************** - */ -ReturnCode st25r3916SetBitrate(uint8_t txrate, uint8_t rxrate); - -/*! - ***************************************************************************** - * \brief Adjusts supply regulators according to the current supply voltage - * - * This function the power level is measured in maximum load conditions and - * the regulated voltage reference is set to 250mV below this level. - * Execution of this function lasts around 5ms. - * - * The regulated voltages will be set to the result of Adjust Regulators - * - * \param [out] result_mV : Result of calibration in milliVolts - * - * \return ERR_IO : Error during communication with ST25R3916 - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916AdjustRegulators(uint16_t* result_mV); - -/*! - ***************************************************************************** - * \brief Measure Amplitude - * - * This function measured the amplitude on the RFI inputs and stores the - * result in parameter \a result. - * - * \param[out] result: result of RF measurement. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916MeasureAmplitude(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Power Supply - * - * This function executes Measure Power Supply and returns the raw value - * - * \param[in] mpsv : one of ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_rf - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_a - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_d - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_am - * - * \return the measured voltage in raw format. - * - ***************************************************************************** - */ -uint8_t st25r3916MeasurePowerSupply(uint8_t mpsv); - -/*! - ***************************************************************************** - * \brief Measure Voltage - * - * This function measures the voltage on one of VDD and VDD_* and returns - * the result in mV - * - * \param[in] mpsv : one of ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_rf - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_a - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_d - * or ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_am - * - * \return the measured voltage in mV - * - ***************************************************************************** - */ -uint16_t st25r3916MeasureVoltage(uint8_t mpsv); - -/*! - ***************************************************************************** - * \brief Measure Phase - * - * This function performs a Phase measurement. - * The result is stored in the \a result parameter. - * - * \param[out] result: 8 bit long result of the measurement. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916MeasurePhase(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Capacitance - * - * This function performs the capacitance measurement and stores the - * result in parameter \a result. - * - * \param[out] result: 8 bit long result of RF measurement. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916MeasureCapacitance(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Calibrates Capacitive Sensor - * - * This function performs automatic calibration of the capacitive sensor - * and stores the result in parameter \a result. - * - * \warning To avoid interference with Xtal oscillator and reader magnetic - * field, it is strongly recommended to perform calibration - * in Power-down mode only. - * This method does not modify the Oscillator nor transmitter state, - * these should be configured before by user. - * - * \param[out] result: 5 bit long result of the calibration. - * Binary weighted, step 0.1 pF, max 3.1 pF - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_IO : The calibration was not successful - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916CalibrateCapacitiveSensor(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Get NRT time - * - * This returns the last value set on the NRT - * - * \warning it does not read chip register, just the sw var that contains the - * last value set before - * - * \return the value of the NRT in 64/fc - */ -uint32_t st25r3916GetNoResponseTime(void); - -/*! - ***************************************************************************** - * \brief Set NRT time - * - * This function sets the No Response Time with the given value - * - * \param [in] nrt_64fcs : no response time in steps of 64/fc (4.72us) - * - * \return ERR_PARAM : Invalid parameter (time is too large) - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916SetNoResponseTime(uint32_t nrt_64fcs); - -/*! - ***************************************************************************** - * \brief Set and Start NRT - * - * This function sets the No Response Time with the given value and - * immediately starts it - * Used when needs to add more time before timeout without performing Tx - * - * \param [in] nrt_64fcs : no response time in steps of 64/fc (4.72us) - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916SetStartNoResponseTimer(uint32_t nrt_64fcs); - -/*! - ***************************************************************************** - * \brief Set GPT time - * - * This function sets the General Purpose Timer time registers - * - * \param [in] gpt_8fcs : general purpose timer timeout in steps of 8/fc (590ns) - * - ***************************************************************************** - */ -void st25r3916SetGPTime(uint16_t gpt_8fcs); - -/*! - ***************************************************************************** - * \brief Set and Start GPT - * - * This function sets the General Purpose Timer with the given timeout and - * immediately starts it ONLY if the trigger source is not set to none. - * - * \param [in] gpt_8fcs : general purpose timer timeout in steps of8/fc (590ns) - * \param [in] trigger_source : no trigger, start of Rx, end of Rx, end of Tx in NFC mode - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916SetStartGPTimer(uint16_t gpt_8fcs, uint8_t trigger_source); - -/*! - ***************************************************************************** - * \brief Sets the number Tx Bits - * - * Sets ST25R3916 internal registers with correct number of complete bytes and - * bits to be sent - * - * \param [in] nBits : number of bits to be set/transmitted - * - ***************************************************************************** - */ -void st25r3916SetNumTxBits(uint16_t nBits); - -/*! - ***************************************************************************** - * \brief Get amount of bytes in FIFO - * - * Gets the number of bytes currently in the FIFO - * - * \return the number of bytes currently in the FIFO - * - ***************************************************************************** - */ -uint16_t st25r3916GetNumFIFOBytes(void); - -/*! - ***************************************************************************** - * \brief Get amount of bits of the last FIFO byte if incomplete - * - * Gets the number of bits of the last FIFO byte if incomplete - * - * \return the number of bits of the last FIFO byte if incomplete, 0 if - * the last byte is complete - * - ***************************************************************************** - */ -uint8_t st25r3916GetNumFIFOLastBits(void); - -/*! - ***************************************************************************** - * \brief Perform Collision Avoidance - * - * Performs Collision Avoidance with the given threshold and with the - * n number of TRFW - * - * \param[in] FieldONCmd : Field ON command to be executed ST25R3916_CMD_INITIAL_RF_COLLISION - * or ST25R3916_CMD_RESPONSE_RF_COLLISION_N - * \param[in] pdThreshold : Peer Detection Threshold (ST25R3916_REG_FIELD_THRESHOLD_trg_xx) - * 0xff : don't set Threshold (ST25R3916_THRESHOLD_DO_NOT_SET) - * \param[in] caThreshold : Collision Avoidance Threshold (ST25R3916_REG_FIELD_THRESHOLD_rfe_xx) - * 0xff : don't set Threshold (ST25R3916_THRESHOLD_DO_NOT_SET) - * \param[in] nTRFW : Number of TRFW - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_NONE : No collision detected - * - ***************************************************************************** - */ -ReturnCode st25r3916PerformCollisionAvoidance( - uint8_t FieldONCmd, - uint8_t pdThreshold, - uint8_t caThreshold, - uint8_t nTRFW); - -/*! - ***************************************************************************** - * \brief Check Identity - * - * Checks if the chip ID is as expected. - * - * 5 bit IC type code for ST25R3916: 00101 - * The 3 lsb contain the IC revision code - * - * \param[out] rev : the IC revision code - * - * \return true when IC type is as expected - * \return false otherwise - */ -bool st25r3916CheckChipID(uint8_t* rev); - -/*! - ***************************************************************************** - * \brief Retrieves all internal registers from ST25R3916 - * - * \param[out] regDump : pointer to the struct/buffer where the reg dump - * will be written - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode st25r3916GetRegsDump(t_st25r3916Regs* regDump); - -/*! - ***************************************************************************** - * \brief Check if command is valid - * - * Checks if the given command is a valid ST25R3916 command - * - * \param[in] cmd: Command to check - * - * \return true if is a valid command - * \return false otherwise - * - ***************************************************************************** - */ -bool st25r3916IsCmdValid(uint8_t cmd); - -/*! - ***************************************************************************** - * \brief Configure the stream mode of ST25R3916 - * - * This function initializes the stream with the given parameters - * - * \param[in] config : all settings for bitrates, type, etc. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error, stream mode driver initialized - * - ***************************************************************************** - */ -ReturnCode st25r3916StreamConfigure(const struct st25r3916StreamConfig* config); - -/*! - ***************************************************************************** - * \brief Executes a direct command and returns the result - * - * This function executes the direct command given by \a cmd waits for - * \a sleeptime for I_dct and returns the result read from register \a resreg. - * The value of cmd is not checked. - * - * \param[in] cmd : direct command to execute - * \param[in] resReg: address of the register containing the result - * \param[in] tout : time in milliseconds to wait before reading the result - * \param[out] result: result - * - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode - st25r3916ExecuteCommandAndGetResult(uint8_t cmd, uint8_t resReg, uint8_t tout, uint8_t* result); - -/*! - ***************************************************************************** - * \brief Gets the RSSI values - * - * This function gets the RSSI value of the previous reception taking into - * account the gain reductions that were used. - * RSSI value for both AM and PM channel can be retrieved. - * - * \param[out] amRssi: the RSSI on the AM channel expressed in mV - * \param[out] pmRssi: the RSSI on the PM channel expressed in mV - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916GetRSSI(uint16_t* amRssi, uint16_t* pmRssi); -#endif /* ST25R3916_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c deleted file mode 100644 index 234bb2e995..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c +++ /dev/null @@ -1,366 +0,0 @@ -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file st25r3916_aat.c - * - * \author - * - * \brief ST25R3916 Antenna Tuning - * - * The antenna tuning algorithm tries to find the optimal settings for - * the AAT_A and AAT_B registers, which are connected to variable capacitors - * to tune the antenna matching. - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "st25r3916_aat.h" -#include "utils.h" -#include "st_errno.h" -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "platform.h" -#include "rfal_chip.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define ST25R3916_AAT_CAP_DELAY_MAX 10 /*!< Max Variable Capacitor settle delay */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -#define st25r3916AatLog(...) /* platformLog(__VA_ARGS__) */ /*!< Logging macro */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode aatHillClimb( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus); -static int32_t aatGreedyDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir); -static int32_t aatSteepestDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir, - int32_t previousDir2); - -static ReturnCode aatMeasure( - uint8_t serCap, - uint8_t parCap, - uint8_t* amplitude, - uint8_t* phase, - uint16_t* measureCnt); -static uint32_t - aatCalcF(const struct st25r3916AatTuneParams* tuningParams, uint8_t amplitude, uint8_t phase); -static ReturnCode aatStepDacVals( - const struct st25r3916AatTuneParams* tuningParams, - uint8_t* a, - uint8_t* b, - int32_t dir); - -/*******************************************************************************/ -ReturnCode st25r3916AatTune( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus) { - ReturnCode err; - const struct st25r3916AatTuneParams* tp = tuningParams; - struct st25r3916AatTuneResult* ts = tuningStatus; - struct st25r3916AatTuneParams defaultTuningParams = { - .aat_a_min = 0, - .aat_a_max = 255, - .aat_a_start = 127, - .aat_a_stepWidth = 32, - .aat_b_min = 0, - .aat_b_max = 255, - .aat_b_start = 127, - .aat_b_stepWidth = 32, - - .phaTarget = 128, - .phaWeight = 2, - .ampTarget = 196, - .ampWeight = 1, - - .doDynamicSteps = true, - .measureLimit = 50, - }; - struct st25r3916AatTuneResult defaultTuneResult; - - if((NULL != tp) && ((tp->aat_a_min > tp->aat_a_max) || (tp->aat_a_start < tp->aat_a_min) || - (tp->aat_a_start > tp->aat_a_max) || (tp->aat_b_min > tp->aat_b_max) || - (tp->aat_b_start < tp->aat_b_min) || (tp->aat_b_start > tp->aat_b_max))) { - return ERR_PARAM; - } - - if(NULL == tp) { /* Start from current caps with default params */ - st25r3916ReadRegister(ST25R3916_REG_ANT_TUNE_A, &defaultTuningParams.aat_a_start); - st25r3916ReadRegister(ST25R3916_REG_ANT_TUNE_B, &defaultTuningParams.aat_b_start); - tp = &defaultTuningParams; - } - - if(NULL == ts) { - ts = &defaultTuneResult; - } - - ts->measureCnt = 0; /* Clear current measure count */ - - err = aatHillClimb(tp, ts); - - return err; -} - -/*******************************************************************************/ -static ReturnCode aatHillClimb( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus) { - ReturnCode err = ERR_NONE; - uint32_t f_min; - int32_t direction, gdirection; - uint8_t amp, phs; - struct st25r3916AatTuneParams tp = *tuningParams; // local copy to obey const - - tuningStatus->aat_a = tuningParams->aat_a_start; - tuningStatus->aat_b = tuningParams->aat_b_start; - - /* Get a proper start value */ - aatMeasure(tuningStatus->aat_a, tuningStatus->aat_b, &, &phs, &tuningStatus->measureCnt); - f_min = aatCalcF(&tp, amp, phs); - direction = 0; - - st25r3916AatLog("%d %d: %d***\n", tuningStatus->aat_a, tuningStatus->aat_b, f_min); - - do { - direction = - 0; /* Initially and after reducing step sizes we don't have a previous direction */ - do { - /* With the greedy step below always executed aftwards the -direction does never need to be investigated */ - direction = aatSteepestDescent(&f_min, &tp, tuningStatus, direction, -direction); - if(tuningStatus->measureCnt > tp.measureLimit) { - err = ERR_OVERRUN; - break; - } - do { - gdirection = aatGreedyDescent(&f_min, &tp, tuningStatus, direction); - if(tuningStatus->measureCnt > tp.measureLimit) { - err = ERR_OVERRUN; - break; - } - } while(0 != gdirection); - } while(0 != direction); - tp.aat_a_stepWidth /= 2U; /* Reduce step sizes */ - tp.aat_b_stepWidth /= 2U; - } while(tp.doDynamicSteps && ((tp.aat_a_stepWidth > 0U) || (tp.aat_b_stepWidth > 0U))); - - return err; -} - -/*******************************************************************************/ -static int32_t aatSteepestDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir, - int32_t previousDir2) { - int32_t i; - uint8_t amp, phs; - uint32_t f; - int32_t bestdir = - 0; /* Negative direction: decrease, Positive: increase. (-)1: aat_a, (-)2: aat_b */ - - for(i = -2; i <= 2; i++) { - uint8_t a = tuningStatus->aat_a, b = tuningStatus->aat_b; - - if((0 == i) || (i == -previousDir) || - (i == -previousDir2)) { /* Skip no direction and avoid going backwards */ - continue; - } - if(0U != aatStepDacVals( - tuningParams, - &a, - &b, - i)) { /* If stepping did not change the value, omit this direction */ - continue; - } - - aatMeasure(a, b, &, &phs, &tuningStatus->measureCnt); - f = aatCalcF(tuningParams, amp, phs); - st25r3916AatLog("%d : %d %d: %d", i, a, b, f); - if(f < *f_min) { /* Value is better than all previous ones */ - st25r3916AatLog("*"); - *f_min = f; - bestdir = i; - } - st25r3916AatLog("\n"); - } - if(0 != bestdir) { /* Walk into the best direction */ - aatStepDacVals(tuningParams, &tuningStatus->aat_a, &tuningStatus->aat_b, bestdir); - } - return bestdir; -} - -/*******************************************************************************/ -static int32_t aatGreedyDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir) { - uint8_t amp, phs; - uint32_t f; - uint8_t a = tuningStatus->aat_a, b = tuningStatus->aat_b; - - if(0U != aatStepDacVals( - tuningParams, - &a, - &b, - previousDir)) { /* If stepping did not change the value, omit this direction */ - return 0; - } - - aatMeasure(a, b, &, &phs, &tuningStatus->measureCnt); - f = aatCalcF(tuningParams, amp, phs); - st25r3916AatLog("g : %d %d: %d", a, b, f); - if(f < *f_min) { /* Value is better than previous one */ - st25r3916AatLog("*\n"); - tuningStatus->aat_a = a; - tuningStatus->aat_b = b; - *f_min = f; - return previousDir; - } - - st25r3916AatLog("\n"); - return 0; -} - -/*******************************************************************************/ -static uint32_t - aatCalcF(const struct st25r3916AatTuneParams* tuningParams, uint8_t amplitude, uint8_t phase) { - /* f(amp, pha) = (ampWeight * |amp - ampTarget|) + (phaWeight * |pha - phaTarget|) */ - uint8_t ampTarget = tuningParams->ampTarget; - uint8_t phaTarget = tuningParams->phaTarget; - - uint32_t ampWeight = tuningParams->ampWeight; - uint32_t phaWeight = tuningParams->phaWeight; - - /* Temp variables to avoid MISRA R10.8 (cast on composite expression) */ - uint8_t ad = ((amplitude > ampTarget) ? (amplitude - ampTarget) : (ampTarget - amplitude)); - uint8_t pd = ((phase > phaTarget) ? (phase - phaTarget) : (phaTarget - phase)); - - uint32_t ampDelta = (uint32_t)ad; - uint32_t phaDelta = (uint32_t)pd; - - return ((ampWeight * ampDelta) + (phaWeight * phaDelta)); -} - -/*******************************************************************************/ -static ReturnCode aatStepDacVals( - const struct st25r3916AatTuneParams* tuningParams, - uint8_t* a, - uint8_t* b, - int32_t dir) { - int16_t aat_a = (int16_t)*a, aat_b = (int16_t)*b; - - switch(abs(dir)) { /* Advance by steps size in requested direction */ - case 1: - aat_a = (dir < 0) ? (aat_a - (int16_t)tuningParams->aat_a_stepWidth) : - (aat_a + (int16_t)tuningParams->aat_a_stepWidth); - if(aat_a < (int16_t)tuningParams->aat_a_min) { - aat_a = (int16_t)tuningParams->aat_a_min; - } - if(aat_a > (int16_t)tuningParams->aat_a_max) { - aat_a = (int16_t)tuningParams->aat_a_max; - } - if((int16_t)*a == aat_a) { - return ERR_PARAM; - } - break; - case 2: - aat_b = (dir < 0) ? (aat_b - (int16_t)tuningParams->aat_b_stepWidth) : - (aat_b + (int16_t)tuningParams->aat_b_stepWidth); - if(aat_b < (int16_t)tuningParams->aat_b_min) { - aat_b = (int16_t)tuningParams->aat_b_min; - } - if(aat_b > (int16_t)tuningParams->aat_b_max) { - aat_b = (int16_t)tuningParams->aat_b_max; - } - if((int16_t)*b == aat_b) { - return ERR_PARAM; - } - break; - default: - return ERR_REQUEST; - } - /* We only get here if actual values have changed. In all other cases an error is returned */ - *a = (uint8_t)aat_a; - *b = (uint8_t)aat_b; - - return ERR_NONE; -} - -/*******************************************************************************/ -static ReturnCode aatMeasure( - uint8_t serCap, - uint8_t parCap, - uint8_t* amplitude, - uint8_t* phase, - uint16_t* measureCnt) { - ReturnCode err; - - *amplitude = 0; - *phase = 0; - - st25r3916WriteRegister(ST25R3916_REG_ANT_TUNE_A, serCap); - st25r3916WriteRegister(ST25R3916_REG_ANT_TUNE_B, parCap); - - /* Wait till caps have settled.. */ - platformDelay(ST25R3916_AAT_CAP_DELAY_MAX); - - /* Get amplitude and phase .. */ - err = rfalChipMeasureAmplitude(amplitude); - if(ERR_NONE == err) { - err = rfalChipMeasurePhase(phase); - } - - if(measureCnt != NULL) { - (*measureCnt)++; - } - return err; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h deleted file mode 100644 index 4c97ad3557..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h +++ /dev/null @@ -1,109 +0,0 @@ -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file st25r3916_aat.h - * - * \author - * - * \brief ST25R3916 Antenna Tuning - * - * The antenna tuning algorithm tries to find the optimal settings for - * the AAT_A and AAT_B registers, which are connected to variable capacitors - * to tune the antenna matching. - * - */ - -#ifndef ST25R3916_AAT_H -#define ST25R3916_AAT_H - -#include "platform.h" -#include "st_errno.h" - -/* -****************************************************************************** -* GLOBAL DATATYPES -****************************************************************************** -*/ - -/*! - * struct representing input parameters for the antenna tuning - */ -struct st25r3916AatTuneParams { - uint8_t aat_a_min; /*!< min value of A cap */ - uint8_t aat_a_max; /*!< max value of A cap */ - uint8_t aat_a_start; /*!< start value of A cap */ - uint8_t aat_a_stepWidth; /*!< increment stepWidth for A cap */ - uint8_t aat_b_min; /*!< min value of B cap */ - uint8_t aat_b_max; /*!< max value of B cap */ - uint8_t aat_b_start; /*!< start value of B cap */ - uint8_t aat_b_stepWidth; /*!< increment stepWidth for B cap */ - - uint8_t phaTarget; /*!< target phase */ - uint8_t phaWeight; /*!< weight of target phase */ - uint8_t ampTarget; /*!< target amplitude */ - uint8_t ampWeight; /*!< weight of target amplitude */ - - bool doDynamicSteps; /*!< dynamically reduce step size in algo */ - uint8_t measureLimit; /*!< max number of allowed steps/measurements */ -}; - -/*! - * struct representing out parameters for the antenna tuning - */ -struct st25r3916AatTuneResult { - uint8_t aat_a; /*!< serial cap after tuning */ - uint8_t aat_b; /*!< parallel cap after tuning */ - uint8_t pha; /*!< phase after tuning */ - uint8_t amp; /*!< amplitude after tuning */ - uint16_t measureCnt; /*!< number of measures performed */ -}; - -/*! - ***************************************************************************** - * \brief Perform antenna tuning - * - * This function starts an antenna tuning procedure by modifying the serial - * and parallel capacitors of the antenna matching circuit via the AAT_A - * and AAT_B registers. - * - * \param[in] tuningParams : Input parameters for the tuning algorithm. If NULL - * default values will be used. - * \param[out] tuningStatus : Result information of performed tuning. If NULL - * no further information is returned, only registers - * ST25R3916 (AAT_A,B) will be adapted. - * - * \return ERR_IO : Error during communication. - * \return ERR_PARAM : Invalid input parameters - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode st25r3916AatTune( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus); - -#endif /* ST25R3916_AAT_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_com.c deleted file mode 100644 index 40d65807d1..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.c +++ /dev/null @@ -1,618 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief Implementation of ST25R3916 communication - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "st25r3916_led.h" -#include "st_errno.h" -#include "platform.h" -#include "utils.h" - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ - -#define ST25R3916_OPTIMIZE \ - true /*!< Optimization switch: false always write value to register */ -#define ST25R3916_I2C_ADDR \ - (0xA0U >> 1) /*!< ST25R3916's default I2C address */ -#define ST25R3916_REG_LEN 1U /*!< Byte length of a ST25R3916 register */ - -#define ST25R3916_WRITE_MODE \ - (0U << 6) /*!< ST25R3916 Operation Mode: Write */ -#define ST25R3916_READ_MODE \ - (1U << 6) /*!< ST25R3916 Operation Mode: Read */ -#define ST25R3916_CMD_MODE \ - (3U << 6) /*!< ST25R3916 Operation Mode: Direct Command */ -#define ST25R3916_FIFO_LOAD \ - (0x80U) /*!< ST25R3916 Operation Mode: FIFO Load */ -#define ST25R3916_FIFO_READ \ - (0x9FU) /*!< ST25R3916 Operation Mode: FIFO Read */ -#define ST25R3916_PT_A_CONFIG_LOAD \ - (0xA0U) /*!< ST25R3916 Operation Mode: Passive Target Memory A-Config Load */ -#define ST25R3916_PT_F_CONFIG_LOAD \ - (0xA8U) /*!< ST25R3916 Operation Mode: Passive Target Memory F-Config Load */ -#define ST25R3916_PT_TSN_DATA_LOAD \ - (0xACU) /*!< ST25R3916 Operation Mode: Passive Target Memory TSN Load */ -#define ST25R3916_PT_MEM_READ \ - (0xBFU) /*!< ST25R3916 Operation Mode: Passive Target Memory Read */ - -#define ST25R3916_CMD_LEN \ - (1U) /*!< ST25R3916 CMD length */ -#define ST25R3916_BUF_LEN \ - (ST25R3916_CMD_LEN + \ - ST25R3916_FIFO_DEPTH) /*!< ST25R3916 communication buffer: CMD + FIFO length */ - -/* -****************************************************************************** -* MACROS -****************************************************************************** -*/ -#ifdef RFAL_USE_I2C -#define st25r3916I2CStart() \ - platformI2CStart() /*!< ST25R3916 HAL I2C driver macro to start a I2C transfer */ -#define st25r3916I2CStop() \ - platformI2CStop() /*!< ST25R3916 HAL I2C driver macro to stop a I2C transfer */ -#define st25r3916I2CRepeatStart() \ - platformI2CRepeatStart() /*!< ST25R3916 HAL I2C driver macro to repeat Start */ -#define st25r3916I2CSlaveAddrWR(sA) \ - platformI2CSlaveAddrWR( \ - sA) /*!< ST25R3916 HAL I2C driver macro to repeat Start */ -#define st25r3916I2CSlaveAddrRD(sA) \ - platformI2CSlaveAddrRD( \ - sA) /*!< ST25R3916 HAL I2C driver macro to repeat Start */ -#endif /* RFAL_USE_I2C */ - -#if defined(ST25R_COM_SINGLETXRX) && !defined(RFAL_USE_I2C) -static uint8_t - comBuf[ST25R3916_BUF_LEN]; /*!< ST25R3916 communication buffer */ -static uint16_t comBufIt; /*!< ST25R3916 communication buffer iterator */ -#endif /* ST25R_COM_SINGLETXRX */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Start - * - * This method performs the required actions to start communications with - * ST25R3916, either by SPI or I2C - ****************************************************************************** - */ -static void st25r3916comStart(void); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Stop - * - * This method performs the required actions to terminate communications with - * ST25R3916, either by SPI or I2C - ****************************************************************************** - */ -static void st25r3916comStop(void); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Repeat Start - * - * This method performs the required actions to repeat start a transmission - * with ST25R3916, either by SPI or I2C - ****************************************************************************** - */ -#ifdef RFAL_USE_I2C -static void st25r3916comRepeatStart(void); -#else -#define st25r3916comRepeatStart() -#endif /* RFAL_USE_I2C */ - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Tx - * - * This method performs the required actions to transmit the given buffer - * to ST25R3916, either by SPI or I2C - * - * \param[in] txBuf : the buffer to transmit - * \param[in] txLen : the length of the buffer to transmit - * \param[in] last : true if last data to be transmitted - * \param[in] txOnly : true no reception is to be performed - * - ****************************************************************************** - */ -static void st25r3916comTx(const uint8_t* txBuf, uint16_t txLen, bool last, bool txOnly); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Rx - * - * This method performs the required actions to receive from ST25R3916 the given - * amount of bytes, either by SPI or I2C - * - * \param[out] rxBuf : the buffer place the received bytes - * \param[in] rxLen : the length to receive - * - ****************************************************************************** - */ -static void st25r3916comRx(uint8_t* rxBuf, uint16_t rxLen); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Tx Byte - * - * This helper method transmits a byte passed by value and not by reference - * - * \param[in] txByte : the value of the byte to be transmitted - * \param[in] last : true if last byte to be transmitted - * \param[in] txOnly : true no reception is to be performed - * - ****************************************************************************** - */ -static void st25r3916comTxByte(uint8_t txByte, bool last, bool txOnly); - -/* - ****************************************************************************** - * LOCAL FUNCTION - ****************************************************************************** - */ -static void st25r3916comStart(void) { - /* Make this operation atomic, disabling ST25R3916 interrupt during communications*/ - platformProtectST25RComm(); - -#ifdef RFAL_USE_I2C - /* I2C Start and send Slave Address */ - st25r3916I2CStart(); - st25r3916I2CSlaveAddrWR(ST25R3916_I2C_ADDR); -#else - /* Perform the chip select */ - platformSpiSelect(); - -#if defined(ST25R_COM_SINGLETXRX) - comBufIt = 0; /* reset local buffer position */ -#endif /* ST25R_COM_SINGLETXRX */ - -#endif /* RFAL_USE_I2C */ -} - -/*******************************************************************************/ -static void st25r3916comStop(void) { -#ifdef RFAL_USE_I2C - /* Generate Stop signal */ - st25r3916I2CStop(); -#else - /* Release the chip select */ - platformSpiDeselect(); -#endif /* RFAL_USE_I2C */ - - /* reEnable the ST25R3916 interrupt */ - platformUnprotectST25RComm(); -} - -/*******************************************************************************/ -#ifdef RFAL_USE_I2C -static void st25r3916comRepeatStart(void) { - st25r3916I2CRepeatStart(); - st25r3916I2CSlaveAddrRD(ST25R3916_I2C_ADDR); -} -#endif /* RFAL_USE_I2C */ - -/*******************************************************************************/ -static void st25r3916comTx(const uint8_t* txBuf, uint16_t txLen, bool last, bool txOnly) { - NO_WARNING(last); - NO_WARNING(txOnly); - - if(txLen > 0U) { -#ifdef RFAL_USE_I2C - platformI2CTx(txBuf, txLen, last, txOnly); -#else /* RFAL_USE_I2C */ - -#ifdef ST25R_COM_SINGLETXRX - - ST_MEMCPY( - &comBuf[comBufIt], - txBuf, - MIN(txLen, - (ST25R3916_BUF_LEN - - comBufIt))); /* copy tx data to local buffer */ - comBufIt += - MIN(txLen, - (ST25R3916_BUF_LEN - - comBufIt)); /* store position on local buffer */ - - if(last && txOnly) /* only perform SPI transaction if no Rx will follow */ - { - platformSpiTxRx(comBuf, NULL, comBufIt); - } - -#else - platformSpiTxRx(txBuf, NULL, txLen); -#endif /* ST25R_COM_SINGLETXRX */ - -#endif /* RFAL_USE_I2C */ - } -} - -/*******************************************************************************/ -static void st25r3916comRx(uint8_t* rxBuf, uint16_t rxLen) { - if(rxLen > 0U) { -#ifdef RFAL_USE_I2C - platformI2CRx(rxBuf, rxLen); -#else /* RFAL_USE_I2C */ - -#ifdef ST25R_COM_SINGLETXRX - ST_MEMSET( - &comBuf[comBufIt], - 0x00, - MIN(rxLen, - (ST25R3916_BUF_LEN - - comBufIt))); /* clear outgoing buffer */ - platformSpiTxRx( - comBuf, - comBuf, - MIN((comBufIt + rxLen), - ST25R3916_BUF_LEN)); /* transceive as a single SPI call */ - ST_MEMCPY( - rxBuf, - &comBuf[comBufIt], - MIN(rxLen, - (ST25R3916_BUF_LEN - - comBufIt))); /* copy from local buf to output buffer and skip cmd byte */ -#else - if(rxBuf != NULL) { - ST_MEMSET( - rxBuf, 0x00, rxLen); /* clear outgoing buffer */ - } - platformSpiTxRx(NULL, rxBuf, rxLen); -#endif /* ST25R_COM_SINGLETXRX */ -#endif /* RFAL_USE_I2C */ - } -} - -/*******************************************************************************/ -static void st25r3916comTxByte(uint8_t txByte, bool last, bool txOnly) { - uint8_t val = txByte; /* MISRA 17.8: use intermediate variable */ - st25r3916comTx(&val, ST25R3916_REG_LEN, last, txOnly); -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode st25r3916ReadRegister(uint8_t reg, uint8_t* val) { - return st25r3916ReadMultipleRegisters(reg, val, ST25R3916_REG_LEN); -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadMultipleRegisters(uint8_t reg, uint8_t* values, uint8_t length) { - if(length > 0U) { - st25r3916comStart(); - - /* If is a space-B register send a direct command first */ - if((reg & ST25R3916_SPACE_B) != 0U) { - st25r3916comTxByte(ST25R3916_CMD_SPACE_B_ACCESS, false, false); - } - - st25r3916comTxByte(((reg & ~ST25R3916_SPACE_B) | ST25R3916_READ_MODE), true, false); - st25r3916comRepeatStart(); - st25r3916comRx(values, length); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteRegister(uint8_t reg, uint8_t val) { - uint8_t value = val; /* MISRA 17.8: use intermediate variable */ - return st25r3916WriteMultipleRegisters(reg, &value, ST25R3916_REG_LEN); -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteMultipleRegisters(uint8_t reg, const uint8_t* values, uint8_t length) { - if(length > 0U) { - st25r3916comStart(); - - if((reg & ST25R3916_SPACE_B) != 0U) { - st25r3916comTxByte(ST25R3916_CMD_SPACE_B_ACCESS, false, true); - } - - st25r3916comTxByte(((reg & ~ST25R3916_SPACE_B) | ST25R3916_WRITE_MODE), false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - - /* Send a WriteMultiReg event to LED handling */ - st25r3916ledEvtWrMultiReg(reg, values, length); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteFifo(const uint8_t* values, uint16_t length) { - if(length > ST25R3916_FIFO_DEPTH) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_FIFO_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadFifo(uint8_t* buf, uint16_t length) { - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_FIFO_READ, true, false); - - st25r3916comRepeatStart(); - st25r3916comRx(buf, length); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WritePTMem(const uint8_t* values, uint16_t length) { - if(length > ST25R3916_PTM_LEN) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_A_CONFIG_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadPTMem(uint8_t* values, uint16_t length) { - uint8_t - tmp[ST25R3916_REG_LEN + - ST25R3916_PTM_LEN]; /* local buffer to handle prepended byte on I2C and SPI */ - - if(length > 0U) { - if(length > ST25R3916_PTM_LEN) { - return ERR_PARAM; - } - - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_MEM_READ, true, false); - - st25r3916comRepeatStart(); - st25r3916comRx(tmp, (ST25R3916_REG_LEN + length)); /* skip prepended byte */ - st25r3916comStop(); - - /* Copy PTMem content without prepended byte */ - ST_MEMCPY(values, (tmp + ST25R3916_REG_LEN), length); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WritePTMemF(const uint8_t* values, uint16_t length) { - if(length > (ST25R3916_PTM_F_LEN + ST25R3916_PTM_TSN_LEN)) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_F_CONFIG_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WritePTMemTSN(const uint8_t* values, uint16_t length) { - if(length > ST25R3916_PTM_TSN_LEN) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_TSN_DATA_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ExecuteCommand(uint8_t cmd) { - st25r3916comStart(); - st25r3916comTxByte((cmd | ST25R3916_CMD_MODE), true, true); - st25r3916comStop(); - - /* Send a cmd event to LED handling */ - st25r3916ledEvtCmd(cmd); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadTestRegister(uint8_t reg, uint8_t* val) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_CMD_TEST_ACCESS, false, false); - st25r3916comTxByte((reg | ST25R3916_READ_MODE), true, false); - st25r3916comRepeatStart(); - st25r3916comRx(val, ST25R3916_REG_LEN); - st25r3916comStop(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteTestRegister(uint8_t reg, uint8_t val) { - uint8_t value = val; /* MISRA 17.8: use intermediate variable */ - - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_CMD_TEST_ACCESS, false, true); - st25r3916comTxByte((reg | ST25R3916_WRITE_MODE), false, true); - st25r3916comTx(&value, ST25R3916_REG_LEN, true, true); - st25r3916comStop(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ClrRegisterBits(uint8_t reg, uint8_t clr_mask) { - ReturnCode ret; - uint8_t rdVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadRegister(reg, &rdVal)); - - /* Only perform a Write if value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == (uint8_t)(rdVal & ~clr_mask))) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteRegister(reg, (uint8_t)(rdVal & ~clr_mask)); -} - -/*******************************************************************************/ -ReturnCode st25r3916SetRegisterBits(uint8_t reg, uint8_t set_mask) { - ReturnCode ret; - uint8_t rdVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadRegister(reg, &rdVal)); - - /* Only perform a Write if the value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == (rdVal | set_mask))) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteRegister(reg, (rdVal | set_mask)); -} - -/*******************************************************************************/ -ReturnCode st25r3916ChangeRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value) { - return st25r3916ModifyRegister(reg, valueMask, (valueMask & value)); -} - -/*******************************************************************************/ -ReturnCode st25r3916ModifyRegister(uint8_t reg, uint8_t clr_mask, uint8_t set_mask) { - ReturnCode ret; - uint8_t rdVal; - uint8_t wrVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadRegister(reg, &rdVal)); - - /* Compute new value */ - wrVal = (uint8_t)(rdVal & ~clr_mask); - wrVal |= set_mask; - - /* Only perform a Write if the value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == wrVal)) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteRegister(reg, wrVal); -} - -/*******************************************************************************/ -ReturnCode st25r3916ChangeTestRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value) { - ReturnCode ret; - uint8_t rdVal; - uint8_t wrVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadTestRegister(reg, &rdVal)); - - /* Compute new value */ - wrVal = (uint8_t)(rdVal & ~valueMask); - wrVal |= (uint8_t)(value & valueMask); - - /* Only perform a Write if the value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == wrVal)) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteTestRegister(reg, wrVal); -} - -/*******************************************************************************/ -bool st25r3916CheckReg(uint8_t reg, uint8_t mask, uint8_t val) { - uint8_t regVal; - - regVal = 0; - st25r3916ReadRegister(reg, ®Val); - - return ((regVal & mask) == val); -} - -/*******************************************************************************/ -bool st25r3916IsRegValid(uint8_t reg) { -#pragma GCC diagnostic ignored "-Wtype-limits" - if(!(((int16_t)reg >= (int32_t)ST25R3916_REG_IO_CONF1) && - (reg <= (ST25R3916_SPACE_B | ST25R3916_REG_IC_IDENTITY)))) { - return false; - } - return true; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c deleted file mode 100644 index 74c2797ce4..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c +++ /dev/null @@ -1,231 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 Interrupt handling - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916_irq.h" -#include "st25r3916_com.h" -#include "st25r3916_led.h" -#include "st25r3916.h" -#include "utils.h" - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -/*! Holds current and previous interrupt callback pointer as well as current Interrupt status and mask */ -typedef struct { - void (*prevCallback)(void); /*!< call back function for ST25R3916 interrupt */ - void (*callback)(void); /*!< call back function for ST25R3916 interrupt */ - uint32_t status; /*!< latest interrupt status */ - uint32_t mask; /*!< Interrupt mask. Negative mask = ST25R3916 mask regs */ -} st25r3916Interrupt; - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/*! Length of the interrupt registers */ -#define ST25R3916_INT_REGS_LEN ((ST25R3916_REG_IRQ_TARGET - ST25R3916_REG_IRQ_MAIN) + 1U) - -/* -****************************************************************************** -* GLOBAL VARIABLES -****************************************************************************** -*/ - -static volatile st25r3916Interrupt st25r3916interrupt; /*!< Instance of ST25R3916 interrupt */ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ -void st25r3916InitInterrupts(void) { - platformIrqST25RPinInitialize(); - platformIrqST25RSetCallback(st25r3916Isr); - - st25r3916interrupt.callback = NULL; - st25r3916interrupt.prevCallback = NULL; - st25r3916interrupt.status = ST25R3916_IRQ_MASK_NONE; - st25r3916interrupt.mask = ST25R3916_IRQ_MASK_NONE; -} - -/*******************************************************************************/ -void st25r3916Isr(void) { - st25r3916CheckForReceivedInterrupts(); - - // Check if callback is set and run it - if(NULL != st25r3916interrupt.callback) { - st25r3916interrupt.callback(); - } -} - -/*******************************************************************************/ -void st25r3916CheckForReceivedInterrupts(void) { - uint8_t iregs[ST25R3916_INT_REGS_LEN]; - uint32_t irqStatus; - - /* Initialize iregs */ - irqStatus = ST25R3916_IRQ_MASK_NONE; - ST_MEMSET(iregs, (int32_t)(ST25R3916_IRQ_MASK_ALL & 0xFFU), ST25R3916_INT_REGS_LEN); - - /* In case the IRQ is Edge (not Level) triggered read IRQs until done */ - while(platformGpioIsHigh(ST25R_INT_PORT, ST25R_INT_PIN)) { - st25r3916ReadMultipleRegisters(ST25R3916_REG_IRQ_MAIN, iregs, ST25R3916_INT_REGS_LEN); - - irqStatus |= (uint32_t)iregs[0]; - irqStatus |= (uint32_t)iregs[1] << 8; - irqStatus |= (uint32_t)iregs[2] << 16; - irqStatus |= (uint32_t)iregs[3] << 24; - } - - /* Forward all interrupts, even masked ones to application */ - platformProtectST25RIrqStatus(); - st25r3916interrupt.status |= irqStatus; - platformUnprotectST25RIrqStatus(); - - /* Send an IRQ event to LED handling */ - st25r3916ledEvtIrq(st25r3916interrupt.status); -} - -/*******************************************************************************/ -void st25r3916ModifyInterrupts(uint32_t clr_mask, uint32_t set_mask) { - uint8_t i; - uint32_t old_mask; - uint32_t new_mask; - - old_mask = st25r3916interrupt.mask; - new_mask = ((~old_mask & set_mask) | (old_mask & clr_mask)); - st25r3916interrupt.mask &= ~clr_mask; - st25r3916interrupt.mask |= set_mask; - - for(i = 0; i < ST25R3916_INT_REGS_LEN; i++) { - if(((new_mask >> (8U * i)) & 0xFFU) == 0U) { - continue; - } - - st25r3916WriteRegister( - ST25R3916_REG_IRQ_MASK_MAIN + i, - (uint8_t)((st25r3916interrupt.mask >> (8U * i)) & 0xFFU)); - } - return; -} - -/*******************************************************************************/ -uint32_t st25r3916WaitForInterruptsTimed(uint32_t mask, uint16_t tmo) { - uint32_t tmrDelay; - uint32_t status; - - tmrDelay = platformTimerCreate(tmo); - - /* Run until specific interrupt has happen or the timer has expired */ - do { - status = (st25r3916interrupt.status & mask); - } while((!platformTimerIsExpired(tmrDelay) || (tmo == 0U)) && (status == 0U)); - - platformTimerDestroy(tmrDelay); - - status = st25r3916interrupt.status & mask; - - platformProtectST25RIrqStatus(); - st25r3916interrupt.status &= ~status; - platformUnprotectST25RIrqStatus(); - - return status; -} - -/*******************************************************************************/ -uint32_t st25r3916GetInterrupt(uint32_t mask) { - uint32_t irqs; - - irqs = (st25r3916interrupt.status & mask); - if(irqs != ST25R3916_IRQ_MASK_NONE) { - platformProtectST25RIrqStatus(); - st25r3916interrupt.status &= ~irqs; - platformUnprotectST25RIrqStatus(); - } - - return irqs; -} - -/*******************************************************************************/ -void st25r3916ClearAndEnableInterrupts(uint32_t mask) { - st25r3916GetInterrupt(mask); - st25r3916EnableInterrupts(mask); -} - -/*******************************************************************************/ -void st25r3916EnableInterrupts(uint32_t mask) { - st25r3916ModifyInterrupts(mask, 0); -} - -/*******************************************************************************/ -void st25r3916DisableInterrupts(uint32_t mask) { - st25r3916ModifyInterrupts(0, mask); -} - -/*******************************************************************************/ -void st25r3916ClearInterrupts(void) { - uint8_t iregs[ST25R3916_INT_REGS_LEN]; - - st25r3916ReadMultipleRegisters(ST25R3916_REG_IRQ_MAIN, iregs, ST25R3916_INT_REGS_LEN); - - platformProtectST25RIrqStatus(); - st25r3916interrupt.status = ST25R3916_IRQ_MASK_NONE; - platformUnprotectST25RIrqStatus(); - return; -} - -/*******************************************************************************/ -void st25r3916IRQCallbackSet(void (*cb)(void)) { - st25r3916interrupt.prevCallback = st25r3916interrupt.callback; - st25r3916interrupt.callback = cb; -} - -/*******************************************************************************/ -void st25r3916IRQCallbackRestore(void) { - st25r3916interrupt.callback = st25r3916interrupt.prevCallback; - st25r3916interrupt.prevCallback = NULL; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h deleted file mode 100644 index e8ce2d07a5..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h +++ /dev/null @@ -1,296 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 Interrupt handling - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_IRQ - * \brief RFAL ST25R3916 IRQ - * @{ - * - */ - -#ifndef ST25R3916_IRQ_H -#define ST25R3916_IRQ_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define ST25R3916_IRQ_MASK_ALL \ - (uint32_t)(0xFFFFFFFFUL) /*!< All ST25R3916 interrupt sources */ -#define ST25R3916_IRQ_MASK_NONE \ - (uint32_t)(0x00000000UL) /*!< No ST25R3916 interrupt source */ - -/* Main interrupt register */ -#define ST25R3916_IRQ_MASK_OSC \ - (uint32_t)(0x00000080U) /*!< ST25R3916 oscillator stable interrupt */ -#define ST25R3916_IRQ_MASK_FWL \ - (uint32_t)(0x00000040U) /*!< ST25R3916 FIFO water level interrupt */ -#define ST25R3916_IRQ_MASK_RXS \ - (uint32_t)(0x00000020U) /*!< ST25R3916 start of receive interrupt */ -#define ST25R3916_IRQ_MASK_RXE \ - (uint32_t)(0x00000010U) /*!< ST25R3916 end of receive interrupt */ -#define ST25R3916_IRQ_MASK_TXE \ - (uint32_t)(0x00000008U) /*!< ST25R3916 end of transmission interrupt */ -#define ST25R3916_IRQ_MASK_COL \ - (uint32_t)(0x00000004U) /*!< ST25R3916 bit collision interrupt */ -#define ST25R3916_IRQ_MASK_RX_REST \ - (uint32_t)(0x00000002U) /*!< ST25R3916 automatic reception restart interrupt */ -#define ST25R3916_IRQ_MASK_RFU \ - (uint32_t)(0x00000001U) /*!< ST25R3916 RFU interrupt */ - -/* Timer and NFC interrupt register */ -#define ST25R3916_IRQ_MASK_DCT \ - (uint32_t)(0x00008000U) /*!< ST25R3916 termination of direct command interrupt. */ -#define ST25R3916_IRQ_MASK_NRE \ - (uint32_t)(0x00004000U) /*!< ST25R3916 no-response timer expired interrupt */ -#define ST25R3916_IRQ_MASK_GPE \ - (uint32_t)(0x00002000U) /*!< ST25R3916 general purpose timer expired interrupt */ -#define ST25R3916_IRQ_MASK_EON \ - (uint32_t)(0x00001000U) /*!< ST25R3916 external field on interrupt */ -#define ST25R3916_IRQ_MASK_EOF \ - (uint32_t)(0x00000800U) /*!< ST25R3916 external field off interrupt */ -#define ST25R3916_IRQ_MASK_CAC \ - (uint32_t)(0x00000400U) /*!< ST25R3916 collision during RF collision avoidance interrupt */ -#define ST25R3916_IRQ_MASK_CAT \ - (uint32_t)(0x00000200U) /*!< ST25R3916 minimum guard time expired interrupt */ -#define ST25R3916_IRQ_MASK_NFCT \ - (uint32_t)(0x00000100U) /*!< ST25R3916 initiator bit rate recognised interrupt */ - -/* Error and wake-up interrupt register */ -#define ST25R3916_IRQ_MASK_CRC \ - (uint32_t)(0x00800000U) /*!< ST25R3916 CRC error interrupt */ -#define ST25R3916_IRQ_MASK_PAR \ - (uint32_t)(0x00400000U) /*!< ST25R3916 parity error interrupt */ -#define ST25R3916_IRQ_MASK_ERR2 \ - (uint32_t)(0x00200000U) /*!< ST25R3916 soft framing error interrupt */ -#define ST25R3916_IRQ_MASK_ERR1 \ - (uint32_t)(0x00100000U) /*!< ST25R3916 hard framing error interrupt */ -#define ST25R3916_IRQ_MASK_WT \ - (uint32_t)(0x00080000U) /*!< ST25R3916 wake-up interrupt */ -#define ST25R3916_IRQ_MASK_WAM \ - (uint32_t)(0x00040000U) /*!< ST25R3916 wake-up due to amplitude interrupt */ -#define ST25R3916_IRQ_MASK_WPH \ - (uint32_t)(0x00020000U) /*!< ST25R3916 wake-up due to phase interrupt */ -#define ST25R3916_IRQ_MASK_WCAP \ - (uint32_t)(0x00010000U) /*!< ST25R3916 wake-up due to capacitance measurement */ - -/* Passive Target Interrupt Register */ -#define ST25R3916_IRQ_MASK_PPON2 \ - (uint32_t)(0x80000000U) /*!< ST25R3916 PPON2 Field on waiting Timer interrupt */ -#define ST25R3916_IRQ_MASK_SL_WL \ - (uint32_t)(0x40000000U) /*!< ST25R3916 Passive target slot number water level interrupt */ -#define ST25R3916_IRQ_MASK_APON \ - (uint32_t)(0x20000000U) /*!< ST25R3916 Anticollision done and Field On interrupt */ -#define ST25R3916_IRQ_MASK_RXE_PTA \ - (uint32_t)(0x10000000U) /*!< ST25R3916 RXE with an automatic response interrupt */ -#define ST25R3916_IRQ_MASK_WU_F \ - (uint32_t)(0x08000000U) /*!< ST25R3916 212/424b/s Passive target interrupt: Active */ -#define ST25R3916_IRQ_MASK_RFU2 \ - (uint32_t)(0x04000000U) /*!< ST25R3916 RFU2 interrupt */ -#define ST25R3916_IRQ_MASK_WU_A_X \ - (uint32_t)(0x02000000U) /*!< ST25R3916 106kb/s Passive target state interrupt: Active* */ -#define ST25R3916_IRQ_MASK_WU_A \ - (uint32_t)(0x01000000U) /*!< ST25R3916 106kb/s Passive target state interrupt: Active */ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Wait until an ST25R3916 interrupt occurs - * - * This function is used to access the ST25R3916 interrupt flags. Use this - * to wait for max. \a tmo milliseconds for the \b first interrupt indicated - * with mask \a mask to occur. - * - * \param[in] mask : mask indicating the interrupts to wait for. - * \param[in] tmo : time in milliseconds until timeout occurs. If set to 0 - * the functions waits forever. - * - * \return : 0 if timeout occurred otherwise a mask indicating the cleared - * interrupts. - * - ***************************************************************************** - */ -uint32_t st25r3916WaitForInterruptsTimed(uint32_t mask, uint16_t tmo); - -/*! - ***************************************************************************** - * \brief Get status for the given interrupt - * - * This function is used to check whether the interrupt given by \a mask - * has occurred. If yes the interrupt gets cleared. This function returns - * only status bits which are inside \a mask. - * - * \param[in] mask : mask indicating the interrupt to check for. - * - * \return the mask of the interrupts occurred - * - ***************************************************************************** - */ -uint32_t st25r3916GetInterrupt(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Init the 3916 interrupt - * - * This function is used to check whether the interrupt given by \a mask - * has occurred. - * - ***************************************************************************** - */ -void st25r3916InitInterrupts(void); - -/*! - ***************************************************************************** - * \brief Modifies the Interrupt - * - * This function modifies the interrupt - * - * \param[in] clr_mask : bit mask to be cleared on the interrupt mask - * \param[in] set_mask : bit mask to be set on the interrupt mask - ***************************************************************************** - */ -void st25r3916ModifyInterrupts(uint32_t clr_mask, uint32_t set_mask); - -/*! - ***************************************************************************** - * \brief Checks received interrupts - * - * Checks received interrupts and saves the result into global params - ***************************************************************************** - */ -void st25r3916CheckForReceivedInterrupts(void); - -/*! - ***************************************************************************** - * \brief ISR Service routine - * - * This function modiefies the interrupt - ***************************************************************************** - */ -void st25r3916Isr(void); - -/*! - ***************************************************************************** - * \brief Enable a given ST25R3916 Interrupt source - * - * This function enables all interrupts given by \a mask, - * ST25R3916_IRQ_MASK_ALL enables all interrupts. - * - * \param[in] mask: mask indicating the interrupts to be enabled - * - ***************************************************************************** - */ -void st25r3916EnableInterrupts(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Disable one or more a given ST25R3916 Interrupt sources - * - * This function disables all interrupts given by \a mask. 0xff disables all. - * - * \param[in] mask: mask indicating the interrupts to be disabled. - * - ***************************************************************************** - */ -void st25r3916DisableInterrupts(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Clear all ST25R3916 irq flags - * - ***************************************************************************** - */ -void st25r3916ClearInterrupts(void); - -/*! - ***************************************************************************** - * \brief Clears and then enables the given ST25R3916 Interrupt sources - * - * \param[in] mask: mask indicating the interrupts to be cleared and enabled - ***************************************************************************** - */ -void st25r3916ClearAndEnableInterrupts(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Sets IRQ callback for the ST25R3916 interrupt - * - ***************************************************************************** - */ -void st25r3916IRQCallbackSet(void (*cb)(void)); - -/*! - ***************************************************************************** - * \brief Sets IRQ callback for the ST25R3916 interrupt - * - ***************************************************************************** - */ -void st25r3916IRQCallbackRestore(void); - -#endif /* ST25R3916_IRQ_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_led.c deleted file mode 100644 index 9737337ca3..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.c +++ /dev/null @@ -1,148 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 LEDs handling - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916_led.h" -#include "st25r3916_irq.h" -#include "st25r3916_com.h" -#include "st25r3916.h" - -/* -****************************************************************************** -* MACROS -****************************************************************************** -*/ - -#ifdef PLATFORM_LED_RX_PIN -#define st25r3916ledRxOn() \ - platformLedOn( \ - PLATFORM_LED_RX_PORT, \ - PLATFORM_LED_RX_PIN); /*!< LED Rx Pin On from system HAL */ -#define st25r3916ledRxOff() \ - platformLedOff( \ - PLATFORM_LED_RX_PORT, \ - PLATFORM_LED_RX_PIN); /*!< LED Rx Pin Off from system HAL */ -#else /* PLATFORM_LED_RX_PIN */ -#define st25r3916ledRxOn() -#define st25r3916ledRxOff() -#endif /* PLATFORM_LED_RX_PIN */ - -#ifdef PLATFORM_LED_FIELD_PIN -#define st25r3916ledFieldOn() \ - platformLedOn( \ - PLATFORM_LED_FIELD_PORT, \ - PLATFORM_LED_FIELD_PIN); /*!< LED Field Pin On from system HAL */ -#define st25r3916ledFieldOff() \ - platformLedOff( \ - PLATFORM_LED_FIELD_PORT, \ - PLATFORM_LED_FIELD_PIN); /*!< LED Field Pin Off from system HAL */ -#else /* PLATFORM_LED_FIELD_PIN */ -#define st25r3916ledFieldOn() -#define st25r3916ledFieldOff() -#endif /* PLATFORM_LED_FIELD_PIN */ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -void st25r3916ledInit(void) { - /* Initialize LEDs if existing and defined */ - platformLedsInitialize(); - - st25r3916ledRxOff(); - st25r3916ledFieldOff(); -} - -/*******************************************************************************/ -void st25r3916ledEvtIrq(uint32_t irqs) { - if((irqs & (ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_CAT)) != 0U) { - st25r3916ledFieldOn(); - } - - if((irqs & (ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_NFCT)) != 0U) { - st25r3916ledRxOn(); - } - - if((irqs & (ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_NRE | ST25R3916_IRQ_MASK_RX_REST | - ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_A | ST25R3916_IRQ_MASK_WU_A_X | - ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RFU2)) != 0U) { - st25r3916ledRxOff(); - } -} - -/*******************************************************************************/ -void st25r3916ledEvtWrReg(uint8_t reg, uint8_t val) { - if(reg == ST25R3916_REG_OP_CONTROL) { - if((ST25R3916_REG_OP_CONTROL_tx_en & val) != 0U) { - st25r3916ledFieldOn(); - } else { - st25r3916ledFieldOff(); - } - } -} - -/*******************************************************************************/ -void st25r3916ledEvtWrMultiReg(uint8_t reg, const uint8_t* vals, uint8_t len) { - uint8_t i; - - for(i = 0; i < (len); i++) { - st25r3916ledEvtWrReg((reg + i), vals[i]); - } -} - -/*******************************************************************************/ -void st25r3916ledEvtCmd(uint8_t cmd) { - if((cmd >= ST25R3916_CMD_TRANSMIT_WITH_CRC) && - (cmd <= ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) { - st25r3916ledFieldOff(); - } - - if(cmd == ST25R3916_CMD_UNMASK_RECEIVE_DATA) { - st25r3916ledRxOff(); - } - - if(cmd == ST25R3916_CMD_SET_DEFAULT) { - st25r3916ledFieldOff(); - st25r3916ledRxOff(); - } -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_led.h deleted file mode 100644 index 376a8d7df7..0000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.h +++ /dev/null @@ -1,151 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 LEDs handling - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_LED - * \brief RFAL ST25R3916 LED - * @{ - * - */ - -#ifndef ST25R3916_LED_H -#define ST25R3916_LED_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Initialize - * - * This function initializes the LEDs that represent ST25R3916 activity - * - ***************************************************************************** - */ -void st25r3916ledInit(void); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Interrupt - * - * This function should be called upon a ST25R3916 Interrupt, providing - * the interrupt event with ST25R3916 irq flags to update LEDs - * - * \param[in] irqs: ST25R3916 irqs mask - * - ***************************************************************************** - */ -void st25r3916ledEvtIrq(uint32_t irqs); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Write Register - * - * This function should be called on a ST25R3916 Write Register operation - * providing the event with the register and value to update LEDs - * - * \param[in] reg: ST25R3916 register to be written - * \param[in] val: value to be written on the register - * - ***************************************************************************** - */ -void st25r3916ledEvtWrReg(uint8_t reg, uint8_t val); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Write Multiple Register - * - * This function should be called upon a ST25R3916 Write Multiple Registers, - * providing the event with the registers and values to update LEDs - * - * \param[in] reg : ST25R3916 first register written - * \param[in] vals: pointer to the values written - * \param[in] len : number of registers written - * - ***************************************************************************** - */ -void st25r3916ledEvtWrMultiReg(uint8_t reg, const uint8_t* vals, uint8_t len); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Direct Command - * - * This function should be called upon a ST25R3916 direct command, providing - * the event with the command executed - * - * \param[in] cmd: ST25R3916 cmd executed - * - ***************************************************************************** - */ -void st25r3916ledEvtCmd(uint8_t cmd); - -#endif /* ST25R3916_LED_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/st_errno.h b/lib/ST25RFAL002/st_errno.h deleted file mode 100644 index cd706b3d4c..0000000000 --- a/lib/ST25RFAL002/st_errno.h +++ /dev/null @@ -1,158 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

© COPYRIGHT 2018 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: STxxxx firmware - * LANGUAGE: ISO C99 - */ - -/*! \file st_errno.h - * - * \author - * - * \brief Main error codes - * - */ - -#ifndef ST_ERRNO_H -#define ST_ERRNO_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include - -/* -****************************************************************************** -* GLOBAL DATA TYPES -****************************************************************************** -*/ - -typedef uint16_t ReturnCode; /*!< Standard Return Code type from function. */ - -/* -****************************************************************************** -* DEFINES -****************************************************************************** -*/ - -/* - * Error codes to be used within the application. - * They are represented by an uint8_t - */ -enum { - ERR_NONE = 0, /*!< no error occurred */ - ERR_NOMEM = 1, /*!< not enough memory to perform the requested operation */ - ERR_BUSY = 2, /*!< device or resource busy */ - ERR_IO = 3, /*!< generic IO error */ - ERR_TIMEOUT = 4, /*!< error due to timeout */ - ERR_REQUEST = 5, /*!< invalid request or requested function can't be executed at the moment */ - ERR_NOMSG = 6, /*!< No message of desired type */ - ERR_PARAM = 7, /*!< Parameter error */ - ERR_SYSTEM = 8, /*!< System error */ - ERR_FRAMING = 9, /*!< Framing error */ - ERR_OVERRUN = 10, /*!< lost one or more received bytes */ - ERR_PROTO = 11, /*!< protocol error */ - ERR_INTERNAL = 12, /*!< Internal Error */ - ERR_AGAIN = 13, /*!< Call again */ - ERR_MEM_CORRUPT = 14, /*!< memory corruption */ - ERR_NOT_IMPLEMENTED = 15, /*!< not implemented */ - ERR_PC_CORRUPT = - 16, /*!< Program Counter has been manipulated or spike/noise trigger illegal operation */ - ERR_SEND = 17, /*!< error sending*/ - ERR_IGNORE = 18, /*!< indicates error detected but to be ignored */ - ERR_SEMANTIC = 19, /*!< indicates error in state machine (unexpected cmd) */ - ERR_SYNTAX = 20, /*!< indicates error in state machine (unknown cmd) */ - ERR_CRC = 21, /*!< crc error */ - ERR_NOTFOUND = 22, /*!< transponder not found */ - ERR_NOTUNIQUE = 23, /*!< transponder not unique - more than one transponder in field */ - ERR_NOTSUPP = 24, /*!< requested operation not supported */ - ERR_WRITE = 25, /*!< write error */ - ERR_FIFO = 26, /*!< fifo over or underflow error */ - ERR_PAR = 27, /*!< parity error */ - ERR_DONE = 28, /*!< transfer has already finished */ - ERR_RF_COLLISION = - 29, /*!< collision error (Bit Collision or during RF Collision avoidance ) */ - ERR_HW_OVERRUN = 30, /*!< lost one or more received bytes */ - ERR_RELEASE_REQ = 31, /*!< device requested release */ - ERR_SLEEP_REQ = 32, /*!< device requested sleep */ - ERR_WRONG_STATE = 33, /*!< incorrent state for requested operation */ - ERR_MAX_RERUNS = 34, /*!< blocking procedure reached maximum runs */ - ERR_DISABLED = 35, /*!< operation aborted due to disabled configuration */ - ERR_HW_MISMATCH = 36, /*!< expected hw do not match */ - ERR_LINK_LOSS = - 37, /*!< Other device's field didn't behave as expected: turned off by Initiator in Passive mode, or AP2P did not turn on field */ - ERR_INVALID_HANDLE = 38, /*!< invalid or not initalized device handle */ - - ERR_INCOMPLETE_BYTE = 40, /*!< Incomplete byte rcvd */ - ERR_INCOMPLETE_BYTE_01 = 41, /*!< Incomplete byte rcvd - 1 bit */ - ERR_INCOMPLETE_BYTE_02 = 42, /*!< Incomplete byte rcvd - 2 bit */ - ERR_INCOMPLETE_BYTE_03 = 43, /*!< Incomplete byte rcvd - 3 bit */ - ERR_INCOMPLETE_BYTE_04 = 44, /*!< Incomplete byte rcvd - 4 bit */ - ERR_INCOMPLETE_BYTE_05 = 45, /*!< Incomplete byte rcvd - 5 bit */ - ERR_INCOMPLETE_BYTE_06 = 46, /*!< Incomplete byte rcvd - 6 bit */ - ERR_INCOMPLETE_BYTE_07 = 47, /*!< Incomplete byte rcvd - 7 bit */ -}; - -/* General Sub-category number */ -#define ERR_GENERIC_GRP (0x0000) /*!< Reserved value for generic error no */ -#define ERR_WARN_GRP (0x0100) /*!< Errors which are not expected in normal operation */ -#define ERR_PROCESS_GRP (0x0200) /*!< Processes management errors */ -#define ERR_SIO_GRP (0x0800) /*!< SIO errors due to logging */ -#define ERR_RINGBUF_GRP (0x0900) /*!< Ring Buffer errors */ -#define ERR_MQ_GRP (0x0A00) /*!< MQ errors */ -#define ERR_TIMER_GRP (0x0B00) /*!< Timer errors */ -#define ERR_RFAL_GRP (0x0C00) /*!< RFAL errors */ -#define ERR_UART_GRP (0x0D00) /*!< UART errors */ -#define ERR_SPI_GRP (0x0E00) /*!< SPI errors */ -#define ERR_I2C_GRP (0x0F00) /*!< I2c errors */ - -#define ERR_INSERT_SIO_GRP(x) (ERR_SIO_GRP | x) /*!< Insert the SIO grp */ -#define ERR_INSERT_RINGBUF_GRP(x) (ERR_RINGBUF_GRP | x) /*!< Insert the Ring Buffer grp */ -#define ERR_INSERT_RFAL_GRP(x) (ERR_RFAL_GRP | x) /*!< Insert the RFAL grp */ -#define ERR_INSERT_SPI_GRP(x) (ERR_SPI_GRP | x) /*!< Insert the spi grp */ -#define ERR_INSERT_I2C_GRP(x) (ERR_I2C_GRP | x) /*!< Insert the i2c grp */ -#define ERR_INSERT_UART_GRP(x) (ERR_UART_GRP | x) /*!< Insert the uart grp */ -#define ERR_INSERT_TIMER_GRP(x) (ERR_TIMER_GRP | x) /*!< Insert the timer grp */ -#define ERR_INSERT_MQ_GRP(x) (ERR_MQ_GRP | x) /*!< Insert the mq grp */ -#define ERR_INSERT_PROCESS_GRP(x) (ERR_PROCESS_GRP | x) /*!< Insert the process grp */ -#define ERR_INSERT_WARN_GRP(x) (ERR_WARN_GRP | x) /*!< Insert the i2c grp */ -#define ERR_INSERT_GENERIC_GRP(x) (ERR_GENERIC_GRP | x) /*!< Insert the generic grp */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -#define ERR_NO_MASK(x) (x & 0x00FF) /*!< Mask the error number */ - -/*! Common code to exit a function with the error if function f return error */ -#define EXIT_ON_ERR(r, f) \ - if(ERR_NONE != (r = f)) { \ - return r; \ - } - -#endif /* ST_ERRNO_H */ diff --git a/lib/ST25RFAL002/timer.c b/lib/ST25RFAL002/timer.c deleted file mode 100644 index ea0678a600..0000000000 --- a/lib/ST25RFAL002/timer.c +++ /dev/null @@ -1,105 +0,0 @@ -/****************************************************************************** - * @attention - * - *

© COPYRIGHT 2016 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ANSI C - */ - -/*! \file timer.c - * - * \brief SW Timer implementation - * - * \author Gustavo Patricio - * - * This module makes use of a System Tick in millisconds and provides - * an abstraction for SW timers - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "timer.h" -#include - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -static uint32_t timerStopwatchTick; - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -uint32_t timerCalculateTimer(uint16_t time) { - return (furi_get_tick() + time); -} - -/*******************************************************************************/ -bool timerIsExpired(uint32_t timer) { - uint32_t uDiff; - int32_t sDiff; - - uDiff = (timer - furi_get_tick()); /* Calculate the diff between the timers */ - sDiff = uDiff; /* Convert the diff to a signed var */ - - /* Check if the given timer has expired already */ - if(sDiff < 0) { - return true; - } - - return false; -} - -/*******************************************************************************/ -void timerDelay(uint16_t tOut) { - uint32_t t; - - /* Calculate the timer and wait blocking until is running */ - t = timerCalculateTimer(tOut); - while(timerIsRunning(t)) - ; -} - -/*******************************************************************************/ -void timerStopwatchStart(void) { - timerStopwatchTick = furi_get_tick(); -} - -/*******************************************************************************/ -uint32_t timerStopwatchMeasure(void) { - return (uint32_t)(furi_get_tick() - timerStopwatchTick); -} diff --git a/lib/ST25RFAL002/timer.h b/lib/ST25RFAL002/timer.h deleted file mode 100644 index b5f5eb329d..0000000000 --- a/lib/ST25RFAL002/timer.h +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once -/****************************************************************************** - * @attention - * - *

© COPYRIGHT 2016 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ANSI C - */ - -/*! \file timer.h - * - * \brief SW Timer implementation header file - * - * This module makes use of a System Tick in millisconds and provides - * an abstraction for SW timers - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include -#include - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -#define timerIsRunning(t) (!timerIsExpired(t)) - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Calculate Timer - * - * This method calculates when the timer will be expired given the amount - * time in milliseconds /a tOut. - * Once the timer has been calculated it will then be used to check when - * it expires. - * - * \see timersIsExpired - * - * \param[in] time : time/duration in Milliseconds for the timer - * - * \return u32 : The new timer calculated based on the given time - ***************************************************************************** - */ -uint32_t timerCalculateTimer(uint16_t time); - -/*! - ***************************************************************************** - * \brief Checks if a Timer is Expired - * - * This method checks if a timer has already expired. - * Based on the given timer previously calculated it checks if this timer - * has already elapsed - * - * \see timersCalculateTimer - * - * \param[in] timer : the timer to check - * - * \return true : timer has already expired - * \return false : timer is still running - ***************************************************************************** - */ -bool timerIsExpired(uint32_t timer); - -/*! - ***************************************************************************** - * \brief Performs a Delay - * - * This method performs a delay for the given amount of time in Milliseconds - * - * \param[in] time : time/duration in Milliseconds of the delay - * - ***************************************************************************** - */ -void timerDelay(uint16_t time); - -/*! - ***************************************************************************** - * \brief Stopwatch start - * - * This method initiates the stopwatch to later measure the time in ms - * - ***************************************************************************** - */ -void timerStopwatchStart(void); - -/*! - ***************************************************************************** - * \brief Stopwatch Measure - * - * This method returns the elapsed time in ms since the stopwatch was initiated - * - * \return The time in ms since the stopwatch was started - ***************************************************************************** - */ -uint32_t timerStopwatchMeasure(void); diff --git a/lib/ST25RFAL002/utils.h b/lib/ST25RFAL002/utils.h deleted file mode 100644 index 44a141986c..0000000000 --- a/lib/ST25RFAL002/utils.h +++ /dev/null @@ -1,100 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

© COPYRIGHT 2018 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: NFCC firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Ulrich Herrmann - * - * \brief Common and helpful macros - * - */ - -#ifndef UTILS_H -#define UTILS_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include -#include - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -/*! - * this macro evaluates an error variable \a ERR against an error code \a EC. - * in case it is not equal it jumps to the given label \a LABEL. - */ -#define EVAL_ERR_NE_GOTO(EC, ERR, LABEL) \ - if(EC != ERR) goto LABEL; - -/*! - * this macro evaluates an error variable \a ERR against an error code \a EC. - * in case it is equal it jumps to the given label \a LABEL. - */ -#define EVAL_ERR_EQ_GOTO(EC, ERR, LABEL) \ - if(EC == ERR) goto LABEL; -#define BITMASK_1 (0x01) /*!< Bit mask for lsb bit */ -#define BITMASK_2 (0x03) /*!< Bit mask for two lsb bits */ -#define BITMASK_3 (0x07) /*!< Bit mask for three lsb bits */ -#define BITMASK_4 (0x0F) /*!< Bit mask for four lsb bits */ -#define U16TOU8(a) ((a)&0x00FF) /*!< Cast 16-bit unsigned to 8-bit unsigned */ -#define GETU16(a) \ - (uint16_t)( \ - (a[0] << 8) | a[1]) /*!< Cast two Big Endian 8-bits byte array to 16-bits unsigned */ - -#define REVERSE_BYTES(pData, nDataSize) \ - unsigned char swap, *lo = pData, *hi = pData + nDataSize - 1; \ - while(lo < hi) { \ - swap = *lo; \ - *lo++ = *hi; \ - *hi-- = swap; \ - } - -#define ST_MEMMOVE memmove /*!< map memmove to string library code */ -#define ST_MEMCPY memcpy /*!< map memcpy to string library code */ -#define ST_MEMSET memset /*!< map memset to string library code */ -#define ST_BYTECMP memcmp /*!< map bytecmp to string library code */ - -#define NO_WARNING(v) ((void)(v)) /*!< Macro to suppress compiler warning */ - -#ifndef NULL -#define NULL (void*)0 /*!< represents a NULL pointer */ -#endif /* !NULL */ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -#endif /* UTILS_H */ diff --git a/lib/digital_signal/SConscript b/lib/digital_signal/SConscript index 2ddf7a58b0..b94c26f780 100644 --- a/lib/digital_signal/SConscript +++ b/lib/digital_signal/SConscript @@ -6,6 +6,7 @@ env.Append( ], SDK_HEADERS=[ File("digital_signal.h"), + File("digital_sequence.h"), ], ) diff --git a/lib/digital_signal/digital_sequence.c b/lib/digital_signal/digital_sequence.c new file mode 100644 index 0000000000..c85aae8c06 --- /dev/null +++ b/lib/digital_signal/digital_sequence.c @@ -0,0 +1,381 @@ +#include "digital_sequence.h" +#include "digital_signal_i.h" + +#include +#include + +#include +#include + +/** + * To enable debug output on an additional pin, set DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN to the required + * GpioPin variable. It can be passed at compile time via the --extra-define fbt switch. + * NOTE: This pin must be on the same GPIO port as the main pin. + * + * Example: + * ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3 + */ + +#define TAG "DigitalSequence" + +/* Special value used to indicate the end of DMA ring buffer. */ +#define DIGITAL_SEQUENCE_TIMER_MAX 0xFFFFFFFFUL + +/* Time to wait in loops before returning */ +#define DIGITAL_SEQUENCE_LOCK_WAIT_MS 10UL +#define DIGITAL_SEQUENCE_LOCK_WAIT_TICKS (DIGITAL_SEQUENCE_LOCK_WAIT_MS * 1000 * 64) + +#define DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE 2 + +/* Maximum capacity of the DMA ring buffer. */ +#define DIGITAL_SEQUENCE_RING_BUFFER_SIZE 128 + +#define DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE 2 + +/* Maximum amount of registered signals. */ +#define DIGITAL_SEQUENCE_BANK_SIZE 32 + +typedef enum { + DigitalSequenceStateIdle, + DigitalSequenceStateActive, +} DigitalSequenceState; + +typedef struct { + uint32_t data[DIGITAL_SEQUENCE_RING_BUFFER_SIZE]; + uint32_t write_pos; + uint32_t read_pos; +} DigitalSequenceRingBuffer; + +typedef uint32_t DigitalSequenceGpioBuffer[DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE]; + +typedef const DigitalSignal* DigitalSequenceSignalBank[DIGITAL_SEQUENCE_BANK_SIZE]; + +struct DigitalSequence { + const GpioPin* gpio; + + uint32_t size; + uint32_t max_size; + uint8_t* data; + + LL_DMA_InitTypeDef dma_config_gpio; + LL_DMA_InitTypeDef dma_config_timer; + + DigitalSequenceGpioBuffer gpio_buf; + DigitalSequenceRingBuffer timer_buf; + DigitalSequenceSignalBank signals; + DigitalSequenceState state; +}; + +DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) { + furi_assert(size); + furi_assert(gpio); + + DigitalSequence* sequence = malloc(sizeof(DigitalSequence)); + + sequence->gpio = gpio; + sequence->max_size = size; + + sequence->data = malloc(sequence->max_size); + + sequence->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t)&gpio->port->BSRR; + sequence->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)sequence->gpio_buf; + sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + sequence->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; + sequence->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + sequence->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + sequence->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + sequence->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + sequence->dma_config_gpio.NbData = DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE; + sequence->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + sequence->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; + + sequence->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t)&TIM2->ARR; + sequence->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)sequence->timer_buf.data; + sequence->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + sequence->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR; + sequence->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + sequence->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + sequence->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + sequence->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + sequence->dma_config_timer.NbData = DIGITAL_SEQUENCE_RING_BUFFER_SIZE; + sequence->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + sequence->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; + + return sequence; +} + +void digital_sequence_free(DigitalSequence* sequence) { + furi_assert(sequence); + + free(sequence->data); + free(sequence); +} + +void digital_sequence_register_signal( + DigitalSequence* sequence, + uint8_t signal_index, + const DigitalSignal* signal) { + furi_assert(sequence); + furi_assert(signal); + furi_assert(signal_index < DIGITAL_SEQUENCE_BANK_SIZE); + + sequence->signals[signal_index] = signal; +} + +void digital_sequence_add_signal(DigitalSequence* sequence, uint8_t signal_index) { + furi_assert(sequence); + furi_assert(signal_index < DIGITAL_SEQUENCE_BANK_SIZE); + furi_assert(sequence->size < sequence->max_size); + + sequence->data[sequence->size++] = signal_index; +} + +static inline void digital_sequence_start_dma(DigitalSequence* sequence) { + furi_assert(sequence); + + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &sequence->dma_config_gpio); + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &sequence->dma_config_timer); + + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); +} + +static inline void digital_sequence_stop_dma() { + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_ClearFlag_TC1(DMA1); + LL_DMA_ClearFlag_TC2(DMA1); +} + +static inline void digital_sequence_start_timer() { + furi_hal_bus_enable(FuriHalBusTIM2); + + LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetPrescaler(TIM2, 0); + LL_TIM_SetAutoReload(TIM2, DIGITAL_SEQUENCE_TIMER_MAX); + LL_TIM_SetCounter(TIM2, 0); + + LL_TIM_EnableCounter(TIM2); + LL_TIM_EnableUpdateEvent(TIM2); + LL_TIM_EnableDMAReq_UPDATE(TIM2); + LL_TIM_GenerateEvent_UPDATE(TIM2); +} + +static void digital_sequence_stop_timer() { + LL_TIM_DisableCounter(TIM2); + LL_TIM_DisableUpdateEvent(TIM2); + LL_TIM_DisableDMAReq_UPDATE(TIM2); + + furi_hal_bus_disable(FuriHalBusTIM2); +} + +static inline void digital_sequence_init_gpio_buffer( + DigitalSequence* sequence, + const DigitalSignal* first_signal) { + const uint32_t bit_set = sequence->gpio->pin << GPIO_BSRR_BS0_Pos +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + | DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << GPIO_BSRR_BS0_Pos +#endif + ; + + const uint32_t bit_reset = sequence->gpio->pin << GPIO_BSRR_BR0_Pos +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + | DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << GPIO_BSRR_BR0_Pos +#endif + ; + + if(first_signal->start_level) { + sequence->gpio_buf[0] = bit_set; + sequence->gpio_buf[1] = bit_reset; + } else { + sequence->gpio_buf[0] = bit_reset; + sequence->gpio_buf[1] = bit_set; + } +} + +static inline void digital_sequence_finish(DigitalSequence* sequence) { + if(sequence->state == DigitalSequenceStateActive) { + const uint32_t prev_timer = DWT->CYCCNT; + + do { + /* Special value has been loaded into the timer, signaling the end of transmission. */ + if(TIM2->ARR == DIGITAL_SEQUENCE_TIMER_MAX) { + break; + } + + if(DWT->CYCCNT - prev_timer > DIGITAL_SEQUENCE_LOCK_WAIT_TICKS) { + DigitalSequenceRingBuffer* dma_buffer = &sequence->timer_buf; + dma_buffer->read_pos = DIGITAL_SEQUENCE_RING_BUFFER_SIZE - + LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); + FURI_LOG_D( + TAG, + "[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)", + DIGITAL_SEQUENCE_LOCK_WAIT_MS, + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + } while(true); + } + + digital_sequence_stop_timer(); + digital_sequence_stop_dma(); +} + +static inline void digital_sequence_enqueue_period(DigitalSequence* sequence, uint32_t length) { + DigitalSequenceRingBuffer* dma_buffer = &sequence->timer_buf; + + if(sequence->state == DigitalSequenceStateActive) { + const uint32_t prev_timer = DWT->CYCCNT; + + do { + dma_buffer->read_pos = + DIGITAL_SEQUENCE_RING_BUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); + + const uint32_t size_free = (DIGITAL_SEQUENCE_RING_BUFFER_SIZE + dma_buffer->read_pos - + dma_buffer->write_pos) % + DIGITAL_SEQUENCE_RING_BUFFER_SIZE; + + if(size_free > DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE) { + break; + } + + if(DWT->CYCCNT - prev_timer > DIGITAL_SEQUENCE_LOCK_WAIT_TICKS) { + FURI_LOG_D( + TAG, + "[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)", + DIGITAL_SEQUENCE_LOCK_WAIT_MS, + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + + if(TIM2->ARR == DIGITAL_SEQUENCE_TIMER_MAX) { + FURI_LOG_D( + TAG, + "[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)", + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + } while(true); + } + + dma_buffer->data[dma_buffer->write_pos] = length; + + dma_buffer->write_pos += 1; + dma_buffer->write_pos %= DIGITAL_SEQUENCE_RING_BUFFER_SIZE; + + dma_buffer->data[dma_buffer->write_pos] = DIGITAL_SEQUENCE_TIMER_MAX; +} + +static inline void digital_sequence_timer_buffer_reset(DigitalSequence* sequence) { + sequence->timer_buf.data[0] = DIGITAL_SEQUENCE_TIMER_MAX; + sequence->timer_buf.read_pos = 0; + sequence->timer_buf.write_pos = 0; +} + +void digital_sequence_transmit(DigitalSequence* sequence) { + furi_assert(sequence); + furi_assert(sequence->size); + furi_assert(sequence->state == DigitalSequenceStateIdle); + + FURI_CRITICAL_ENTER(); + + furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + furi_hal_gpio_init( + &DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#endif + + const DigitalSignal* signal_current = sequence->signals[sequence->data[0]]; + + digital_sequence_init_gpio_buffer(sequence, signal_current); + + int32_t remainder_ticks = 0; + uint32_t reload_value_carry = 0; + uint32_t next_signal_index = 1; + + for(;;) { + const DigitalSignal* signal_next = + (next_signal_index < sequence->size) ? + sequence->signals[sequence->data[next_signal_index++]] : + NULL; + + for(uint32_t i = 0; i < signal_current->size; i++) { + const bool is_last_value = (i == signal_current->size - 1); + const uint32_t reload_value = signal_current->data[i] + reload_value_carry; + + reload_value_carry = 0; + + if(is_last_value) { + if(signal_next != NULL) { + /* Special case: signal boundary. Depending on whether the adjacent levels are equal or not, + * they will be combined to a single one or handled separately. */ + const bool end_level = signal_current->start_level ^ + ((signal_current->size % 2) == 0); + + /* If the adjacent levels are equal, carry the current period duration over to the next signal. */ + if(end_level == signal_next->start_level) { + reload_value_carry = reload_value; + } + } else { + /** Special case: during the last period of the last signal, hold the output level indefinitely. + * @see digital_signal.h + * + * Setting reload_value_carry to a non-zero value will prevent the respective period from being + * added to the DMA ring buffer. */ + reload_value_carry = 1; + } + } + + /* A non-zero reload_value_carry means that the level was the same on the both sides of the signal boundary + * and the two respective periods were combined to one. */ + if(reload_value_carry == 0) { + digital_sequence_enqueue_period(sequence, reload_value); + } + + if(sequence->state == DigitalSequenceStateIdle) { + const bool is_buffer_filled = sequence->timer_buf.write_pos >= + (DIGITAL_SEQUENCE_RING_BUFFER_SIZE - + DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE); + const bool is_end_of_data = (signal_next == NULL) && is_last_value; + + if(is_buffer_filled || is_end_of_data) { + digital_sequence_start_dma(sequence); + digital_sequence_start_timer(); + sequence->state = DigitalSequenceStateActive; + } + } + } + + /* Exit the loop here when no further signals are available */ + if(signal_next == NULL) break; + + /* Prevent the rounding error from accumulating by distributing it across multiple periods. */ + remainder_ticks += signal_current->remainder; + if(remainder_ticks >= DIGITAL_SIGNAL_T_TIM_DIV2) { + remainder_ticks -= DIGITAL_SIGNAL_T_TIM; + reload_value_carry += 1; + } + + signal_current = signal_next; + }; + + digital_sequence_finish(sequence); + digital_sequence_timer_buffer_reset(sequence); + + FURI_CRITICAL_EXIT(); + + sequence->state = DigitalSequenceStateIdle; +} + +void digital_sequence_clear(DigitalSequence* sequence) { + furi_assert(sequence); + + sequence->size = 0; +} diff --git a/lib/digital_signal/digital_sequence.h b/lib/digital_signal/digital_sequence.h new file mode 100644 index 0000000000..4f04ecc464 --- /dev/null +++ b/lib/digital_signal/digital_sequence.h @@ -0,0 +1,112 @@ +/** + * @file digital_sequence.h + * @brief Fast and precise digital signal generation library. + * + * Each sequence is represented by one or more (up to 32) registered signals, which are addressed + * by their indices, and a list of signal indices to be transmitted. + * + * The registered signals must be set up prior to actually defining the sequence. + * + * Example: A sequence containing 4 registered signals and n indices to transmit. + * + * |Signal | Index | + * |:-----:|:-----:| + * | SOF | 0 | + * | EOF | 1 | + * | Zero | 2 | + * | One | 3 | + * + * ``` + * Signal index | 0 | 3 | 2 | 2 | ... | 3 | 1 | + * 0 1 2 3 ... n - 2 n - 1 + * ``` + * + * The above sequence starts by transmitting the signal with index 0, which is SOF in this case, + * then it proceeds with indices 3, 2, 2, which are One, Zero, Zero and after n - 2 signals, + * it will conclude with indices 3 and 1 which are One and EOF respectively. + * + * This way, only the order in which the signals are sent is stored, while the signals themselves + * are not duplicated. + */ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DigitalSequence DigitalSequence; + +/** + * @brief Allocate a DigitalSequence instance of a given size which will operate on a set GPIO pin. + * + * @param[in] size maximum number of signal indices contained in the instance. + * @param[in] gpio the GPIO pin used to generate the signal. + * @returns pointer to the allocated DigitalSequence instance. + */ +DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio); + +/** + * @brief Delete a previously allocated DigitalSequence instance. + * + * @param[in,out] sequence pointer to the instance to be deleted. + */ +void digital_sequence_free(DigitalSequence* sequence); + +/** + * @brief Register a signal within a DigitalSequence instance by its index. + * + * This function must be called for each signal to be used in the sequence. The DigitalSequence + * instance does not own the signals, therefore, their lifetime must be no less than the instance's. + * + * The user is responsible for creation and deletion of DigitalSignal instances and + * also for keeping track of their respective indices. + * + * @param[in,out] sequence pointer to the instance to be modified. + * @param[in] signal_index index to register the signal under (must be less than 32). + * @param[in] signal pointer to the DigitalSignal instance to be registered. + */ +void digital_sequence_register_signal( + DigitalSequence* sequence, + uint8_t signal_index, + const DigitalSignal* signal); + +/** + * @brief Append a signal index to a DigitalSequence instance. + * + * The signal under the index must be registered beforehand by calling digital_sequence_set_signal(). + * + * @param[in,out] sequence pointer to the instance to be modified. + * @param[in] signal_index signal index to be appended to the sequence (must be less than 32). + */ +void digital_sequence_add_signal(DigitalSequence* sequence, uint8_t signal_index); + +/** + * @brief Transmit the sequence contained in the DigitalSequence instance. + * + * Must contain at least one registered signal and one signal index. + * + * NOTE: The current implementation will properly initialise the GPIO provided during construction, + * but it is the caller's responsibility to reconfigure it back before reusing for other purposes. + * This is due to performance reasons. + * + * @param[in] sequence pointer to the sequence to be transmitted. + */ +void digital_sequence_transmit(DigitalSequence* sequence); + +/** + * @brief Clear the signal sequence in a DigitalSequence instance. + * + * Calling this function does not un-register the registered signals, so it is + * safe to call digital_sequence_add_signal() right afterwards. + * + * @param[in,out] sequence pointer to the instance to be cleared. + */ +void digital_sequence_clear(DigitalSequence* sequence); + +#ifdef __cplusplus +} +#endif diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 25adb878b7..a0408900de 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -1,106 +1,15 @@ #include "digital_signal.h" +#include "digital_signal_i.h" #include -#include -#include -#include - -#include -#include - -/* must be on bank B */ -// For debugging purposes use `--extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3` fbt option - -struct ReloadBuffer { - uint32_t* buffer; /* DMA ringbuffer */ - uint32_t size; /* maximum entry count of the ring buffer */ - uint32_t write_pos; /* current buffer write index */ - uint32_t read_pos; /* current buffer read index */ - bool dma_active; -}; - -struct DigitalSequence { - uint8_t signals_size; - bool bake; - uint32_t sequence_used; - uint32_t sequence_size; - DigitalSignal** signals; - uint8_t* sequence; - const GpioPin* gpio; - uint32_t send_time; - bool send_time_active; - LL_DMA_InitTypeDef dma_config_gpio; - LL_DMA_InitTypeDef dma_config_timer; - uint32_t* gpio_buff; - struct ReloadBuffer* dma_buffer; -}; - -struct DigitalSignalInternals { - uint64_t factor; - uint32_t reload_reg_entries; - uint32_t reload_reg_remainder; - uint32_t gpio_buff[2]; - const GpioPin* gpio; - LL_DMA_InitTypeDef dma_config_gpio; - LL_DMA_InitTypeDef dma_config_timer; -}; #define TAG "DigitalSignal" -#define F_TIM (64000000.0) -#define T_TIM 1562 /* 15.625 ns *100 */ -#define T_TIM_DIV2 781 /* 15.625 ns / 2 *100 */ - -/* end marker in DMA ringbuffer, will get written into timer register at the end */ -#define SEQ_TIMER_MAX 0xFFFFFFFF - -/* time to wait in loops before returning */ -#define SEQ_LOCK_WAIT_MS 10UL -#define SEQ_LOCK_WAIT_TICKS (SEQ_LOCK_WAIT_MS * 1000 * 64) - -/* maximum entry count of the sequence dma ring buffer */ -#define RINGBUFFER_SIZE 128 - -/* maximum number of DigitalSignals in a sequence */ -#define SEQUENCE_SIGNALS_SIZE 32 -/* - * if sequence size runs out from the initial value passed to digital_sequence_alloc - * the size will be increased by this amount and reallocated - */ -#define SEQUENCE_SIZE_REALLOCATE_INCREMENT 256 - -DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { +DigitalSignal* digital_signal_alloc(uint32_t max_size) { DigitalSignal* signal = malloc(sizeof(DigitalSignal)); - signal->start_level = true; - signal->edges_max_cnt = max_edges_cnt; - signal->edge_timings = malloc(signal->edges_max_cnt * sizeof(uint32_t)); - signal->edge_cnt = 0; - signal->reload_reg_buff = malloc(signal->edges_max_cnt * sizeof(uint32_t)); - - signal->internals = malloc(sizeof(DigitalSignalInternals)); - DigitalSignalInternals* internals = signal->internals; - internals->factor = 1024 * 1024; - - internals->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - internals->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; - internals->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - internals->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - internals->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - internals->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - internals->dma_config_gpio.NbData = 2; - internals->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - internals->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; - - internals->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); - internals->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - internals->dma_config_timer.Mode = LL_DMA_MODE_NORMAL; - internals->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - internals->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - internals->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - internals->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - internals->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - internals->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; + signal->max_size = max_size; + signal->data = malloc(max_size * sizeof(uint32_t)); return signal; } @@ -108,559 +17,81 @@ DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { void digital_signal_free(DigitalSignal* signal) { furi_assert(signal); - free(signal->edge_timings); - free(signal->reload_reg_buff); - free(signal->internals); + free(signal->data); free(signal); } -bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) { - furi_assert(signal_a); - furi_assert(signal_b); - - if(signal_a->edges_max_cnt < signal_a->edge_cnt + signal_b->edge_cnt) { - return false; - } - /* in case there are no edges in our target signal, the signal to append makes the rules */ - if(!signal_a->edge_cnt) { - signal_a->start_level = signal_b->start_level; - } - bool end_level = signal_a->start_level; - if(signal_a->edge_cnt) { - end_level = signal_a->start_level ^ !(signal_a->edge_cnt % 2); - } - uint8_t start_copy = 0; - if(end_level == signal_b->start_level) { - if(signal_a->edge_cnt) { - signal_a->edge_timings[signal_a->edge_cnt - 1] += signal_b->edge_timings[0]; - start_copy += 1; - } else { - signal_a->edge_timings[signal_a->edge_cnt] += signal_b->edge_timings[0]; - } - } - - for(size_t i = 0; i < signal_b->edge_cnt - start_copy; i++) { - signal_a->edge_timings[signal_a->edge_cnt + i] = signal_b->edge_timings[start_copy + i]; - } - signal_a->edge_cnt += signal_b->edge_cnt - start_copy; - - return true; -} - -bool digital_signal_get_start_level(DigitalSignal* signal) { +bool digital_signal_get_start_level(const DigitalSignal* signal) { furi_assert(signal); return signal->start_level; } -uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) { +void digital_signal_set_start_level(DigitalSignal* signal, bool level) { furi_assert(signal); - return signal->edge_cnt; + signal->start_level = level; } -void digital_signal_add(DigitalSignal* signal, uint32_t ticks) { +uint32_t digital_signal_get_size(const DigitalSignal* signal) { furi_assert(signal); - furi_assert(signal->edge_cnt < signal->edges_max_cnt); - signal->edge_timings[signal->edge_cnt++] = ticks; + return signal->size; } -void digital_signal_add_pulse(DigitalSignal* signal, uint32_t ticks, bool level) { +void digital_signal_add_period(DigitalSignal* signal, uint32_t ticks) { furi_assert(signal); - furi_assert(signal->edge_cnt < signal->edges_max_cnt); + furi_assert(signal->size < signal->max_size); - /* virgin signal? add it as the only level */ - if(signal->edge_cnt == 0) { - signal->start_level = level; - signal->edge_timings[signal->edge_cnt++] = ticks; - } else { - bool end_level = signal->start_level ^ !(signal->edge_cnt % 2); + const uint32_t duration = ticks + signal->remainder; - if(level != end_level) { - signal->edge_timings[signal->edge_cnt++] = ticks; - } else { - signal->edge_timings[signal->edge_cnt - 1] += ticks; - } - } -} + uint32_t reload_value = duration / DIGITAL_SIGNAL_T_TIM; + int32_t remainder = duration - reload_value * DIGITAL_SIGNAL_T_TIM; -uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { - furi_assert(signal); - furi_assert(edge_num < signal->edge_cnt); - - return signal->edge_timings[edge_num]; -} - -void digital_signal_prepare_arr(DigitalSignal* signal) { - furi_assert(signal); - - DigitalSignalInternals* internals = signal->internals; - - /* set up signal polarities */ - if(internals->gpio) { - uint32_t bit_set = internals->gpio->pin; - uint32_t bit_reset = internals->gpio->pin << 16; - -#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN - bit_set |= DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin; - bit_reset |= DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << 16; -#endif - - if(signal->start_level) { - internals->gpio_buff[0] = bit_set; - internals->gpio_buff[1] = bit_reset; - } else { - internals->gpio_buff[0] = bit_reset; - internals->gpio_buff[1] = bit_set; - } + if(remainder >= DIGITAL_SIGNAL_T_TIM_DIV2) { + reload_value += 1; + remainder -= DIGITAL_SIGNAL_T_TIM; } - /* set up edge timings */ - internals->reload_reg_entries = 0; - - for(size_t pos = 0; pos < signal->edge_cnt; pos++) { - uint32_t pulse_duration = signal->edge_timings[pos] + internals->reload_reg_remainder; - if(pulse_duration < 10 || pulse_duration > 10000000) { - FURI_LOG_D( - TAG, - "[prepare] pulse_duration out of range: %lu = %lu * %llu", - pulse_duration, - signal->edge_timings[pos], - internals->factor); - pulse_duration = 100; - } - uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM; - internals->reload_reg_remainder = pulse_duration - (pulse_ticks * T_TIM); + furi_check(reload_value > 1); - if(pulse_ticks > 1) { - signal->reload_reg_buff[internals->reload_reg_entries++] = pulse_ticks - 1; - } - } + signal->data[signal->size++] = reload_value - 1; + signal->remainder = remainder; } -static void digital_signal_stop_dma() { - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); - LL_DMA_ClearFlag_TC1(DMA1); - LL_DMA_ClearFlag_TC2(DMA1); -} +static void digital_signal_extend_last_period(DigitalSignal* signal, uint32_t ticks) { + furi_assert(signal->size <= signal->max_size); -static void digital_signal_stop_timer() { - LL_TIM_DisableCounter(TIM2); - LL_TIM_DisableUpdateEvent(TIM2); - LL_TIM_DisableDMAReq_UPDATE(TIM2); + const uint32_t reload_value_old = signal->data[signal->size - 1] + 1; + const uint32_t duration = ticks + signal->remainder + reload_value_old * DIGITAL_SIGNAL_T_TIM; - furi_hal_bus_disable(FuriHalBusTIM2); -} - -static void digital_signal_setup_timer() { - furi_hal_bus_enable(FuriHalBusTIM2); + uint32_t reload_value = duration / DIGITAL_SIGNAL_T_TIM; + int32_t remainder = duration - reload_value * DIGITAL_SIGNAL_T_TIM; - LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); - LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); - LL_TIM_SetPrescaler(TIM2, 0); - LL_TIM_SetAutoReload(TIM2, SEQ_TIMER_MAX); - LL_TIM_SetCounter(TIM2, 0); -} - -static void digital_signal_start_timer() { - LL_TIM_EnableCounter(TIM2); - LL_TIM_EnableUpdateEvent(TIM2); - LL_TIM_EnableDMAReq_UPDATE(TIM2); - LL_TIM_GenerateEvent_UPDATE(TIM2); -} - -static bool digital_signal_setup_dma(DigitalSignal* signal) { - furi_assert(signal); - DigitalSignalInternals* internals = signal->internals; - - if(!signal->internals->reload_reg_entries) { - return false; + if(remainder >= DIGITAL_SIGNAL_T_TIM_DIV2) { + reload_value += 1; + remainder -= DIGITAL_SIGNAL_T_TIM; } - digital_signal_stop_dma(); - - internals->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)internals->gpio_buff; - internals->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (internals->gpio->port->BSRR); - internals->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff; - internals->dma_config_timer.NbData = signal->internals->reload_reg_entries; - - /* set up DMA channel 1 and 2 for GPIO and timer copy operations */ - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &internals->dma_config_gpio); - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &internals->dma_config_timer); - /* enable both DMA channels */ - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + furi_check(reload_value > 1); - return true; + signal->data[signal->size - 1] = reload_value - 1; + signal->remainder = remainder; } -void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { +void digital_signal_add_period_with_level(DigitalSignal* signal, uint32_t ticks, bool level) { furi_assert(signal); - if(!signal->edge_cnt) { - return; - } - - /* Configure gpio as output */ - signal->internals->gpio = gpio; - furi_hal_gpio_init( - signal->internals->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - - digital_signal_prepare_arr(signal); - - digital_signal_setup_dma(signal); - digital_signal_setup_timer(); - digital_signal_start_timer(); - - while(!LL_DMA_IsActiveFlag_TC2(DMA1)) { - } - - digital_signal_stop_timer(); - digital_signal_stop_dma(); -} - -static void digital_sequence_alloc_signals(DigitalSequence* sequence, uint32_t size) { - sequence->signals_size = size; - sequence->signals = malloc(sequence->signals_size * sizeof(DigitalSignal*)); -} - -static void digital_sequence_alloc_sequence(DigitalSequence* sequence, uint32_t size) { - sequence->sequence_used = 0; - sequence->sequence_size = size; - sequence->sequence = malloc(sequence->sequence_size); - sequence->send_time = 0; - sequence->send_time_active = false; -} - -DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) { - furi_assert(gpio); - - DigitalSequence* sequence = malloc(sizeof(DigitalSequence)); - - sequence->gpio = gpio; - sequence->bake = false; - - sequence->dma_buffer = malloc(sizeof(struct ReloadBuffer)); - sequence->dma_buffer->size = RINGBUFFER_SIZE; - sequence->dma_buffer->buffer = malloc(sequence->dma_buffer->size * sizeof(uint32_t)); - - sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - sequence->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; - sequence->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - sequence->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - sequence->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - sequence->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - sequence->dma_config_gpio.NbData = 2; - sequence->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - sequence->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; - - sequence->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - sequence->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR; - sequence->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - sequence->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - sequence->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - sequence->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - sequence->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); - sequence->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)sequence->dma_buffer->buffer; - sequence->dma_config_timer.NbData = sequence->dma_buffer->size; - sequence->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - sequence->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; - - digital_sequence_alloc_signals(sequence, SEQUENCE_SIGNALS_SIZE); - digital_sequence_alloc_sequence(sequence, size); - - return sequence; -} - -void digital_sequence_free(DigitalSequence* sequence) { - furi_assert(sequence); - - free(sequence->signals); - free(sequence->sequence); - free(sequence->dma_buffer->buffer); - free(sequence->dma_buffer); - free(sequence); -} - -void digital_sequence_set_signal( - DigitalSequence* sequence, - uint8_t signal_index, - DigitalSignal* signal) { - furi_assert(sequence); - furi_assert(signal); - furi_assert(signal_index < sequence->signals_size); - - sequence->signals[signal_index] = signal; - signal->internals->gpio = sequence->gpio; - signal->internals->reload_reg_remainder = 0; - - digital_signal_prepare_arr(signal); -} - -void digital_sequence_set_sendtime(DigitalSequence* sequence, uint32_t send_time) { - furi_assert(sequence); - - sequence->send_time = send_time; - sequence->send_time_active = true; -} - -void digital_sequence_add(DigitalSequence* sequence, uint8_t signal_index) { - furi_assert(sequence); - furi_assert(signal_index < sequence->signals_size); - - if(sequence->sequence_used >= sequence->sequence_size) { - sequence->sequence_size += SEQUENCE_SIZE_REALLOCATE_INCREMENT; - sequence->sequence = realloc(sequence->sequence, sequence->sequence_size); //-V701 - furi_assert(sequence->sequence); - } - - sequence->sequence[sequence->sequence_used++] = signal_index; -} - -static bool digital_sequence_setup_dma(DigitalSequence* sequence) { - furi_assert(sequence); - - digital_signal_stop_dma(); - - sequence->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)sequence->gpio_buff; - sequence->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (sequence->gpio->port->BSRR); - - /* set up DMA channel 1 and 2 for GPIO and timer copy operations */ - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &sequence->dma_config_gpio); - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &sequence->dma_config_timer); - - /* enable both DMA channels */ - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); - - return true; -} - -static DigitalSignal* digital_sequence_bake(DigitalSequence* sequence) { - furi_assert(sequence); - - uint32_t edges = 0; - - for(uint32_t pos = 0; pos < sequence->sequence_used; pos++) { - uint8_t signal_index = sequence->sequence[pos]; - DigitalSignal* sig = sequence->signals[signal_index]; - - edges += sig->edge_cnt; - } - - DigitalSignal* ret = digital_signal_alloc(edges); - - for(uint32_t pos = 0; pos < sequence->sequence_used; pos++) { - uint8_t signal_index = sequence->sequence[pos]; - DigitalSignal* sig = sequence->signals[signal_index]; - - digital_signal_append(ret, sig); - } - - return ret; -} - -static void digital_sequence_finish(DigitalSequence* sequence) { - struct ReloadBuffer* dma_buffer = sequence->dma_buffer; - - if(dma_buffer->dma_active) { - uint32_t prev_timer = DWT->CYCCNT; - do { - /* we are finished, when the DMA transferred the SEQ_TIMER_MAX marker */ - if(TIM2->ARR == SEQ_TIMER_MAX) { - break; - } - if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) { - dma_buffer->read_pos = - RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); - FURI_LOG_D( - TAG, - "[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)", - SEQ_LOCK_WAIT_MS, - TIM2->ARR, - dma_buffer->read_pos, - dma_buffer->write_pos); - break; - } - } while(1); - } - - digital_signal_stop_timer(); - digital_signal_stop_dma(); -} - -static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t length) { - struct ReloadBuffer* dma_buffer = sequence->dma_buffer; - - if(dma_buffer->dma_active) { - uint32_t prev_timer = DWT->CYCCNT; - do { - dma_buffer->read_pos = RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); - - uint32_t free = - (RINGBUFFER_SIZE + dma_buffer->read_pos - dma_buffer->write_pos) % RINGBUFFER_SIZE; - - if(free > 2) { - break; - } - - if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) { - FURI_LOG_D( - TAG, - "[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)", - SEQ_LOCK_WAIT_MS, - TIM2->ARR, - dma_buffer->read_pos, - dma_buffer->write_pos); - break; - } - if(TIM2->ARR == SEQ_TIMER_MAX) { - FURI_LOG_D( - TAG, - "[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)", - TIM2->ARR, - dma_buffer->read_pos, - dma_buffer->write_pos); - break; - } - } while(1); - } - - dma_buffer->buffer[dma_buffer->write_pos] = length; - dma_buffer->write_pos++; - dma_buffer->write_pos %= RINGBUFFER_SIZE; - dma_buffer->buffer[dma_buffer->write_pos] = SEQ_TIMER_MAX; -} - -bool digital_sequence_send(DigitalSequence* sequence) { - furi_assert(sequence); - - struct ReloadBuffer* dma_buffer = sequence->dma_buffer; - - furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN - furi_hal_gpio_init( - &DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -#endif - - if(sequence->bake) { - DigitalSignal* sig = digital_sequence_bake(sequence); - - digital_signal_send(sig, sequence->gpio); - digital_signal_free(sig); - return true; - } - - if(!sequence->sequence_used) { - return false; - } - - int32_t remainder = 0; - uint32_t trade_for_next = 0; - uint32_t seq_pos_next = 1; - - dma_buffer->dma_active = false; - dma_buffer->buffer[0] = SEQ_TIMER_MAX; - dma_buffer->read_pos = 0; - dma_buffer->write_pos = 0; - - /* already prepare the current signal pointer */ - DigitalSignal* sig = sequence->signals[sequence->sequence[0]]; - DigitalSignal* sig_next = NULL; - /* re-use the GPIO buffer from the first signal */ - sequence->gpio_buff = sig->internals->gpio_buff; - - FURI_CRITICAL_ENTER(); - - while(sig) { - bool last_signal = (seq_pos_next >= sequence->sequence_used); - - if(!last_signal) { - sig_next = sequence->signals[sequence->sequence[seq_pos_next++]]; - } - - for(uint32_t pulse_pos = 0; pulse_pos < sig->internals->reload_reg_entries; pulse_pos++) { - bool last_pulse = ((pulse_pos + 1) >= sig->internals->reload_reg_entries); - uint32_t pulse_length = sig->reload_reg_buff[pulse_pos] + trade_for_next; - - trade_for_next = 0; - - /* when we are too late more than half a tick, make the first edge temporarily longer */ - if(remainder >= T_TIM_DIV2) { - remainder -= T_TIM; - pulse_length += 1; - } - - /* last pulse in current signal and have a next signal? */ - if(last_pulse && sig_next) { - /* when a signal ends with the same level as the next signal begins, let the next signal generate the whole pulse. - beware, we do not want the level after the last edge, but the last level before that edge */ - bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0); - - /* if they have the same level, pass the duration to the next pulse(s) */ - if(end_level == sig_next->start_level) { - trade_for_next = pulse_length; - } - } - - /* if it was decided, that the next signal's first pulse shall also handle our "length", then do not queue here */ - if(!trade_for_next) { - digital_sequence_queue_pulse(sequence, pulse_length); - - if(!dma_buffer->dma_active) { - /* start transmission when buffer was filled enough */ - bool start_send = sequence->dma_buffer->write_pos >= (RINGBUFFER_SIZE - 2); - - /* or it was the last pulse */ - if(last_pulse && last_signal) { - start_send = true; - } - - /* start transmission */ - if(start_send) { - digital_sequence_setup_dma(sequence); - digital_signal_setup_timer(); - - /* if the send time is specified, wait till the core timer passed beyond that time */ - if(sequence->send_time_active) { - sequence->send_time_active = false; - while(sequence->send_time - DWT->CYCCNT < 0x80000000) { - } - } - digital_signal_start_timer(); - dma_buffer->dma_active = true; - } - } - } - } - - remainder += sig->internals->reload_reg_remainder; - sig = sig_next; - sig_next = NULL; - } - - /* wait until last dma transaction was finished */ - FURI_CRITICAL_EXIT(); - digital_sequence_finish(sequence); - - return true; -} - -void digital_sequence_clear(DigitalSequence* sequence) { - furi_assert(sequence); - - sequence->sequence_used = 0; -} - -void digital_sequence_timebase_correction(DigitalSequence* sequence, float factor) { - for(uint32_t sig_pos = 0; sig_pos < sequence->signals_size; sig_pos++) { - DigitalSignal* signal = sequence->signals[sig_pos]; + if(signal->size == 0) { + signal->start_level = level; + digital_signal_add_period(signal, ticks); + } else { + const bool end_level = signal->start_level ^ !(signal->size % 2); - if(signal) { - signal->internals->factor = (uint32_t)(1024 * 1024 * factor); - digital_signal_prepare_arr(signal); + if(level != end_level) { + digital_signal_add_period(signal, ticks); + } else { + digital_signal_extend_last_period(signal, ticks); } } } diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h index 404d02605e..f29facfd83 100644 --- a/lib/digital_signal/digital_signal.h +++ b/lib/digital_signal/digital_signal.h @@ -1,74 +1,121 @@ +/** + * @file digital_signal.h + * @brief Simple digital signal container for the DigitalSequence library. + * + * Each signal is represented by its start level (high or low) and one or more periods. + * The output will transition to its inverse value on each period boundary. + * + * Example: A signal with n periods and HIGH start level. + * + * ``` + * ----+ +------+ +- ... -+ + * t0 | t1 | t2 | t3 | | tn - 1 + * +--------+ +----+ +-------- + * ``` + * + */ #pragma once #include -#include #include -#include - #ifdef __cplusplus extern "C" { #endif -/* helper for easier signal generation */ +// DigitalSignal uses 10 picosecond time units (1 tick = 10 ps). +// Use the macros below to convert the time from other units. + #define DIGITAL_SIGNAL_MS(x) ((x)*100000000UL) #define DIGITAL_SIGNAL_US(x) ((x)*100000UL) #define DIGITAL_SIGNAL_NS(x) ((x)*100UL) #define DIGITAL_SIGNAL_PS(x) ((x) / 10UL) -/* using an anonymous type for the internals */ -typedef struct DigitalSignalInternals DigitalSignalInternals; - -/* and a public one for accessing user-side fields */ -typedef struct DigitalSignal { - bool start_level; - uint32_t edge_cnt; - uint32_t edges_max_cnt; - uint32_t* edge_timings; - uint32_t* reload_reg_buff; /* internal, but used by unit tests */ - DigitalSignalInternals* internals; -} DigitalSignal; - -typedef struct DigitalSequence DigitalSequence; - -DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt); - +typedef struct DigitalSignal DigitalSignal; + +/** + * @brief Allocate a DigitalSignal instance with a defined maximum size. + * + * @param[in] max_size the maximum number of periods the instance will be able to contain. + * @returns pointer to the allocated instance. + */ +DigitalSignal* digital_signal_alloc(uint32_t max_size); + +/** + * @brief Delete a previously allocated DigitalSignal instance. + * + * @param[in,out] signal pointer to the instance to be deleted. + */ void digital_signal_free(DigitalSignal* signal); -void digital_signal_add(DigitalSignal* signal, uint32_t ticks); - -void digital_signal_add_pulse(DigitalSignal* signal, uint32_t ticks, bool level); - -bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b); - -void digital_signal_prepare_arr(DigitalSignal* signal); - -bool digital_signal_get_start_level(DigitalSignal* signal); - -uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal); - -uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); - -void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); - -DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio); - -void digital_sequence_free(DigitalSequence* sequence); - -void digital_sequence_set_signal( - DigitalSequence* sequence, - uint8_t signal_index, - DigitalSignal* signal); - -void digital_sequence_set_sendtime(DigitalSequence* sequence, uint32_t send_time); - -void digital_sequence_add(DigitalSequence* sequence, uint8_t signal_index); - -bool digital_sequence_send(DigitalSequence* sequence); - -void digital_sequence_clear(DigitalSequence* sequence); - -void digital_sequence_timebase_correction(DigitalSequence* sequence, float factor); +/** + * @brief Append one period to the end of the DigitalSignal instance. + * + * @param[in,out] signal pointer to a the instance to append to. + * @param[in] ticks the period length, in 10 picosecond units. + */ +void digital_signal_add_period(DigitalSignal* signal, uint32_t ticks); + +/** + * @brief Append one period to the end of the DigitalSignal instance, with the level specified. + * + * If the level is the same as the last level contained in the instance, then it is extened + * by the given ticks value. Otherwise, the behaviour is identical to digital_signal_add_period(). + * + * Example 1: add tc with HIGH level + * ``` + * before: + * ... ------+ + * ta | tb + * +------- + * after: + * ... ------+ +------- + * ta | tb | tc + * +------+ + * ``` + * Example 2: add tc with LOW level + * ``` + * before: + * ... ------+ + * ta | tb + * +------- + * after: + * ... ------+ + * ta | tb + tc + * +-------------- + * ``` + * + * @param[in,out] signal pointer to the instance to append to. + * @param[in] ticks the period length, in 10 picosecond units. + * @param[in] level the level to be set during the period. + */ +void digital_signal_add_period_with_level(DigitalSignal* signal, uint32_t ticks, bool level); + +/** + * @brief Get the current start level contained in the DigitalSignal instance. + * + * If not explicitly set with digital_signal_set_start_level(), it defaults to false. + * + * @param[in] signal pointer to the instance to be queried. + * @returns the start level value. + */ +bool digital_signal_get_start_level(const DigitalSignal* signal); + +/** + * @brief Set the start level contained in the DigitalSignal instance. + * + * @param[in,out] signal pointer to the instance to be modified. + * @param[in] level signal level to be set as the start level. + */ +void digital_signal_set_start_level(DigitalSignal* signal, bool level); + +/** + * @brief Get the number of periods currently stored in a DigitalSignal instance. + * + * @param[in] signal pointer to the instance to be queried. + * @return the number of periods stored in the instance. + */ +uint32_t digital_signal_get_size(const DigitalSignal* signal); #ifdef __cplusplus } diff --git a/lib/digital_signal/digital_signal_i.h b/lib/digital_signal/digital_signal_i.h new file mode 100644 index 0000000000..e473c80c09 --- /dev/null +++ b/lib/digital_signal/digital_signal_i.h @@ -0,0 +1,23 @@ +/** + * @file digital_signal_i.h + * @brief DigitalSignal private definitions. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#include +#include + +#define DIGITAL_SIGNAL_T_TIM 1562 /**< 15.625 ns *100 */ +#define DIGITAL_SIGNAL_T_TIM_DIV2 (DIGITAL_SIGNAL_T_TIM / 2) /**< 15.625 ns / 2 *100 */ + +/** + * @brief DigitalSignal structure type. + */ +struct DigitalSignal { + bool start_level; /**< The level to begin the signal with. */ + uint32_t size; /**< Current period count contained in the instance. */ + uint32_t max_size; /**< Maximum period count this instance can hold. */ + uint32_t* data; /**< Pointer to the array of time periods. */ + int32_t remainder; /**< Remainder left after converting all periods into timer ticks. */ +}; diff --git a/lib/digital_signal/presets/nfc/iso14443_3a_signal.c b/lib/digital_signal/presets/nfc/iso14443_3a_signal.c new file mode 100644 index 0000000000..1f3824e2dc --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso14443_3a_signal.c @@ -0,0 +1,139 @@ +#include "iso14443_3a_signal.h" + +#include + +#define BITS_IN_BYTE (8) + +#define ISO14443_3A_SIGNAL_BIT_MAX_EDGES (10) +#define ISO14443_3A_SIGNAL_MAX_EDGES (1350) + +#define ISO14443_3A_SIGNAL_SEQUENCE_SIZE \ + (ISO14443_3A_SIGNAL_MAX_EDGES / (ISO14443_3A_SIGNAL_BIT_MAX_EDGES - 2)) + +#define ISO14443_3A_SIGNAL_F_SIG (13560000.0) +#define ISO14443_3A_SIGNAL_T_SIG 7374 //73.746ns*100 +#define ISO14443_3A_SIGNAL_T_SIG_X8 58992 //T_SIG*8 +#define ISO14443_3A_SIGNAL_T_SIG_X8_X8 471936 //T_SIG*8*8 +#define ISO14443_3A_SIGNAL_T_SIG_X8_X9 530928 //T_SIG*8*9 + +typedef enum { + Iso14443_3aSignalIndexZero, + Iso14443_3aSignalIndexOne, + Iso14443_3aSignalIndexCount, +} Iso14443_3aSignalIndex; + +typedef DigitalSignal* Iso14443_3aSignalBank[Iso14443_3aSignalIndexCount]; + +struct Iso14443_3aSignal { + DigitalSequence* tx_sequence; + Iso14443_3aSignalBank signals; +}; + +static void iso14443_3a_signal_add_byte(Iso14443_3aSignal* instance, uint8_t byte, bool parity) { + for(size_t i = 0; i < BITS_IN_BYTE; i++) { + digital_sequence_add_signal( + instance->tx_sequence, + FURI_BIT(byte, i) ? Iso14443_3aSignalIndexOne : Iso14443_3aSignalIndexZero); + } + digital_sequence_add_signal( + instance->tx_sequence, parity ? Iso14443_3aSignalIndexOne : Iso14443_3aSignalIndexZero); +} + +static void iso14443_3a_signal_encode( + Iso14443_3aSignal* instance, + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits) { + furi_assert(instance); + furi_assert(tx_data); + furi_assert(tx_parity); + + // Start of frame + digital_sequence_add_signal(instance->tx_sequence, Iso14443_3aSignalIndexOne); + + if(tx_bits < BITS_IN_BYTE) { + for(size_t i = 0; i < tx_bits; i++) { + digital_sequence_add_signal( + instance->tx_sequence, + FURI_BIT(tx_data[0], i) ? Iso14443_3aSignalIndexOne : Iso14443_3aSignalIndexZero); + } + } else { + for(size_t i = 0; i < tx_bits / BITS_IN_BYTE; i++) { + bool parity = FURI_BIT(tx_parity[i / BITS_IN_BYTE], i % BITS_IN_BYTE); + iso14443_3a_signal_add_byte(instance, tx_data[i], parity); + } + } +} + +static inline void iso14443_3a_signal_set_bit(DigitalSignal* signal, bool bit) { + digital_signal_set_start_level(signal, bit); + + if(bit) { + for(uint32_t i = 0; i < 7; ++i) { + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8); + } + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8_X9); + } else { + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8_X8); + for(uint32_t i = 0; i < 8; ++i) { + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8); + } + } +} + +static inline void iso14443_3a_signal_bank_fill(Iso14443_3aSignalBank bank) { + for(uint32_t i = 0; i < Iso14443_3aSignalIndexCount; ++i) { + bank[i] = digital_signal_alloc(ISO14443_3A_SIGNAL_BIT_MAX_EDGES); + iso14443_3a_signal_set_bit(bank[i], i % Iso14443_3aSignalIndexCount != 0); + } +} + +static inline void iso14443_3a_signal_bank_clear(Iso14443_3aSignalBank bank) { + for(uint32_t i = 0; i < Iso14443_3aSignalIndexCount; ++i) { + digital_signal_free(bank[i]); + } +} + +static inline void + iso14443_3a_signal_bank_register(Iso14443_3aSignalBank bank, DigitalSequence* sequence) { + for(uint32_t i = 0; i < Iso14443_3aSignalIndexCount; ++i) { + digital_sequence_register_signal(sequence, i, bank[i]); + } +} + +Iso14443_3aSignal* iso14443_3a_signal_alloc(const GpioPin* pin) { + furi_assert(pin); + + Iso14443_3aSignal* instance = malloc(sizeof(Iso14443_3aSignal)); + instance->tx_sequence = digital_sequence_alloc(ISO14443_3A_SIGNAL_SEQUENCE_SIZE, pin); + + iso14443_3a_signal_bank_fill(instance->signals); + iso14443_3a_signal_bank_register(instance->signals, instance->tx_sequence); + + return instance; +} + +void iso14443_3a_signal_free(Iso14443_3aSignal* instance) { + furi_assert(instance); + furi_assert(instance->tx_sequence); + + iso14443_3a_signal_bank_clear(instance->signals); + digital_sequence_free(instance->tx_sequence); + free(instance); +} + +void iso14443_3a_signal_tx( + Iso14443_3aSignal* instance, + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits) { + furi_assert(instance); + furi_assert(tx_data); + furi_assert(tx_parity); + + FURI_CRITICAL_ENTER(); + digital_sequence_clear(instance->tx_sequence); + iso14443_3a_signal_encode(instance, tx_data, tx_parity, tx_bits); + digital_sequence_transmit(instance->tx_sequence); + FURI_CRITICAL_EXIT(); +} diff --git a/lib/digital_signal/presets/nfc/iso14443_3a_signal.h b/lib/digital_signal/presets/nfc/iso14443_3a_signal.h new file mode 100644 index 0000000000..73269e66c8 --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso14443_3a_signal.h @@ -0,0 +1,51 @@ +/** + * @file iso14443_3a_signal.h + * @brief DigitalSequence preset for generating ISO14443-3A compliant signals. + */ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3aSignal Iso14443_3aSignal; + +/** + * @brief Allocate an Iso14443_3aSignal instance with a set GPIO pin. + * + * @param[in] pin GPIO pin to use during transmission. + * @returns pointer to the allocated instance. + */ +Iso14443_3aSignal* iso14443_3a_signal_alloc(const GpioPin* pin); + +/** + * @brief Delete an Iso14443_3aSignal instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void iso14443_3a_signal_free(Iso14443_3aSignal* instance); + +/** + * @brief Transmit arbitrary bytes using an Iso14443_3aSignal instance. + * + * This function will block until the transmisson has been completed. + * + * @param[in] instance pointer to the instance used in transmission. + * @param[in] tx_data pointer to the data to be transmitted. + * @param[in] tx_parity pointer to the bit-packed parity array. + * @param[in] tx_bits size of the data to be transmitted in bits. + */ +void iso14443_3a_signal_tx( + Iso14443_3aSignal* instance, + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits); + +#ifdef __cplusplus +} +#endif diff --git a/lib/digital_signal/presets/nfc/iso15693_signal.c b/lib/digital_signal/presets/nfc/iso15693_signal.c new file mode 100644 index 0000000000..735a88f57a --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso15693_signal.c @@ -0,0 +1,204 @@ +#include "iso15693_signal.h" + +#include + +#define BITS_IN_BYTE (8U) + +#define ISO15693_SIGNAL_COEFF_HI (1U) +#define ISO15693_SIGNAL_COEFF_LO (4U) + +#define ISO15693_SIGNAL_ZERO_EDGES (16U) +#define ISO15693_SIGNAL_ONE_EDGES (ISO15693_SIGNAL_ZERO_EDGES + 1U) +#define ISO15693_SIGNAL_EOF_EDGES (64U) +#define ISO15693_SIGNAL_SOF_EDGES (ISO15693_SIGNAL_EOF_EDGES + 1U) +#define ISO15693_SIGNAL_EDGES (1350U) + +#define ISO15693_SIGNAL_FC (13.56e6) +#define ISO15693_SIGNAL_FC_16 (16.0e11 / ISO15693_SIGNAL_FC) +#define ISO15693_SIGNAL_FC_256 (256.0e11 / ISO15693_SIGNAL_FC) +#define ISO15693_SIGNAL_FC_768 (768.0e11 / ISO15693_SIGNAL_FC) + +typedef enum { + Iso15693SignalIndexSof, + Iso15693SignalIndexEof, + Iso15693SignalIndexOne, + Iso15693SignalIndexZero, + Iso15693SignalIndexNum, +} Iso15693SignalIndex; + +typedef DigitalSignal* Iso15693SignalBank[Iso15693SignalIndexNum]; + +struct Iso15693Signal { + DigitalSequence* tx_sequence; + Iso15693SignalBank banks[Iso15693SignalDataRateNum]; +}; + +// Add an unmodulated signal for the length of Fc / 256 * k (where k = 1 or 4) +static void iso15693_add_silence(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + const uint32_t k = data_rate == Iso15693SignalDataRateHi ? ISO15693_SIGNAL_COEFF_HI : + ISO15693_SIGNAL_COEFF_LO; + digital_signal_add_period_with_level(signal, ISO15693_SIGNAL_FC_256 * k, false); +} + +// Add 8 * k subcarrier pulses of Fc / 16 (where k = 1 or 4) +static void iso15693_add_subcarrier(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + const uint32_t k = data_rate == Iso15693SignalDataRateHi ? ISO15693_SIGNAL_COEFF_HI : + ISO15693_SIGNAL_COEFF_LO; + for(uint32_t i = 0; i < ISO15693_SIGNAL_ZERO_EDGES * k; ++i) { + digital_signal_add_period_with_level(signal, ISO15693_SIGNAL_FC_16, !(i % 2)); + } +} + +static void iso15693_add_bit(DigitalSignal* signal, Iso15693SignalDataRate data_rate, bool bit) { + if(bit) { + iso15693_add_silence(signal, data_rate); + iso15693_add_subcarrier(signal, data_rate); + } else { + iso15693_add_subcarrier(signal, data_rate); + iso15693_add_silence(signal, data_rate); + } +} + +static inline void iso15693_add_sof(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + // Not adding silence since it only increases response time + + for(uint32_t i = 0; i < ISO15693_SIGNAL_FC_768 / ISO15693_SIGNAL_FC_256; ++i) { + iso15693_add_subcarrier(signal, data_rate); + } + + iso15693_add_bit(signal, data_rate, true); +} + +static inline void iso15693_add_eof(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + iso15693_add_bit(signal, data_rate, false); + + for(uint32_t i = 0; i < ISO15693_SIGNAL_FC_768 / ISO15693_SIGNAL_FC_256; ++i) { + iso15693_add_subcarrier(signal, data_rate); + } + + // Not adding silence since it does nothing here +} + +static inline uint32_t + iso15693_get_sequence_index(Iso15693SignalIndex index, Iso15693SignalDataRate data_rate) { + return index + data_rate * Iso15693SignalIndexNum; +} + +static inline void + iso15693_add_byte(Iso15693Signal* instance, Iso15693SignalDataRate data_rate, uint8_t byte) { + for(size_t i = 0; i < BITS_IN_BYTE; i++) { + const uint8_t bit = byte & (1U << i); + digital_sequence_add_signal( + instance->tx_sequence, + iso15693_get_sequence_index( + bit ? Iso15693SignalIndexOne : Iso15693SignalIndexZero, data_rate)); + } +} + +static inline void iso15693_signal_encode( + Iso15693Signal* instance, + Iso15693SignalDataRate data_rate, + const uint8_t* tx_data, + size_t tx_data_size) { + digital_sequence_add_signal( + instance->tx_sequence, iso15693_get_sequence_index(Iso15693SignalIndexSof, data_rate)); + + for(size_t i = 0; i < tx_data_size; i++) { + iso15693_add_byte(instance, data_rate, tx_data[i]); + } + + digital_sequence_add_signal( + instance->tx_sequence, iso15693_get_sequence_index(Iso15693SignalIndexEof, data_rate)); +} + +static void iso15693_signal_bank_fill(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + const uint32_t k = data_rate == Iso15693SignalDataRateHi ? ISO15693_SIGNAL_COEFF_HI : + ISO15693_SIGNAL_COEFF_LO; + DigitalSignal** bank = instance->banks[data_rate]; + + bank[Iso15693SignalIndexSof] = digital_signal_alloc(ISO15693_SIGNAL_SOF_EDGES * k); + bank[Iso15693SignalIndexEof] = digital_signal_alloc(ISO15693_SIGNAL_EOF_EDGES * k); + bank[Iso15693SignalIndexOne] = digital_signal_alloc(ISO15693_SIGNAL_ONE_EDGES * k); + bank[Iso15693SignalIndexZero] = digital_signal_alloc(ISO15693_SIGNAL_ZERO_EDGES * k); + + iso15693_add_sof(bank[Iso15693SignalIndexSof], data_rate); + iso15693_add_eof(bank[Iso15693SignalIndexEof], data_rate); + iso15693_add_bit(bank[Iso15693SignalIndexOne], data_rate, true); + iso15693_add_bit(bank[Iso15693SignalIndexZero], data_rate, false); +} + +static void + iso15693_signal_bank_clear(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + DigitalSignal** bank = instance->banks[data_rate]; + + for(uint32_t i = 0; i < Iso15693SignalIndexNum; ++i) { + digital_signal_free(bank[i]); + } +} + +static void + iso15693_signal_bank_register(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + for(uint32_t i = 0; i < Iso15693SignalIndexNum; ++i) { + digital_sequence_register_signal( + instance->tx_sequence, + iso15693_get_sequence_index(i, data_rate), + instance->banks[data_rate][i]); + } +} + +Iso15693Signal* iso15693_signal_alloc(const GpioPin* pin) { + furi_assert(pin); + + Iso15693Signal* instance = malloc(sizeof(Iso15693Signal)); + + instance->tx_sequence = digital_sequence_alloc(BITS_IN_BYTE * 255 + 2, pin); + + for(uint32_t i = 0; i < Iso15693SignalDataRateNum; ++i) { + iso15693_signal_bank_fill(instance, i); + iso15693_signal_bank_register(instance, i); + } + + return instance; +} + +void iso15693_signal_free(Iso15693Signal* instance) { + furi_assert(instance); + + digital_sequence_free(instance->tx_sequence); + + for(uint32_t i = 0; i < Iso15693SignalDataRateNum; ++i) { + iso15693_signal_bank_clear(instance, i); + } + + free(instance); +} + +void iso15693_signal_tx( + Iso15693Signal* instance, + Iso15693SignalDataRate data_rate, + const uint8_t* tx_data, + size_t tx_data_size) { + furi_assert(instance); + furi_assert(data_rate < Iso15693SignalDataRateNum); + furi_assert(tx_data); + + FURI_CRITICAL_ENTER(); + digital_sequence_clear(instance->tx_sequence); + iso15693_signal_encode(instance, data_rate, tx_data, tx_data_size); + digital_sequence_transmit(instance->tx_sequence); + + FURI_CRITICAL_EXIT(); +} + +void iso15693_signal_tx_sof(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + furi_assert(instance); + furi_assert(data_rate < Iso15693SignalDataRateNum); + + FURI_CRITICAL_ENTER(); + digital_sequence_clear(instance->tx_sequence); + digital_sequence_add_signal( + instance->tx_sequence, iso15693_get_sequence_index(Iso15693SignalIndexSof, data_rate)); + digital_sequence_transmit(instance->tx_sequence); + + FURI_CRITICAL_EXIT(); +} diff --git a/lib/digital_signal/presets/nfc/iso15693_signal.h b/lib/digital_signal/presets/nfc/iso15693_signal.h new file mode 100644 index 0000000000..8219cff157 --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso15693_signal.h @@ -0,0 +1,73 @@ +/** + * @file iso15693_signal.h + * @brief DigitalSequence preset for generating ISO15693-compliant signals. + * + */ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693Signal Iso15693Signal; + +/** + * @brief Supported data rates. + */ +typedef enum { + Iso15693SignalDataRateHi, /**< High data rate. */ + Iso15693SignalDataRateLo, /**< Low data rate. */ + Iso15693SignalDataRateNum, /**< Data rate mode count. Internal use. */ +} Iso15693SignalDataRate; + +/** + * @brief Allocate an Iso15693Signal instance with a set GPIO pin. + * + * @param[in] pin GPIO pin to use during transmission. + * @returns pointer to the allocated instance. + */ +Iso15693Signal* iso15693_signal_alloc(const GpioPin* pin); + +/** + * @brief Delete an Iso15693Signal instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void iso15693_signal_free(Iso15693Signal* instance); + +/** + * @brief Transmit arbitrary bytes using an Iso15693Signal instance. + * @see Iso15693SignalDataRate + * + * This function will block until the transmisson has been completed. + * + * @param[in] instance pointer to the instance used in transmission. + * @param[in] data_rate data rate to transmit at. + * @param[in] tx_data pointer to the data to be transmitted. + * @param[in] tx_data_size size of the data to be transmitted in bytes. + */ +void iso15693_signal_tx( + Iso15693Signal* instance, + Iso15693SignalDataRate data_rate, + const uint8_t* tx_data, + size_t tx_data_size); + +/** + * @brief Transmit Start of Frame using an Iso15693Signal instance. + * @see Iso15693SignalDataRate + * + * This function will block until the transmisson has been completed. + * + * @param[in] instance pointer to the instance used in transmission. + * @param[in] data_rate data rate to transmit at. + */ +void iso15693_signal_tx_sof(Iso15693Signal* instance, Iso15693SignalDataRate data_rate); + +#ifdef __cplusplus +} +#endif diff --git a/lib/drivers/st25r3916.c b/lib/drivers/st25r3916.c new file mode 100644 index 0000000000..4772612135 --- /dev/null +++ b/lib/drivers/st25r3916.c @@ -0,0 +1,81 @@ +#include "st25r3916.h" + +#include + +void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) { + furi_assert(handle); + + uint8_t irq_mask_regs[4] = { + mask & 0xff, + (mask >> 8) & 0xff, + (mask >> 16) & 0xff, + (mask >> 24) & 0xff, + }; + st25r3916_write_burst_regs(handle, ST25R3916_REG_IRQ_MASK_MAIN, irq_mask_regs, 4); +} + +uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) { + furi_assert(handle); + + uint8_t irq_regs[4] = {}; + uint32_t irq = 0; + st25r3916_read_burst_regs(handle, ST25R3916_REG_IRQ_MASK_MAIN, irq_regs, 4); + // FURI_LOG_I( + // "Mask Irq", "%02X %02X %02X %02X", irq_regs[0], irq_regs[1], irq_regs[2], irq_regs[3]); + st25r3916_read_burst_regs(handle, ST25R3916_REG_IRQ_MAIN, irq_regs, 4); + irq = (uint32_t)irq_regs[0]; + irq |= (uint32_t)irq_regs[1] << 8; + irq |= (uint32_t)irq_regs[2] << 16; + irq |= (uint32_t)irq_regs[3] << 24; + // FURI_LOG_I("iRQ", "%02X %02X %02X %02X", irq_regs[0], irq_regs[1], irq_regs[2], irq_regs[3]); + + return irq; +} + +void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) { + furi_assert(handle); + furi_assert(buff); + + size_t bytes = (bits + 7) / 8; + + st25r3916_write_reg(handle, ST25R3916_REG_NUM_TX_BYTES2, (uint8_t)(bits & 0xFFU)); + st25r3916_write_reg(handle, ST25R3916_REG_NUM_TX_BYTES1, (uint8_t)((bits >> 8) & 0xFFU)); + + st25r3916_reg_write_fifo(handle, buff, bytes); +} + +bool st25r3916_read_fifo( + FuriHalSpiBusHandle* handle, + uint8_t* buff, + size_t buff_size, + size_t* buff_bits) { + furi_assert(handle); + furi_assert(buff); + + bool read_success = false; + + do { + uint8_t fifo_status[2] = {}; + st25r3916_read_burst_regs(handle, ST25R3916_REG_FIFO_STATUS1, fifo_status, 2); + size_t bytes = ((fifo_status[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> + ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) | + fifo_status[0]; + uint8_t bits = + ((fifo_status[1] & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) >> + ST25R3916_REG_FIFO_STATUS2_fifo_lb_shift); + + if(bytes == 0) break; + if(bytes > buff_size) break; + + st25r3916_reg_read_fifo(handle, buff, bytes); + + if(bits) { + *buff_bits = (bytes - 1) * 8 + bits; + } else { + *buff_bits = bytes * 8; + } + read_success = true; + } while(false); + + return read_success; +} diff --git a/lib/drivers/st25r3916.h b/lib/drivers/st25r3916.h new file mode 100644 index 0000000000..532239f472 --- /dev/null +++ b/lib/drivers/st25r3916.h @@ -0,0 +1,113 @@ +#pragma once + +#include "st25r3916_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ST25R3916_IRQ_MASK_ALL (uint32_t)(0xFFFFFFFFUL) /** All ST25R3916 interrupt sources */ +#define ST25R3916_IRQ_MASK_NONE (uint32_t)(0x00000000UL) /**No ST25R3916 interrupt source */ + +/** Main interrupt register */ +#define ST25R3916_IRQ_MASK_OSC (uint32_t)(0x00000080U) /** ST25R3916 oscillator stable interrupt */ +#define ST25R3916_IRQ_MASK_FWL (uint32_t)(0x00000040U) /** ST25R3916 FIFO water level interrupt */ +#define ST25R3916_IRQ_MASK_RXS (uint32_t)(0x00000020U) /** ST25R3916 start of receive interrupt */ +#define ST25R3916_IRQ_MASK_RXE (uint32_t)(0x00000010U) /** ST25R3916 end of receive interrupt */ +#define ST25R3916_IRQ_MASK_TXE \ + (uint32_t)(0x00000008U) /** ST25R3916 end of transmission interrupt */ +#define ST25R3916_IRQ_MASK_COL (uint32_t)(0x00000004U) /** ST25R3916 bit collision interrupt */ +#define ST25R3916_IRQ_MASK_RX_REST \ + (uint32_t)(0x00000002U) /** ST25R3916 automatic reception restart interrupt */ +#define ST25R3916_IRQ_MASK_RFU (uint32_t)(0x00000001U) /** ST25R3916 RFU interrupt */ + +/** Timer and NFC interrupt register */ +#define ST25R3916_IRQ_MASK_DCT \ + (uint32_t)(0x00008000U) /** ST25R3916 termination of direct command interrupt. */ +#define ST25R3916_IRQ_MASK_NRE \ + (uint32_t)(0x00004000U) /** ST25R3916 no-response timer expired interrupt */ +#define ST25R3916_IRQ_MASK_GPE \ + (uint32_t)(0x00002000U) /** ST25R3916 general purpose timer expired interrupt */ +#define ST25R3916_IRQ_MASK_EON (uint32_t)(0x00001000U) /** ST25R3916 external field on interrupt */ +#define ST25R3916_IRQ_MASK_EOF \ + (uint32_t)(0x00000800U) /** ST25R3916 external field off interrupt */ +#define ST25R3916_IRQ_MASK_CAC \ + (uint32_t)(0x00000400U) /** ST25R3916 collision during RF collision avoidance */ +#define ST25R3916_IRQ_MASK_CAT \ + (uint32_t)(0x00000200U) /** ST25R3916 minimum guard time expired interrupt */ +#define ST25R3916_IRQ_MASK_NFCT \ + (uint32_t)(0x00000100U) /** ST25R3916 initiator bit rate recognised interrupt */ + +/** Error and wake-up interrupt register */ +#define ST25R3916_IRQ_MASK_CRC (uint32_t)(0x00800000U) /** ST25R3916 CRC error interrupt */ +#define ST25R3916_IRQ_MASK_PAR (uint32_t)(0x00400000U) /** ST25R3916 parity error interrupt */ +#define ST25R3916_IRQ_MASK_ERR2 \ + (uint32_t)(0x00200000U) /** ST25R3916 soft framing error interrupt */ +#define ST25R3916_IRQ_MASK_ERR1 \ + (uint32_t)(0x00100000U) /** ST25R3916 hard framing error interrupt */ +#define ST25R3916_IRQ_MASK_WT (uint32_t)(0x00080000U) /** ST25R3916 wake-up interrupt */ +#define ST25R3916_IRQ_MASK_WAM \ + (uint32_t)(0x00040000U) /** ST25R3916 wake-up due to amplitude interrupt */ +#define ST25R3916_IRQ_MASK_WPH \ + (uint32_t)(0x00020000U) /** ST25R3916 wake-up due to phase interrupt */ +#define ST25R3916_IRQ_MASK_WCAP \ + (uint32_t)(0x00010000U) /** ST25R3916 wake-up due to capacitance measurement */ + +/** Passive Target Interrupt Register */ +#define ST25R3916_IRQ_MASK_PPON2 \ + (uint32_t)(0x80000000U) /** ST25R3916 PPON2 Field on waiting Timer interrupt */ +#define ST25R3916_IRQ_MASK_SL_WL \ + (uint32_t)(0x40000000U) /** ST25R3916 Passive target slot number water level interrupt */ +#define ST25R3916_IRQ_MASK_APON \ + (uint32_t)(0x20000000U) /** ST25R3916 Anticollision done and Field On interrupt */ +#define ST25R3916_IRQ_MASK_RXE_PTA \ + (uint32_t)(0x10000000U) /** ST25R3916 RXE with an automatic response interrupt */ +#define ST25R3916_IRQ_MASK_WU_F \ + (uint32_t)(0x08000000U) /** ST25R3916 212/424b/s Passive target interrupt: Active */ +#define ST25R3916_IRQ_MASK_RFU2 (uint32_t)(0x04000000U) /** ST25R3916 RFU2 interrupt */ +#define ST25R3916_IRQ_MASK_WU_A_X \ + (uint32_t)(0x02000000U) /** ST25R3916 106kb/s Passive target state interrupt: Active* */ +#define ST25R3916_IRQ_MASK_WU_A \ + (uint32_t)(0x01000000U) /** ST25R3916 106kb/s Passive target state interrupt: Active */ + +/** Mask st25r3916 interrupts + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param mask - mask of interrupts to be disabled + */ +void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask); + +/** Get st25r3916 interrupts + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * + * @return received interrupts + */ +uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle); + +/** Write FIFO + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to write to FIFO + * @param bits - number of bits to write + */ +void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits); + +/** Read FIFO + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to read from FIFO + * @param buff_size - buffer size n bytes + * @param buff_bits - pointer to number of bits read + * + * @return true if read success, false otherwise +*/ +bool st25r3916_read_fifo( + FuriHalSpiBusHandle* handle, + uint8_t* buff, + size_t buff_size, + size_t* buff_bits); + +#ifdef __cplusplus +} +#endif diff --git a/lib/drivers/st25r3916_reg.c b/lib/drivers/st25r3916_reg.c new file mode 100644 index 0000000000..8ac4672383 --- /dev/null +++ b/lib/drivers/st25r3916_reg.c @@ -0,0 +1,257 @@ +#include "st25r3916_reg.h" + +#include + +#define ST25R3916_WRITE_MODE \ + (0U << 6) /*!< ST25R3916 Operation Mode: Write */ +#define ST25R3916_READ_MODE \ + (1U << 6) /*!< ST25R3916 Operation Mode: Read */ +#define ST25R3916_CMD_MODE \ + (3U << 6) /*!< ST25R3916 Operation Mode: Direct Command */ +#define ST25R3916_FIFO_LOAD \ + (0x80U) /*!< ST25R3916 Operation Mode: FIFO Load */ +#define ST25R3916_FIFO_READ \ + (0x9FU) /*!< ST25R3916 Operation Mode: FIFO Read */ +#define ST25R3916_PT_A_CONFIG_LOAD \ + (0xA0U) /*!< ST25R3916 Operation Mode: Passive Target Memory A-Config Load */ +#define ST25R3916_PT_F_CONFIG_LOAD \ + (0xA8U) /*!< ST25R3916 Operation Mode: Passive Target Memory F-Config Load */ +#define ST25R3916_PT_TSN_DATA_LOAD \ + (0xACU) /*!< ST25R3916 Operation Mode: Passive Target Memory TSN Load */ +#define ST25R3916_PT_MEM_READ \ + (0xBFU) /*!< ST25R3916 Operation Mode: Passive Target Memory Read */ + +#define ST25R3916_CMD_LEN \ + (1U) /*!< ST25R3916 CMD length */ +#define ST25R3916_FIFO_DEPTH (512U) +#define ST25R3916_BUF_LEN \ + (ST25R3916_CMD_LEN + \ + ST25R3916_FIFO_DEPTH) /*!< ST25R3916 communication buffer: CMD + FIFO length */ + +static void st25r3916_reg_tx_byte(FuriHalSpiBusHandle* handle, uint8_t byte) { + uint8_t val = byte; + furi_hal_spi_bus_tx(handle, &val, 1, 5); +} + +void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { + furi_assert(handle); + st25r3916_read_burst_regs(handle, reg, val, 1); +} + +void st25r3916_read_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + uint8_t* values, + uint8_t length) { + furi_assert(handle); + furi_assert(values); + furi_assert(length); + + furi_hal_gpio_write(handle->cs, false); + + if(reg_start & ST25R3916_SPACE_B) { + // Send direct command first + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_SPACE_B_ACCESS); + } + st25r3916_reg_tx_byte(handle, (reg_start & ~ST25R3916_SPACE_B) | ST25R3916_READ_MODE); + furi_hal_spi_bus_rx(handle, values, length, 5); + + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { + furi_assert(handle); + uint8_t reg_val = val; + st25r3916_write_burst_regs(handle, reg, ®_val, 1); +} + +void st25r3916_write_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + const uint8_t* values, + uint8_t length) { + furi_assert(handle); + furi_assert(values); + furi_assert(length); + + furi_hal_gpio_write(handle->cs, false); + + if(reg_start & ST25R3916_SPACE_B) { + // Send direct command first + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_SPACE_B_ACCESS); + } + st25r3916_reg_tx_byte(handle, (reg_start & ~ST25R3916_SPACE_B) | ST25R3916_WRITE_MODE); + furi_hal_spi_bus_tx(handle, values, length, 5); + + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + furi_assert(length); + furi_assert(length <= ST25R3916_FIFO_DEPTH); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_FIFO_LOAD); + furi_hal_spi_bus_tx(handle, buff, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + furi_assert(length); + furi_assert(length <= ST25R3916_FIFO_DEPTH); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_FIFO_READ); + furi_hal_spi_bus_rx(handle, buff, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { + furi_assert(handle); + furi_assert(values); + furi_assert(length); + furi_assert(length <= ST25R3916_PTM_LEN); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_A_CONFIG_LOAD); + furi_hal_spi_bus_tx(handle, values, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + furi_assert(length); + furi_assert(length <= ST25R3916_PTM_LEN); + + uint8_t tmp_buff[ST25R3916_PTM_LEN + 1]; + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_MEM_READ); + furi_hal_spi_bus_rx(handle, tmp_buff, length + 1, 200); + furi_hal_gpio_write(handle->cs, true); + memcpy(buff, tmp_buff + 1, length); +} + +void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { + furi_assert(handle); + furi_assert(values); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_F_CONFIG_LOAD); + furi_hal_spi_bus_tx(handle, values, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_TSN_DATA_LOAD); + furi_hal_spi_bus_tx(handle, buff, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) { + furi_assert(handle); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, cmd | ST25R3916_CMD_MODE); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { + furi_assert(handle); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_TEST_ACCESS); + st25r3916_reg_tx_byte(handle, reg | ST25R3916_READ_MODE); + furi_hal_spi_bus_rx(handle, val, 1, 5); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { + furi_assert(handle); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_TEST_ACCESS); + st25r3916_reg_tx_byte(handle, reg | ST25R3916_WRITE_MODE); + furi_hal_spi_bus_tx(handle, &val, 1, 5); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) { + furi_assert(handle); + + uint8_t reg_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + if((reg_val & ~clr_mask) != reg_val) { + reg_val &= ~clr_mask; + st25r3916_write_reg(handle, reg, reg_val); + } +} + +void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) { + furi_assert(handle); + + uint8_t reg_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + if((reg_val | set_mask) != reg_val) { + reg_val |= set_mask; + st25r3916_write_reg(handle, reg, reg_val); + } +} + +void st25r3916_change_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value) { + furi_assert(handle); + + st25r3916_modify_reg(handle, reg, mask, (mask & value)); +} + +void st25r3916_modify_reg( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t clr_mask, + uint8_t set_mask) { + furi_assert(handle); + + uint8_t reg_val = 0; + uint8_t new_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + new_val = (reg_val & ~clr_mask) | set_mask; + if(new_val != reg_val) { + st25r3916_write_reg(handle, reg, new_val); + } +} + +void st25r3916_change_test_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value) { + furi_assert(handle); + + uint8_t reg_val = 0; + uint8_t new_val = 0; + st25r3916_read_test_reg(handle, reg, ®_val); + new_val = (reg_val & ~mask) | (mask & value); + if(new_val != reg_val) { + st25r3916_write_test_reg(handle, reg, new_val); + } +} + +bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) { + furi_assert(handle); + + uint8_t reg_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + return ((reg_val & mask) == val); +} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h b/lib/drivers/st25r3916_reg.h similarity index 62% rename from lib/ST25RFAL002/source/st25r3916/st25r3916_com.h rename to lib/drivers/st25r3916_reg.h index 6a47317e37..8a924fc392 100644 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h +++ b/lib/drivers/st25r3916_reg.h @@ -1,262 +1,202 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 communication declaration file - * - * This driver provides basic abstraction for communication with the ST25R3916 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_COM - * \brief RFAL ST25R3916 Communications - * @{ - * - */ - -#ifndef ST25R3916_COM_H -#define ST25R3916_COM_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define ST25R3916_SPACE_B 0x40U /*!< ST25R3916 Space-B indicator */ -#define ST25R3916_SPACE_B_REG_LEN 16U /*!< Number of register in the space B */ - -#define ST25R3916_FIFO_STATUS_LEN 2 /*!< Number of FIFO Status Register */ - -#define ST25R3916_PTM_A_LEN 15U /*!< Passive target memory A config length */ -#define ST25R3916_PTM_B_LEN 0U /*!< Passive target memory B config length */ -#define ST25R3916_PTM_F_LEN 21U /*!< Passive target memory F config length */ -#define ST25R3916_PTM_TSN_LEN 12U /*!< Passive target memory TSN data length */ - -/*! Full Passive target memory length */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** ST25R3916 direct commands */ +#define ST25R3916_CMD_SET_DEFAULT \ + 0xC1U /** Puts the chip in default state (same as after power-up */ +#define ST25R3916_CMD_STOP 0xC2U /*!< Stops all activities and clears FIFO */ +#define ST25R3916_CMD_TRANSMIT_WITH_CRC 0xC4U /** Transmit with CRC */ +#define ST25R3916_CMD_TRANSMIT_WITHOUT_CRC 0xC5U /** Transmit without CRC */ +#define ST25R3916_CMD_TRANSMIT_REQA 0xC6U /** Transmit REQA */ +#define ST25R3916_CMD_TRANSMIT_WUPA 0xC7U /** Transmit WUPA */ +#define ST25R3916_CMD_INITIAL_RF_COLLISION \ + 0xC8U /** NFC transmit with Initial RF Collision Avoidance */ +#define ST25R3916_CMD_RESPONSE_RF_COLLISION_N \ + 0xC9U /** NFC transmit with Response RF Collision Avoidance */ +#define ST25R3916_CMD_GOTO_SENSE 0xCDU /** Passive target logic to Sense/Idle state */ +#define ST25R3916_CMD_GOTO_SLEEP 0xCEU /** Passive target logic to Sleep/Halt state */ +#define ST25R3916_CMD_MASK_RECEIVE_DATA 0xD0U /** Mask receive data */ +#define ST25R3916_CMD_UNMASK_RECEIVE_DATA 0xD1U /** Unmask receive data */ +#define ST25R3916_CMD_AM_MOD_STATE_CHANGE 0xD2U /** AM Modulation state change */ +#define ST25R3916_CMD_MEASURE_AMPLITUDE 0xD3U /** Measure singal amplitude on RFI inputs */ +#define ST25R3916_CMD_RESET_RXGAIN 0xD5U /** Reset RX Gain */ +#define ST25R3916_CMD_ADJUST_REGULATORS 0xD6U /** Adjust regulators */ +#define ST25R3916_CMD_CALIBRATE_DRIVER_TIMING \ + 0xD8U /** Starts the sequence to adjust the driver timing */ +#define ST25R3916_CMD_MEASURE_PHASE 0xD9U /** Measure phase between RFO and RFI signal */ +#define ST25R3916_CMD_CLEAR_RSSI 0xDAU /** Clear RSSI bits and restart the measurement */ +#define ST25R3916_CMD_CLEAR_FIFO 0xDBU /** Clears FIFO, Collision and IRQ status */ +#define ST25R3916_CMD_TRANSPARENT_MODE 0xDCU /** Transparent mode */ +#define ST25R3916_CMD_CALIBRATE_C_SENSOR 0xDDU /** Calibrate the capacitive sensor */ +#define ST25R3916_CMD_MEASURE_CAPACITANCE 0xDEU /** Measure capacitance */ +#define ST25R3916_CMD_MEASURE_VDD 0xDFU /** Measure power supply voltage */ +#define ST25R3916_CMD_START_GP_TIMER 0xE0U /** Start the general purpose timer */ +#define ST25R3916_CMD_START_WUP_TIMER 0xE1U /** Start the wake-up timer */ +#define ST25R3916_CMD_START_MASK_RECEIVE_TIMER 0xE2U /** Start the mask-receive timer */ +#define ST25R3916_CMD_START_NO_RESPONSE_TIMER 0xE3U /** Start the no-response timer */ +#define ST25R3916_CMD_START_PPON2_TIMER 0xE4U /** Start PPon2 timer */ +#define ST25R3916_CMD_STOP_NRT 0xE8U /** Stop No Response Timer */ +#define ST25R3916_CMD_SPACE_B_ACCESS 0xFBU /** Enable R/W access to the test registers */ +#define ST25R3916_CMD_TEST_ACCESS 0xFCU /** Enable R/W access to the test registers */ + +#define ST25R3916_SPACE_B 0x40U /** ST25R3916 Space-B indicator */ +#define ST25R3916_SPACE_B_REG_LEN 16U /** Number of register in the space B */ + +#define ST25R3916_FIFO_STATUS_LEN 2 /** Number of FIFO Status Register */ + +#define ST25R3916_PTM_A_LEN 15U /** Passive target memory A config length */ +#define ST25R3916_PTM_B_LEN 0U /** Passive target memory B config length */ +#define ST25R3916_PTM_F_LEN 21U /** Passive target memory F config length */ +#define ST25R3916_PTM_TSN_LEN 12U /** Passive target memory TSN data length */ + +/** Full Passive target memory length */ #define ST25R3916_PTM_LEN \ (ST25R3916_PTM_A_LEN + ST25R3916_PTM_B_LEN + ST25R3916_PTM_F_LEN + ST25R3916_PTM_TSN_LEN) -/* IO configuration registers */ -#define ST25R3916_REG_IO_CONF1 0x00U /*!< RW IO Configuration Register 1 */ -#define ST25R3916_REG_IO_CONF2 0x01U /*!< RW IO Configuration Register 2 */ +/** IO configuration registers */ +#define ST25R3916_REG_IO_CONF1 0x00U /** RW IO Configuration Register 1 */ +#define ST25R3916_REG_IO_CONF2 0x01U /** RW IO Configuration Register 2 */ -/* Operation control and mode definition registers */ -#define ST25R3916_REG_OP_CONTROL 0x02U /*!< RW Operation Control Register */ -#define ST25R3916_REG_MODE 0x03U /*!< RW Mode Definition Register */ -#define ST25R3916_REG_BIT_RATE 0x04U /*!< RW Bit Rate Definition Register */ +/** Operation control and mode definition */ +#define ST25R3916_REG_OP_CONTROL 0x02U /** RW Operation Control Register */ +#define ST25R3916_REG_MODE 0x03U /** RW Mode Definition Register */ +#define ST25R3916_REG_BIT_RATE 0x04U /** RW Bit Rate Definition Register */ -/* Protocol Configuration registers */ -#define ST25R3916_REG_ISO14443A_NFC \ - 0x05U /*!< RW ISO14443A and NFC 106 kBit/s Settings Register */ +/** Protocol Configuration registers */ +#define ST25R3916_REG_ISO14443A_NFC 0x05U /** RW ISO14443A and NFC 106 kBit/s Settings Register */ #define ST25R3916_REG_EMD_SUP_CONF \ - (ST25R3916_SPACE_B | 0x05U) /*!< RW EMD Suppression Configuration Register */ -#define ST25R3916_REG_ISO14443B_1 \ - 0x06U /*!< RW ISO14443B Settings Register 1 */ + (ST25R3916_SPACE_B | 0x05U) /*!< RW EMD Suppression Configuration Register */ +#define ST25R3916_REG_ISO14443B_1 0x06U /** RW ISO14443B Settings Register 1 */ #define ST25R3916_REG_SUBC_START_TIME \ - (ST25R3916_SPACE_B | 0x06U) /*!< RW Subcarrier Start Time Register */ -#define ST25R3916_REG_ISO14443B_2 \ - 0x07U /*!< RW ISO14443B Settings Register 2 */ -#define ST25R3916_REG_PASSIVE_TARGET \ - 0x08U /*!< RW Passive Target Definition Register */ -#define ST25R3916_REG_STREAM_MODE \ - 0x09U /*!< RW Stream Mode Definition Register */ -#define ST25R3916_REG_AUX 0x0AU /*!< RW Auxiliary Definition Register */ - -/* Receiver Configuration registers */ -#define ST25R3916_REG_RX_CONF1 0x0BU /*!< RW Receiver Configuration Register 1 */ -#define ST25R3916_REG_RX_CONF2 0x0CU /*!< RW Receiver Configuration Register 2 */ -#define ST25R3916_REG_RX_CONF3 0x0DU /*!< RW Receiver Configuration Register 3 */ -#define ST25R3916_REG_RX_CONF4 0x0EU /*!< RW Receiver Configuration Register 4 */ + (ST25R3916_SPACE_B | 0x06U) /*!< RW Subcarrier Start Time Register */ +#define ST25R3916_REG_ISO14443B_2 0x07U /** RW ISO14443B Settings Register 2 */ +#define ST25R3916_REG_PASSIVE_TARGET 0x08U /** RW Passive Target Definition Register */ +#define ST25R3916_REG_STREAM_MODE 0x09U /** RW Stream Mode Definition Register */ +#define ST25R3916_REG_AUX 0x0AU /** RW Auxiliary Definition Register */ + +/** Receiver Configuration registers */ +#define ST25R3916_REG_RX_CONF1 0x0BU /** RW Receiver Configuration Register 1 */ +#define ST25R3916_REG_RX_CONF2 0x0CU /** RW Receiver Configuration Register 2 */ +#define ST25R3916_REG_RX_CONF3 0x0DU /** RW Receiver Configuration Register 3 */ +#define ST25R3916_REG_RX_CONF4 0x0EU /** RW Receiver Configuration Register 4 */ #define ST25R3916_REG_P2P_RX_CONF \ - (ST25R3916_SPACE_B | 0x0BU) /*!< RW P2P Receiver Configuration Register 1 */ + (ST25R3916_SPACE_B | 0x0BU) /** RW P2P Receiver Configuration Register 1 */ #define ST25R3916_REG_CORR_CONF1 \ - (ST25R3916_SPACE_B | 0x0CU) /*!< RW Correlator configuration register 1 */ + (ST25R3916_SPACE_B | 0x0CU) /** RW Correlator configuration register 1 */ #define ST25R3916_REG_CORR_CONF2 \ - (ST25R3916_SPACE_B | 0x0DU) /*!< RW Correlator configuration register 2 */ - -/* Timer definition registers */ -#define ST25R3916_REG_MASK_RX_TIMER \ - 0x0FU /*!< RW Mask Receive Timer Register */ -#define ST25R3916_REG_NO_RESPONSE_TIMER1 \ - 0x10U /*!< RW No-response Timer Register 1 */ -#define ST25R3916_REG_NO_RESPONSE_TIMER2 \ - 0x11U /*!< RW No-response Timer Register 2 */ -#define ST25R3916_REG_TIMER_EMV_CONTROL \ - 0x12U /*!< RW Timer and EMV Control */ -#define ST25R3916_REG_GPT1 0x13U /*!< RW General Purpose Timer Register 1 */ -#define ST25R3916_REG_GPT2 0x14U /*!< RW General Purpose Timer Register 2 */ -#define ST25R3916_REG_PPON2 0x15U /*!< RW PPON2 Field waiting Timer Register */ -#define ST25R3916_REG_SQUELCH_TIMER \ - (ST25R3916_SPACE_B | 0x0FU) /*!< RW Squelch timeout Register */ -#define ST25R3916_REG_FIELD_ON_GT \ - (ST25R3916_SPACE_B | 0x15U) /*!< RW NFC Field on guard time */ - -/* Interrupt and associated reporting registers */ -#define ST25R3916_REG_IRQ_MASK_MAIN \ - 0x16U /*!< RW Mask Main Interrupt Register */ -#define ST25R3916_REG_IRQ_MASK_TIMER_NFC \ - 0x17U /*!< RW Mask Timer and NFC Interrupt Register */ -#define ST25R3916_REG_IRQ_MASK_ERROR_WUP \ - 0x18U /*!< RW Mask Error and Wake-up Interrupt Register */ -#define ST25R3916_REG_IRQ_MASK_TARGET \ - 0x19U /*!< RW Mask 3916 Target Interrupt Register */ -#define ST25R3916_REG_IRQ_MAIN 0x1AU /*!< R Main Interrupt Register */ -#define ST25R3916_REG_IRQ_TIMER_NFC \ - 0x1BU /*!< R Timer and NFC Interrupt Register */ -#define ST25R3916_REG_IRQ_ERROR_WUP \ - 0x1CU /*!< R Error and Wake-up Interrupt Register */ -#define ST25R3916_REG_IRQ_TARGET 0x1DU /*!< R ST25R3916 Target Interrupt Register */ -#define ST25R3916_REG_FIFO_STATUS1 \ - 0x1EU /*!< R FIFO Status Register 1 */ -#define ST25R3916_REG_FIFO_STATUS2 \ - 0x1FU /*!< R FIFO Status Register 2 */ -#define ST25R3916_REG_COLLISION_STATUS \ - 0x20U /*!< R Collision Display Register */ -#define ST25R3916_REG_PASSIVE_TARGET_STATUS \ - 0x21U /*!< R Passive target state status */ - -/* Definition of number of transmitted bytes */ -#define ST25R3916_REG_NUM_TX_BYTES1 \ - 0x22U /*!< RW Number of Transmitted Bytes Register 1 */ -#define ST25R3916_REG_NUM_TX_BYTES2 \ - 0x23U /*!< RW Number of Transmitted Bytes Register 2 */ - -/* NFCIP Bit Rate Display Register */ -#define ST25R3916_REG_NFCIP1_BIT_RATE \ - 0x24U /*!< R NFCIP Bit Rate Detection Display Register */ - -/* A/D Converter Output Register */ -#define ST25R3916_REG_AD_RESULT 0x25U /*!< R A/D Converter Output Register */ - -/* Antenna tuning registers */ -#define ST25R3916_REG_ANT_TUNE_A 0x26U /*!< RW Antenna Tuning Control (AAT-A) Register 1 */ -#define ST25R3916_REG_ANT_TUNE_B 0x27U /*!< RW Antenna Tuning Control (AAT-B) Register 2 */ - -/* Antenna Driver and Modulation registers */ -#define ST25R3916_REG_TX_DRIVER 0x28U /*!< RW TX driver register */ -#define ST25R3916_REG_PT_MOD 0x29U /*!< RW PT modulation Register */ -#define ST25R3916_REG_AUX_MOD \ - (ST25R3916_SPACE_B | 0x28U) /*!< RW Aux Modulation setting Register */ + (ST25R3916_SPACE_B | 0x0DU) /** RW Correlator configuration register 2 */ + +/** Timer definition registers */ +#define ST25R3916_REG_MASK_RX_TIMER 0x0FU /** RW Mask Receive Timer Register */ +#define ST25R3916_REG_NO_RESPONSE_TIMER1 0x10U /** RW No-response Timer Register 1 */ +#define ST25R3916_REG_NO_RESPONSE_TIMER2 0x11U /** RW No-response Timer Register 2 */ +#define ST25R3916_REG_TIMER_EMV_CONTROL 0x12U /** RW Timer and EMV Control */ +#define ST25R3916_REG_GPT1 0x13U /** RW General Purpose Timer Register 1 */ +#define ST25R3916_REG_GPT2 0x14U /** RW General Purpose Timer Register 2 */ +#define ST25R3916_REG_PPON2 0x15U /** RW PPON2 Field waiting Timer Register */ +#define ST25R3916_REG_SQUELCH_TIMER (ST25R3916_SPACE_B | 0x0FU) /** RW Squelch timeout Register */ +#define ST25R3916_REG_FIELD_ON_GT (ST25R3916_SPACE_B | 0x15U) /** RW NFC Field on guard time */ + +/** Interrupt and associated reporting registers */ +#define ST25R3916_REG_IRQ_MASK_MAIN 0x16U /** RW Mask Main Interrupt Register */ +#define ST25R3916_REG_IRQ_MASK_TIMER_NFC 0x17U /** RW Mask Timer and NFC Interrupt Register */ +#define ST25R3916_REG_IRQ_MASK_ERROR_WUP 0x18U /** RW Mask Error and Wake-up Interrupt Register */ +#define ST25R3916_REG_IRQ_MASK_TARGET 0x19U /** RW Mask 3916 Target Interrupt Register */ +#define ST25R3916_REG_IRQ_MAIN 0x1AU /** R Main Interrupt Register */ +#define ST25R3916_REG_IRQ_TIMER_NFC 0x1BU /** R Timer and NFC Interrupt Register */ +#define ST25R3916_REG_IRQ_ERROR_WUP 0x1CU /** R Error and Wake-up Interrupt Register */ +#define ST25R3916_REG_IRQ_TARGET 0x1DU /*!< R ST25R3916 Target Interrupt Register */ +#define ST25R3916_REG_FIFO_STATUS1 0x1EU /** R FIFO Status Register 1 */ +#define ST25R3916_REG_FIFO_STATUS2 0x1FU /** R FIFO Status Register 2 */ +#define ST25R3916_REG_COLLISION_STATUS 0x20U /** R Collision Display Register */ +#define ST25R3916_REG_PASSIVE_TARGET_STATUS 0x21U /** R Passive target state status */ + +/** Definition of number of transmitted bytes */ +#define ST25R3916_REG_NUM_TX_BYTES1 0x22U /** RW Number of Transmitted Bytes Register 1 */ +#define ST25R3916_REG_NUM_TX_BYTES2 0x23U /** RW Number of Transmitted Bytes Register 2 */ + +/** NFCIP Bit Rate Display Register */ +#define ST25R3916_REG_NFCIP1_BIT_RATE 0x24U /** R NFCIP Bit Rate Detection Display Register */ + +/** A/D Converter Output Register */ +#define ST25R3916_REG_AD_RESULT 0x25U /** R A/D Converter Output Register */ + +/** Antenna tuning registers */ +#define ST25R3916_REG_ANT_TUNE_A 0x26U /** RW Antenna Tuning Control (AAT-A) Register 1 */ +#define ST25R3916_REG_ANT_TUNE_B 0x27U /** RW Antenna Tuning Control (AAT-B) Register 2 */ + +/** Antenna Driver and Modulation registers */ +#define ST25R3916_REG_TX_DRIVER 0x28U /** RW TX driver register */ +#define ST25R3916_REG_PT_MOD 0x29U /** RW PT modulation Register */ +#define ST25R3916_REG_AUX_MOD (ST25R3916_SPACE_B | 0x28U) /** RW Aux Modulation setting Register */ #define ST25R3916_REG_TX_DRIVER_TIMING \ - (ST25R3916_SPACE_B | 0x29U) /*!< RW TX driver timing Register */ + (ST25R3916_SPACE_B | 0x29U) /** RW TX driver timing Register */ #define ST25R3916_REG_RES_AM_MOD \ - (ST25R3916_SPACE_B | 0x2AU) /*!< RW Resistive AM modulation register */ + (ST25R3916_SPACE_B | 0x2AU) /** RW Resistive AM modulation register */ #define ST25R3916_REG_TX_DRIVER_STATUS \ - (ST25R3916_SPACE_B | 0x2BU) /*!< R TX driver timing readout Register */ + (ST25R3916_SPACE_B | 0x2BU) /** R TX driver timing readout Register */ -/* External Field Detector Threshold Registers */ +/** External Field Detector Threshold Registers */ #define ST25R3916_REG_FIELD_THRESHOLD_ACTV \ - 0x2AU /*!< RW External Field Detector Activation Threshold Reg */ + 0x2AU /** RW External Field Detector Activation Threshold Reg */ #define ST25R3916_REG_FIELD_THRESHOLD_DEACTV \ - 0x2BU /*!< RW External Field Detector Deactivation Threshold Reg*/ + 0x2BU /** RW External Field Detector Deactivation Threshold Reg */ -/* Regulator registers */ -#define ST25R3916_REG_REGULATOR_CONTROL \ - 0x2CU /*!< RW Regulated Voltage Control Register */ +/** Regulator registers */ +#define ST25R3916_REG_REGULATOR_CONTROL 0x2CU /** RW Regulated Voltage Control Register */ #define ST25R3916_REG_REGULATOR_RESULT \ - (ST25R3916_SPACE_B | 0x2CU) /*!< R Regulator Display Register */ - -/* Receiver State Display Register */ -#define ST25R3916_REG_RSSI_RESULT \ - 0x2DU /*!< R RSSI Display Register */ -#define ST25R3916_REG_GAIN_RED_STATE \ - 0x2EU /*!< R Gain Reduction State Register */ -#define ST25R3916_REG_CAP_SENSOR_CONTROL \ - 0x2FU /*!< RW Capacitive Sensor Control Register */ -#define ST25R3916_REG_CAP_SENSOR_RESULT \ - 0x30U /*!< R Capacitive Sensor Display Register */ -#define ST25R3916_REG_AUX_DISPLAY \ - 0x31U /*!< R Auxiliary Display Register */ - -/* Over/Undershoot Protection Configuration Registers */ + (ST25R3916_SPACE_B | 0x2CU) /** R Regulator Display Register */ + +/** Receiver State Display Register */ +#define ST25R3916_REG_RSSI_RESULT 0x2DU /** R RSSI Display Register */ +#define ST25R3916_REG_GAIN_RED_STATE 0x2EU /** R Gain Reduction State Register */ +#define ST25R3916_REG_CAP_SENSOR_CONTROL 0x2FU /** RW Capacitive Sensor Control Register */ +#define ST25R3916_REG_CAP_SENSOR_RESULT 0x30U /** R Capacitive Sensor Display Register */ +#define ST25R3916_REG_AUX_DISPLAY 0x31U /** R Auxiliary Display Register */ + +/** Over/Undershoot Protection Configuration Registers */ #define ST25R3916_REG_OVERSHOOT_CONF1 \ - (ST25R3916_SPACE_B | 0x30U) /*!< RW Overshoot Protection Configuration Register 1 */ + (ST25R3916_SPACE_B | 0x30U) /** RW Overshoot Protection Configuration Register 1 */ #define ST25R3916_REG_OVERSHOOT_CONF2 \ - (ST25R3916_SPACE_B | 0x31U) /*!< RW Overshoot Protection Configuration Register 2 */ + (ST25R3916_SPACE_B | 0x31U) /** RW Overshoot Protection Configuration Register 2 */ #define ST25R3916_REG_UNDERSHOOT_CONF1 \ - (ST25R3916_SPACE_B | 0x32U) /*!< RW Undershoot Protection Configuration Register 1 */ + (ST25R3916_SPACE_B | 0x32U) /** RW Undershoot Protection Configuration Register 1 */ #define ST25R3916_REG_UNDERSHOOT_CONF2 \ - (ST25R3916_SPACE_B | 0x33U) /*!< RW Undershoot Protection Configuration Register 2 */ + (ST25R3916_SPACE_B | 0x33U) /** RW Undershoot Protection Configuration Register 2 */ -/* Detection of card presence */ -#define ST25R3916_REG_WUP_TIMER_CONTROL \ - 0x32U /*!< RW Wake-up Timer Control Register */ +/** Detection of card presence */ +#define ST25R3916_REG_WUP_TIMER_CONTROL 0x32U /** RW Wake-up Timer Control Register */ #define ST25R3916_REG_AMPLITUDE_MEASURE_CONF \ - 0x33U /*!< RW Amplitude Measurement Configuration Register */ + 0x33U /** RW Amplitude Measurement Configuration Register */ #define ST25R3916_REG_AMPLITUDE_MEASURE_REF \ - 0x34U /*!< RW Amplitude Measurement Reference Register */ + 0x34U /** RW Amplitude Measurement Reference Register */ #define ST25R3916_REG_AMPLITUDE_MEASURE_AA_RESULT \ - 0x35U /*!< R Amplitude Measurement Auto Averaging Display Reg */ + 0x35U /** R Amplitude Measurement Auto Averaging Display Reg */ #define ST25R3916_REG_AMPLITUDE_MEASURE_RESULT \ - 0x36U /*!< R Amplitude Measurement Display Register */ -#define ST25R3916_REG_PHASE_MEASURE_CONF \ - 0x37U /*!< RW Phase Measurement Configuration Register */ -#define ST25R3916_REG_PHASE_MEASURE_REF \ - 0x38U /*!< RW Phase Measurement Reference Register */ + 0x36U /** R Amplitude Measurement Display Register */ +#define ST25R3916_REG_PHASE_MEASURE_CONF 0x37U /** RW Phase Measurement Configuration Register */ +#define ST25R3916_REG_PHASE_MEASURE_REF 0x38U /** RW Phase Measurement Reference Register */ #define ST25R3916_REG_PHASE_MEASURE_AA_RESULT \ - 0x39U /*!< R Phase Measurement Auto Averaging Display Register */ -#define ST25R3916_REG_PHASE_MEASURE_RESULT \ - 0x3AU /*!< R Phase Measurement Display Register */ + 0x39U /** R Phase Measurement Auto Averaging Display */ +#define ST25R3916_REG_PHASE_MEASURE_RESULT 0x3AU /** R Phase Measurement Display Register */ #define ST25R3916_REG_CAPACITANCE_MEASURE_CONF \ - 0x3BU /*!< RW Capacitance Measurement Configuration Register */ + 0x3BU /** RW Capacitance Measurement Configuration Register */ #define ST25R3916_REG_CAPACITANCE_MEASURE_REF \ - 0x3CU /*!< RW Capacitance Measurement Reference Register */ + 0x3CU /** RW Capacitance Measurement Reference Register */ #define ST25R3916_REG_CAPACITANCE_MEASURE_AA_RESULT \ - 0x3DU /*!< R Capacitance Measurement Auto Averaging Display Reg*/ + 0x3DU /** R Capacitance Measurement Auto Averaging Display Reg */ #define ST25R3916_REG_CAPACITANCE_MEASURE_RESULT \ - 0x3EU /*!< R Capacitance Measurement Display Register */ + 0x3EU /** R Capacitance Measurement Display Register */ -/* IC identity */ -#define ST25R3916_REG_IC_IDENTITY \ - 0x3FU /*!< R Chip Id: 0 for old silicon, v2 silicon: 0x09 */ +/** IC identity */ +#define ST25R3916_REG_IC_IDENTITY 0x3FU /** R Chip Id: 0 for old silicon, v2 silicon: 0x09 */ -/*! Register bit definitions \cond DOXYGEN_SUPRESS */ +/** Register bit definitions */ #define ST25R3916_REG_IO_CONF1_single (1U << 7) #define ST25R3916_REG_IO_CONF1_rfo2 (1U << 6) @@ -1021,364 +961,184 @@ #define ST25R3916_REG_IC_IDENTITY_ic_rev_mask (7U << 0) #define ST25R3916_REG_IC_IDENTITY_ic_rev_shift (0U) -/*! \endcond DOXYGEN_SUPRESS */ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Returns the content of a register within the ST25R3916 - * - * This function is used to read out the content of ST25R3916 registers. +/** Read register * - * \param[in] reg: Address of register to read. - * \param[out] val: Returned value. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer t FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - pointer to the variable to store the read value */ -ReturnCode st25r3916ReadRegister(uint8_t reg, uint8_t* val); +void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); -/*! - ***************************************************************************** - * \brief Reads from multiple ST25R3916 registers - * - * This function is used to read from multiple registers using the - * auto-increment feature. That is, after each read the address pointer - * inside the ST25R3916 gets incremented automatically. +/** Read multiple registers * - * \param[in] reg: Address of the first register to read from. - * \param[in] values: pointer to a buffer where the result shall be written to. - * \param[in] length: Number of registers to be read out. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg_start - start register address + * @param values - pointer to the buffer to store the read values + * @param length - number of registers to read */ -ReturnCode st25r3916ReadMultipleRegisters(uint8_t reg, uint8_t* values, uint8_t length); +void st25r3916_read_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + uint8_t* values, + uint8_t length); -/*! - ***************************************************************************** - * \brief Writes a given value to a register within the ST25R3916 - * - * This function is used to write \a val to address \a reg within the ST25R3916. +/** Write register * - * \param[in] reg: Address of the register to write. - * \param[in] val: Value to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - value to write */ -ReturnCode st25r3916WriteRegister(uint8_t reg, uint8_t val); +void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); -/*! - ***************************************************************************** - * \brief Writes multiple values to ST25R3916 registers - * - * This function is used to write multiple values to the ST25R3916 using the - * auto-increment feature. That is, after each write the address pointer - * inside the ST25R3916 gets incremented automatically. +/** Write multiple registers * - * \param[in] reg: Address of the first register to write. - * \param[in] values: pointer to a buffer containing the values to be written. - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg_start - start register address + * @param values - pointer to buffer to write + * @param length - number of registers to write */ -ReturnCode st25r3916WriteMultipleRegisters(uint8_t reg, const uint8_t* values, uint8_t length); +void st25r3916_write_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + const uint8_t* values, + uint8_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 FIFO - * - * This function needs to be called in order to write to the ST25R3916 FIFO. +/** Write fifo register * - * \param[in] values: pointer to a buffer containing the values to be written - * to the FIFO. - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to write to FIFO + * @param length - number of bytes to write */ -ReturnCode st25r3916WriteFifo(const uint8_t* values, uint16_t length); +void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length); -/*! - ***************************************************************************** - * \brief Read values from ST25R3916 FIFO - * - * This function needs to be called in order to read from ST25R3916 FIFO. +/** Read fifo register * - * \param[out] buf: pointer to a buffer where the FIFO content shall be - * written to. - * \param[in] length: Number of bytes to read. - * - * \note: This function doesn't check whether \a length is really the - * number of available bytes in FIFO - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to store the read values + * @param length - number of bytes to read */ -ReturnCode st25r3916ReadFifo(uint8_t* buf, uint16_t length); +void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 PTM - * - * Accesses to the begging of ST25R3916 Passive Target Memory (PTM A Config) - * and writes the given values +/** Write PTA memory register * - * \param[in] values: pointer to a buffer containing the values to be written - * to the Passive Target Memory. - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - pointer to buffer to write + * @param length - number of bytes to write */ -ReturnCode st25r3916WritePTMem(const uint8_t* values, uint16_t length); +void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Reads the ST25R3916 PTM - * - * Accesses to the begging of ST25R3916 Passive Target Memory (PTM A Config) - * and reads the memory for the given length - * - * \param[out] values: pointer to a buffer where the PTM content shall be - * written to. - * \param[in] length: Number of bytes to read. +/** Read PTA memory register * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - buffer to store the read values + * @param length - number of bytes to read */ -ReturnCode st25r3916ReadPTMem(uint8_t* values, uint16_t length); +void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 PTM F config +/** Write PTF memory register * - * Accesses ST25R3916 Passive Target Memory F config and writes the given values - * - * \param[in] values: pointer to a buffer containing the values to be written - * to the Passive Target Memory - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - pointer to buffer to write + * @param length - number of bytes to write */ -ReturnCode st25r3916WritePTMemF(const uint8_t* values, uint16_t length); +void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 PTM TSN Data - * - * Accesses ST25R3916 Passive Target Memory TSN data and writes the given values - * - * \param[in] values: pointer to a buffer containing the values to be written - * to the Passive Target Memory. - * \param[in] length: Number of values to be written. +/** Read PTTSN memory register * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - pointer to buffer to write + * @param length - number of bytes to write */ -ReturnCode st25r3916WritePTMemTSN(const uint8_t* values, uint16_t length); +void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Execute a direct command +/** Send Direct command * - * This function is used to start so-called direct command. These commands - * are implemented inside the chip and each command has unique code (see - * datasheet). - * - * \param[in] cmd : code of the direct command to be executed. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param cmd - direct command */ -ReturnCode st25r3916ExecuteCommand(uint8_t cmd); +void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd); -/*! - ***************************************************************************** - * \brief Read a test register within the ST25R3916 - * - * This function is used to read the content of test address \a reg within the ST25R3916 - * - * \param[in] reg: Address of the register to read - * \param[out] val: Returned read value - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** +/** Read test register + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - pointer to the variable to store the read value */ -ReturnCode st25r3916ReadTestRegister(uint8_t reg, uint8_t* val); +void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); -/*! - ***************************************************************************** - * \brief Writes a given value to a test register within the ST25R3916 - * - * This function is used to write \a val to test address \a reg within the ST25R3916 +/** Write test register * - * \param[in] reg: Address of the register to write - * \param[in] val: Value to be written - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - value to write */ -ReturnCode st25r3916WriteTestRegister(uint8_t reg, uint8_t val); +void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); -/*! - ***************************************************************************** - * \brief Cleart bits on Register - * - * This function clears the given bitmask on the register +/** Clear register bits * - * \param[in] reg: Address of the register clear - * \param[in] clr_mask: Bitmask of bit to be cleared - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param clr_mask - bit mask to clear */ -ReturnCode st25r3916ClrRegisterBits(uint8_t reg, uint8_t clr_mask); +void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask); -/*! - ***************************************************************************** - * \brief Set bits on Register - * - * This function sets the given bitmask on the register - * - * \param[in] reg: Address of the register clear - * \param[in] set_mask: Bitmask of bit to be cleared +/** Set register bits * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param set_mask - bit mask to set */ -ReturnCode st25r3916SetRegisterBits(uint8_t reg, uint8_t set_mask); +void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask); -/*! - ***************************************************************************** - * \brief Changes the given bits on a ST25R3916 register +/** Change register bits * - * This function is used if only a particular bits should be changed within - * an ST25R3916 register. - * - * \param[in] reg: Address of the register to change. - * \param[in] valueMask: bitmask of bits to be changed - * \param[in] value: the bits to be written on the enabled valueMask bits - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param mask - bit mask to change + * @param value - new register value to write */ -ReturnCode st25r3916ChangeRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value); - -/*! - ***************************************************************************** - * \brief Modifies a value within a ST25R3916 register - * - * This function is used if only a particular bits should be changed within - * an ST25R3916 register. - * - * \param[in] reg: Address of the register to write. - * \param[in] clr_mask: bitmask of bits to be cleared to 0. - * \param[in] set_mask: bitmask of bits to be set to 1. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** +void st25r3916_change_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value); + +/** Modify register + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param clr_mask - bit mask to clear + * @param set_mask - bit mask to set */ -ReturnCode st25r3916ModifyRegister(uint8_t reg, uint8_t clr_mask, uint8_t set_mask); - -/*! - ***************************************************************************** - * \brief Changes the given bits on a ST25R3916 Test register - * - * This function is used if only a particular bits should be changed within - * an ST25R3916 register. - * - * \param[in] reg: Address of the Test register to change. - * \param[in] valueMask: bitmask of bits to be changed - * \param[in] value: the bits to be written on the enabled valueMask bits - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** +void st25r3916_modify_reg( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t clr_mask, + uint8_t set_mask); + +/** Change test register bits + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param mask - bit mask to change + * @param value - new register value to write */ -ReturnCode st25r3916ChangeTestRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value); +void st25r3916_change_test_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value); -/*! - ***************************************************************************** - * \brief Checks if register contains a expected value +/** Check register * - * This function checks if the given reg contains a value that once masked - * equals the expected value + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param mask - bit mask to check + * @param val - expected register value * - * \param reg : the register to check the value - * \param mask : the mask apply on register value - * \param val : expected value to be compared to - * - * \return true when reg contains the expected value | false otherwise + * @return true if register value matches the expected value, false otherwise */ -bool st25r3916CheckReg(uint8_t reg, uint8_t mask, uint8_t val); +bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val); -/*! - ***************************************************************************** - * \brief Check if register ID is valid - * - * Checks if the given register ID a valid ST25R3916 register - * - * \param[in] reg: Address of register to check - * - * \return true if is a valid register ID - * \return false otherwise - * - ***************************************************************************** - */ -bool st25r3916IsRegValid(uint8_t reg); - -#endif /* ST25R3916_COM_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ +#ifdef __cplusplus +} +#endif diff --git a/lib/misc.scons b/lib/misc.scons index 10aa3f9d5a..92fa5a106f 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -15,6 +15,9 @@ env.Append( ], SDK_HEADERS=[ File("micro-ecc/uECC.h"), + File("nanopb/pb.h"), + File("nanopb/pb_decode.h"), + File("nanopb/pb_encode.h"), ], ) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 7a0859ee46..0e09b69392 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -5,20 +5,53 @@ env.Append( "#/lib/nfc", ], SDK_HEADERS=[ + # Main + File("nfc.h"), File("nfc_device.h"), - File("nfc_worker.h"), - File("nfc_types.h"), - File("helpers/mfkey32.h"), - File("parsers/nfc_supported_card.h"), - File("helpers/nfc_generators.h"), - File("protocols/nfc_util.h"), + File("nfc_listener.h"), + File("nfc_poller.h"), + File("nfc_scanner.h"), + # Protocols + File("protocols/iso14443_3a/iso14443_3a.h"), + File("protocols/iso14443_3b/iso14443_3b.h"), + File("protocols/iso14443_4a/iso14443_4a.h"), + File("protocols/iso14443_4b/iso14443_4b.h"), + File("protocols/mf_ultralight/mf_ultralight.h"), + File("protocols/mf_classic/mf_classic.h"), + File("protocols/mf_desfire/mf_desfire.h"), + File("protocols/slix/slix.h"), + File("protocols/st25tb/st25tb.h"), + # Pollers + File("protocols/iso14443_3a/iso14443_3a_poller.h"), + File("protocols/iso14443_3b/iso14443_3b_poller.h"), + File("protocols/iso14443_4a/iso14443_4a_poller.h"), + File("protocols/iso14443_4b/iso14443_4b_poller.h"), + File("protocols/mf_ultralight/mf_ultralight_poller.h"), + File("protocols/mf_classic/mf_classic_poller.h"), + File("protocols/mf_desfire/mf_desfire_poller.h"), + File("protocols/st25tb/st25tb_poller.h"), + # Listeners + File("protocols/iso14443_3a/iso14443_3a_listener.h"), + File("protocols/iso14443_4a/iso14443_4a_listener.h"), + File("protocols/mf_ultralight/mf_ultralight_listener.h"), + File("protocols/mf_classic/mf_classic_listener.h"), + # Sync API + File("protocols/iso14443_3a/iso14443_3a_poller_sync_api.h"), + File("protocols/mf_ultralight/mf_ultralight_poller_sync_api.h"), + File("protocols/mf_classic/mf_classic_poller_sync_api.h"), + # Misc + File("helpers/nfc_util.h"), + File("helpers/iso14443_crc.h"), + File("helpers/iso13239_crc.h"), + File("helpers/nfc_data_generator.h"), + File("helpers/nfc_dict.h"), ], ) libenv = env.Clone(FW_LIB_NAME="nfc") libenv.ApplyLibFlags() -sources = libenv.GlobRecursive("*.c*") +sources = libenv.GlobRecursive("*.c*", exclude="deprecated/*c") lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/nfc/helpers/felica_crc.c b/lib/nfc/helpers/felica_crc.c new file mode 100644 index 0000000000..d5e0853a10 --- /dev/null +++ b/lib/nfc/helpers/felica_crc.c @@ -0,0 +1,52 @@ +#include "felica_crc.h" + +#include + +#define FELICA_CRC_POLY (0x1021U) // Polynomial: x^16 + x^12 + x^5 + 1 +#define FELICA_CRC_INIT (0x0000U) + +uint16_t felica_crc_calculate(const uint8_t* data, size_t length) { + uint16_t crc = FELICA_CRC_INIT; + + for(size_t i = 0; i < length; i++) { + crc ^= ((uint16_t)data[i] << 8); + for(size_t j = 0; j < 8; j++) { + if(crc & 0x8000) { + crc <<= 1; + crc ^= FELICA_CRC_POLY; + } else { + crc <<= 1; + } + } + } + + return (crc << 8) | (crc >> 8); +} + +void felica_crc_append(BitBuffer* buf) { + const uint8_t* data = bit_buffer_get_data(buf); + const size_t data_size = bit_buffer_get_size_bytes(buf); + + const uint16_t crc = felica_crc_calculate(data, data_size); + bit_buffer_append_bytes(buf, (const uint8_t*)&crc, FELICA_CRC_SIZE); +} + +bool felica_crc_check(const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + if(data_size <= FELICA_CRC_SIZE) return false; + + uint16_t crc_received; + bit_buffer_write_bytes_mid(buf, &crc_received, data_size - FELICA_CRC_SIZE, FELICA_CRC_SIZE); + + const uint8_t* data = bit_buffer_get_data(buf); + const uint16_t crc_calc = felica_crc_calculate(data, data_size - FELICA_CRC_SIZE); + + return (crc_calc == crc_received); +} + +void felica_crc_trim(BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + furi_assert(data_size > FELICA_CRC_SIZE); + + bit_buffer_set_size_bytes(buf, data_size - FELICA_CRC_SIZE); +} diff --git a/lib/nfc/helpers/felica_crc.h b/lib/nfc/helpers/felica_crc.h new file mode 100644 index 0000000000..d1dc29e74d --- /dev/null +++ b/lib/nfc/helpers/felica_crc.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "bit_buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FELICA_CRC_SIZE sizeof(uint16_t) + +void felica_crc_append(BitBuffer* buf); + +bool felica_crc_check(const BitBuffer* buf); + +void felica_crc_trim(BitBuffer* buf); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/iso13239_crc.c b/lib/nfc/helpers/iso13239_crc.c new file mode 100644 index 0000000000..c54fbcfb21 --- /dev/null +++ b/lib/nfc/helpers/iso13239_crc.c @@ -0,0 +1,62 @@ +#include "iso13239_crc.h" + +#include + +#define ISO13239_CRC_INIT_DEFAULT (0xFFFFU) +#define ISO13239_CRC_INIT_PICOPASS (0xE012U) +#define ISO13239_CRC_POLY (0x8408U) + +static uint16_t + iso13239_crc_calculate(Iso13239CrcType type, const uint8_t* data, size_t data_size) { + uint16_t crc; + + if(type == Iso13239CrcTypeDefault) { + crc = ISO13239_CRC_INIT_DEFAULT; + } else if(type == Iso13239CrcTypePicopass) { + crc = ISO13239_CRC_INIT_PICOPASS; + } else { + furi_crash("Wrong ISO13239 CRC type"); + } + + for(size_t i = 0; i < data_size; ++i) { + crc ^= (uint16_t)data[i]; + for(size_t j = 0; j < 8; ++j) { + if(crc & 1U) { + crc = (crc >> 1) ^ ISO13239_CRC_POLY; + } else { + crc >>= 1; + } + } + } + + return type == Iso13239CrcTypePicopass ? crc : ~crc; +} + +void iso13239_crc_append(Iso13239CrcType type, BitBuffer* buf) { + const uint8_t* data = bit_buffer_get_data(buf); + const size_t data_size = bit_buffer_get_size_bytes(buf); + + const uint16_t crc = iso13239_crc_calculate(type, data, data_size); + bit_buffer_append_bytes(buf, (const uint8_t*)&crc, ISO13239_CRC_SIZE); +} + +bool iso13239_crc_check(Iso13239CrcType type, const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + if(data_size <= ISO13239_CRC_SIZE) return false; + + uint16_t crc_received; + bit_buffer_write_bytes_mid( + buf, &crc_received, data_size - ISO13239_CRC_SIZE, ISO13239_CRC_SIZE); + + const uint8_t* data = bit_buffer_get_data(buf); + const uint16_t crc_calc = iso13239_crc_calculate(type, data, data_size - ISO13239_CRC_SIZE); + + return (crc_calc == crc_received); +} + +void iso13239_crc_trim(BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + furi_assert(data_size > ISO13239_CRC_SIZE); + + bit_buffer_set_size_bytes(buf, data_size - ISO13239_CRC_SIZE); +} diff --git a/lib/nfc/helpers/iso13239_crc.h b/lib/nfc/helpers/iso13239_crc.h new file mode 100644 index 0000000000..c71ec6befe --- /dev/null +++ b/lib/nfc/helpers/iso13239_crc.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO13239_CRC_SIZE sizeof(uint16_t) + +typedef enum { + Iso13239CrcTypeDefault, + Iso13239CrcTypePicopass, +} Iso13239CrcType; + +void iso13239_crc_append(Iso13239CrcType type, BitBuffer* buf); + +bool iso13239_crc_check(Iso13239CrcType type, const BitBuffer* buf); + +void iso13239_crc_trim(BitBuffer* buf); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c new file mode 100644 index 0000000000..26f4dc3b7b --- /dev/null +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -0,0 +1,64 @@ +#include "iso14443_4_layer.h" + +#include + +#define ISO14443_4_BLOCK_PCB (1U << 1) +#define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_R (5U << 5) +#define ISO14443_4_BLOCK_PCB_S (3U << 6) + +struct Iso14443_4Layer { + uint8_t pcb; + uint8_t pcb_prev; +}; + +static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance) { + instance->pcb_prev = instance->pcb; + instance->pcb ^= (uint8_t)0x01; +} + +Iso14443_4Layer* iso14443_4_layer_alloc() { + Iso14443_4Layer* instance = malloc(sizeof(Iso14443_4Layer)); + + iso14443_4_layer_reset(instance); + return instance; +} + +void iso14443_4_layer_free(Iso14443_4Layer* instance) { + furi_assert(instance); + free(instance); +} + +void iso14443_4_layer_reset(Iso14443_4Layer* instance) { + furi_assert(instance); + instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; +} + +void iso14443_4_layer_encode_block( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + bit_buffer_append_byte(block_data, instance->pcb); + bit_buffer_append(block_data, input_data); + + iso14443_4_layer_update_pcb(instance); +} + +bool iso14443_4_layer_decode_block( + Iso14443_4Layer* instance, + BitBuffer* output_data, + const BitBuffer* block_data) { + furi_assert(instance); + + bool ret = false; + + do { + if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; + bit_buffer_copy_right(output_data, block_data, 1); + ret = true; + } while(false); + + return ret; +} diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h new file mode 100644 index 0000000000..712173ce1b --- /dev/null +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4Layer Iso14443_4Layer; + +Iso14443_4Layer* iso14443_4_layer_alloc(); + +void iso14443_4_layer_free(Iso14443_4Layer* instance); + +void iso14443_4_layer_reset(Iso14443_4Layer* instance); + +void iso14443_4_layer_encode_block( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +bool iso14443_4_layer_decode_block( + Iso14443_4Layer* instance, + BitBuffer* output_data, + const BitBuffer* block_data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/iso14443_crc.c b/lib/nfc/helpers/iso14443_crc.c new file mode 100644 index 0000000000..fda9871aa9 --- /dev/null +++ b/lib/nfc/helpers/iso14443_crc.c @@ -0,0 +1,57 @@ +#include "iso14443_crc.h" + +#include + +#define ISO14443_3A_CRC_INIT (0x6363U) +#define ISO14443_3B_CRC_INIT (0xFFFFU) + +static uint16_t + iso14443_crc_calculate(Iso14443CrcType type, const uint8_t* data, size_t data_size) { + uint16_t crc; + + if(type == Iso14443CrcTypeA) { + crc = ISO14443_3A_CRC_INIT; + } else if(type == Iso14443CrcTypeB) { + crc = ISO14443_3B_CRC_INIT; + } else { + furi_crash("Wrong ISO14443 CRC type"); + } + + for(size_t i = 0; i < data_size; i++) { + uint8_t byte = data[i]; + byte ^= (uint8_t)(crc & 0xff); + byte ^= byte << 4; + crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^ (byte >> 4); + } + + return type == Iso14443CrcTypeA ? crc : ~crc; +} + +void iso14443_crc_append(Iso14443CrcType type, BitBuffer* buf) { + const uint8_t* data = bit_buffer_get_data(buf); + const size_t data_size = bit_buffer_get_size_bytes(buf); + + const uint16_t crc = iso14443_crc_calculate(type, data, data_size); + bit_buffer_append_bytes(buf, (const uint8_t*)&crc, ISO14443_CRC_SIZE); +} + +bool iso14443_crc_check(Iso14443CrcType type, const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + if(data_size <= ISO14443_CRC_SIZE) return false; + + uint16_t crc_received; + bit_buffer_write_bytes_mid( + buf, &crc_received, data_size - ISO14443_CRC_SIZE, ISO14443_CRC_SIZE); + + const uint8_t* data = bit_buffer_get_data(buf); + const uint16_t crc_calc = iso14443_crc_calculate(type, data, data_size - ISO14443_CRC_SIZE); + + return (crc_calc == crc_received); +} + +void iso14443_crc_trim(BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + furi_assert(data_size > ISO14443_CRC_SIZE); + + bit_buffer_set_size_bytes(buf, data_size - ISO14443_CRC_SIZE); +} diff --git a/lib/nfc/helpers/iso14443_crc.h b/lib/nfc/helpers/iso14443_crc.h new file mode 100644 index 0000000000..14a63841e7 --- /dev/null +++ b/lib/nfc/helpers/iso14443_crc.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_CRC_SIZE sizeof(uint16_t) + +typedef enum { + Iso14443CrcTypeA, + Iso14443CrcTypeB, +} Iso14443CrcType; + +void iso14443_crc_append(Iso14443CrcType type, BitBuffer* buf); + +bool iso14443_crc_check(Iso14443CrcType type, const BitBuffer* buf); + +void iso14443_crc_trim(BitBuffer* buf); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c deleted file mode 100644 index 93098d409b..0000000000 --- a/lib/nfc/helpers/mf_classic_dict.c +++ /dev/null @@ -1,346 +0,0 @@ -#include "mf_classic_dict.h" - -#include -#include - -#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") - -#define TAG "MfClassicDict" - -#define NFC_MF_CLASSIC_KEY_LEN (13) - -struct MfClassicDict { - Stream* stream; - uint32_t total_keys; -}; - -bool mf_classic_dict_check_presence(MfClassicDictType dict_type) { - Storage* storage = furi_record_open(RECORD_STORAGE); - - bool dict_present = false; - if(dict_type == MfClassicDictTypeSystem) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; - } else if(dict_type == MfClassicDictTypeUser) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; - } else if(dict_type == MfClassicDictTypeUnitTest) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == - FSE_OK; - } - - furi_record_close(RECORD_STORAGE); - - return dict_present; -} - -MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { - MfClassicDict* dict = malloc(sizeof(MfClassicDict)); - Storage* storage = furi_record_open(RECORD_STORAGE); - dict->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); - - bool dict_loaded = false; - do { - if(dict_type == MfClassicDictTypeSystem) { - if(!buffered_file_stream_open( - dict->stream, - MF_CLASSIC_DICT_FLIPPER_PATH, - FSAM_READ_WRITE, - FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == MfClassicDictTypeUser) { - if(!buffered_file_stream_open( - dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == MfClassicDictTypeUnitTest) { - if(!buffered_file_stream_open( - dict->stream, - MF_CLASSIC_DICT_UNIT_TEST_PATH, - FSAM_READ_WRITE, - FSOM_OPEN_ALWAYS)) { - buffered_file_stream_close(dict->stream); - break; - } - } - - // Check for new line ending - if(!stream_eof(dict->stream)) { - if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break; - uint8_t last_char = 0; - if(stream_read(dict->stream, &last_char, 1) != 1) break; - if(last_char != '\n') { - FURI_LOG_D(TAG, "Adding new line ending"); - if(stream_write_char(dict->stream, '\n') != 1) break; - } - if(!stream_rewind(dict->stream)) break; - } - - // Read total amount of keys - FuriString* next_line; - next_line = furi_string_alloc(); - while(true) { - if(!stream_read_line(dict->stream, next_line)) { - FURI_LOG_T(TAG, "No keys left in dict"); - break; - } - FURI_LOG_T( - TAG, - "Read line: %s, len: %zu", - furi_string_get_cstr(next_line), - furi_string_size(next_line)); - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - dict->total_keys++; - } - furi_string_free(next_line); - stream_rewind(dict->stream); - - dict_loaded = true; - FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); - } while(false); - - if(!dict_loaded) { - buffered_file_stream_close(dict->stream); - free(dict); - dict = NULL; - } - - return dict; -} - -void mf_classic_dict_free(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - buffered_file_stream_close(dict->stream); - stream_free(dict->stream); - free(dict); -} - -static void mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) { - furi_string_reset(key_str); - for(size_t i = 0; i < 6; i++) { - furi_string_cat_printf(key_str, "%02X", key_int[i]); - } -} - -static void mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { - uint8_t key_byte_tmp; - - *key_int = 0ULL; - for(uint8_t i = 0; i < 12; i += 2) { - args_char_to_hex( - furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2)); - } -} - -uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) { - furi_assert(dict); - - return dict->total_keys; -} - -bool mf_classic_dict_rewind(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - return stream_rewind(dict->stream); -} - -bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - bool key_read = false; - furi_string_reset(key); - while(!key_read) { - if(!stream_read_line(dict->stream, key)) break; - if(furi_string_get_char(key, 0) == '#') continue; - if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(key, 12); - key_read = true; - } - - return key_read; -} - -bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - bool key_read = mf_classic_dict_get_next_key_str(dict, temp_key); - if(key_read) { - mf_classic_dict_str_to_int(temp_key, key); - } - furi_string_free(temp_key); - return key_read; -} - -bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - - bool key_found = false; - stream_rewind(dict->stream); - while(!key_found) { //-V654 - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(next_line, 12); - if(!furi_string_equal(key, next_line)) continue; - key_found = true; - } - - furi_string_free(next_line); - return key_found; -} - -bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { - FuriString* temp_key; - - temp_key = furi_string_alloc(); - mf_classic_dict_int_to_str(key, temp_key); - bool key_found = mf_classic_dict_is_key_present_str(dict, temp_key); - furi_string_free(temp_key); - return key_found; -} - -bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - furi_string_cat_printf(key, "\n"); - - bool key_added = false; - do { - if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; - if(!stream_insert_string(dict->stream, key)) break; - dict->total_keys++; - key_added = true; - } while(false); - - furi_string_left(key, 12); - return key_added; -} - -bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - mf_classic_dict_int_to_str(key, temp_key); - bool key_added = mf_classic_dict_add_key_str(dict, temp_key); - - furi_string_free(temp_key); - return key_added; -} - -bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - uint32_t index = 0; - next_line = furi_string_alloc(); - furi_string_reset(key); - - bool key_found = false; - while(!key_found) { - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - if(index++ != target) continue; - furi_string_set_n(key, next_line, 0, 12); - key_found = true; - } - - furi_string_free(next_line); - return key_found; -} - -bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - bool key_found = mf_classic_dict_get_key_at_index_str(dict, temp_key, target); - if(key_found) { - mf_classic_dict_str_to_int(temp_key, key); - } - furi_string_free(temp_key); - return key_found; -} - -bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - - bool key_found = false; - uint32_t index = 0; - stream_rewind(dict->stream); - while(!key_found) { //-V654 - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(next_line, 12); - if(!furi_string_equal(key, next_line)) continue; - key_found = true; - *target = index; - } - - furi_string_free(next_line); - return key_found; -} - -bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - mf_classic_dict_int_to_str(key, temp_key); - bool key_found = mf_classic_dict_find_index_str(dict, temp_key, target); - - furi_string_free(temp_key); - return key_found; -} - -bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - uint32_t index = 0; - - bool key_removed = false; - while(!key_removed) { - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - if(index++ != target) continue; - stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent); - if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break; - dict->total_keys--; - key_removed = true; - } - - furi_string_free(next_line); - return key_removed; -} diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h deleted file mode 100644 index b798b1c92e..0000000000 --- a/lib/nfc/helpers/mf_classic_dict.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - MfClassicDictTypeUser, - MfClassicDictTypeSystem, - MfClassicDictTypeUnitTest, -} MfClassicDictType; - -typedef struct MfClassicDict MfClassicDict; - -bool mf_classic_dict_check_presence(MfClassicDictType dict_type); - -/** Allocate MfClassicDict instance - * - * @param[in] dict_type The dictionary type - * - * @return MfClassicDict instance - */ -MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type); - -/** Free MfClassicDict instance - * - * @param dict MfClassicDict instance - */ -void mf_classic_dict_free(MfClassicDict* dict); - -/** Get total keys count - * - * @param dict MfClassicDict instance - * - * @return total keys count - */ -uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict); - -/** Rewind to the beginning - * - * @param dict MfClassicDict instance - * - * @return true on success - */ -bool mf_classic_dict_rewind(MfClassicDict* dict); - -bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key); - -bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key); - -bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key); - -bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key); - -/** Get key at target offset as uint64_t - * - * @param dict MfClassicDict instance - * @param[out] key Pointer to the uint64_t key - * @param[in] target Target offset from current position - * - * @return true on success - */ -bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target); - -/** Get key at target offset as string_t - * - * @param dict MfClassicDict instance - * @param[out] key Found key destination buffer - * @param[in] target Target offset from current position - * - * @return true on success - */ -bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target); - -bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); - -/** Add string representation of the key - * - * @param dict MfClassicDict instance - * @param[in] key String representation of the key - * - * @return true on success - */ -bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key); - -bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target); - -bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target); - -/** Delete key at target offset - * - * @param dict MfClassicDict instance - * @param[in] target Target offset from current position - * - * @return true on success - */ -bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/helpers/mfkey32.c b/lib/nfc/helpers/mfkey32.c deleted file mode 100644 index 47e7e9f6b3..0000000000 --- a/lib/nfc/helpers/mfkey32.c +++ /dev/null @@ -1,228 +0,0 @@ -#include "mfkey32.h" - -#include -#include -#include -#include -#include - -#include -#include - -#define TAG "Mfkey32" - -#define MFKEY32_LOGS_PATH EXT_PATH("nfc/.mfkey32.log") - -typedef enum { - Mfkey32StateIdle, - Mfkey32StateAuthReceived, - Mfkey32StateAuthNtSent, - Mfkey32StateAuthArNrReceived, -} Mfkey32State; - -typedef struct { - uint32_t cuid; - uint8_t sector; - MfClassicKey key; - uint32_t nt0; - uint32_t nr0; - uint32_t ar0; - uint32_t nt1; - uint32_t nr1; - uint32_t ar1; -} Mfkey32Params; - -ARRAY_DEF(Mfkey32Params, Mfkey32Params, M_POD_OPLIST); - -typedef struct { - uint8_t sector; - MfClassicKey key; - uint32_t nt; - uint32_t nr; - uint32_t ar; -} Mfkey32Nonce; - -struct Mfkey32 { - Mfkey32State state; - Stream* file_stream; - Mfkey32Params_t params_arr; - Mfkey32Nonce nonce; - uint32_t cuid; - Mfkey32ParseDataCallback callback; - void* context; -}; - -Mfkey32* mfkey32_alloc(uint32_t cuid) { - Mfkey32* instance = malloc(sizeof(Mfkey32)); - instance->cuid = cuid; - instance->state = Mfkey32StateIdle; - Storage* storage = furi_record_open(RECORD_STORAGE); - instance->file_stream = buffered_file_stream_alloc(storage); - if(!buffered_file_stream_open( - instance->file_stream, MFKEY32_LOGS_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) { - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - free(instance); - instance = NULL; - } else { - Mfkey32Params_init(instance->params_arr); - } - - furi_record_close(RECORD_STORAGE); - - return instance; -} - -void mfkey32_free(Mfkey32* instance) { - furi_assert(instance != NULL); - - Mfkey32Params_clear(instance->params_arr); - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - free(instance); -} - -void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { - FuriString* str = furi_string_alloc_printf( - "Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n", - params->sector, - params->key == MfClassicKeyA ? 'A' : 'B', - params->cuid, - params->nt0, - params->nr0, - params->ar0, - params->nt1, - params->nr1, - params->ar1); - bool write_success = stream_write_string(instance->file_stream, str); - furi_string_free(str); - return write_success; -} - -static void mfkey32_add_params(Mfkey32* instance) { - Mfkey32Nonce* nonce = &instance->nonce; - bool nonce_added = false; - // Search if we partially collected params - if(Mfkey32Params_size(instance->params_arr)) { - Mfkey32Params_it_t it; - for(Mfkey32Params_it(it, instance->params_arr); !Mfkey32Params_end_p(it); - Mfkey32Params_next(it)) { - Mfkey32Params* params = Mfkey32Params_ref(it); - if((params->sector == nonce->sector) && (params->key == nonce->key)) { - params->nt1 = nonce->nt; - params->nr1 = nonce->nr; - params->ar1 = nonce->ar; - nonce_added = true; - FURI_LOG_I( - TAG, - "Params for sector %d key %c collected", - params->sector, - params->key == MfClassicKeyA ? 'A' : 'B'); - // Write on sd card - if(mfkey32_write_params(instance, params)) { - Mfkey32Params_remove(instance->params_arr, it); - if(instance->callback) { - instance->callback(Mfkey32EventParamCollected, instance->context); - } - } - } - } - } - if(!nonce_added) { - Mfkey32Params params = { - .sector = nonce->sector, - .key = nonce->key, - .cuid = instance->cuid, - .nt0 = nonce->nt, - .nr0 = nonce->nr, - .ar0 = nonce->ar, - }; - Mfkey32Params_push_back(instance->params_arr, params); - } -} - -void mfkey32_process_data( - Mfkey32* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - furi_assert(instance); - furi_assert(data); - - Mfkey32Nonce* nonce = &instance->nonce; - uint16_t data_len = len; - if((data_len > 3) && !crc_dropped) { - data_len -= 2; - } - - bool data_processed = false; - if(instance->state == Mfkey32StateIdle) { - if(reader_to_tag) { - if((data[0] == 0x60) || (data[0] == 0x61)) { - nonce->key = data[0] == 0x60 ? MfClassicKeyA : MfClassicKeyB; - nonce->sector = mf_classic_get_sector_by_block(data[1]); - instance->state = Mfkey32StateAuthReceived; - data_processed = true; - } - } - } else if(instance->state == Mfkey32StateAuthReceived) { - if(!reader_to_tag) { - if(len == 4) { - nonce->nt = nfc_util_bytes2num(data, 4); - instance->state = Mfkey32StateAuthNtSent; - data_processed = true; - } - } - } else if(instance->state == Mfkey32StateAuthNtSent) { - if(reader_to_tag) { - if(len == 8) { - nonce->nr = nfc_util_bytes2num(data, 4); - nonce->ar = nfc_util_bytes2num(&data[4], 4); - mfkey32_add_params(instance); - instance->state = Mfkey32StateIdle; - } - } - } - if(!data_processed) { - instance->state = Mfkey32StateIdle; - } -} - -uint16_t mfkey32_get_auth_sectors(FuriString* data_str) { - furi_assert(data_str); - - uint16_t nonces_num = 0; - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* file_stream = buffered_file_stream_alloc(storage); - FuriString* temp_str; - temp_str = furi_string_alloc(); - - do { - if(!buffered_file_stream_open( - file_stream, MFKEY32_LOGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) - break; - while(true) { - if(!stream_read_line(file_stream, temp_str)) break; - size_t uid_pos = furi_string_search(temp_str, "cuid"); - furi_string_left(temp_str, uid_pos); - furi_string_push_back(temp_str, '\n'); - furi_string_cat(data_str, temp_str); - nonces_num++; - } - } while(false); - - buffered_file_stream_close(file_stream); - stream_free(file_stream); - furi_string_free(temp_str); - - return nonces_num; -} diff --git a/lib/nfc/helpers/mfkey32.h b/lib/nfc/helpers/mfkey32.h deleted file mode 100644 index e290432240..0000000000 --- a/lib/nfc/helpers/mfkey32.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct Mfkey32 Mfkey32; - -typedef enum { - Mfkey32EventParamCollected, -} Mfkey32Event; - -typedef void (*Mfkey32ParseDataCallback)(Mfkey32Event event, void* context); - -Mfkey32* mfkey32_alloc(uint32_t cuid); - -void mfkey32_free(Mfkey32* instance); - -void mfkey32_process_data( - Mfkey32* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped); - -void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context); - -uint16_t mfkey32_get_auth_sectors(FuriString* string); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c new file mode 100644 index 0000000000..2783051622 --- /dev/null +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -0,0 +1,566 @@ +#include "nfc_data_generator.h" + +#include +#include +#include +#include +#include + +#define NXP_MANUFACTURER_ID (0x04) + +typedef void (*NfcDataGeneratorHandler)(NfcDevice* nfc_device); + +typedef struct { + const char* name; + NfcDataGeneratorHandler handler; +} NfcDataGenerator; + +static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03}; +static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03}; +static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03}; +static const uint8_t default_data_ntag203[] = + {0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE}; +static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00}; + +static void nfc_generate_mf_ul_uid(uint8_t* uid) { + uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&uid[1], 6); + // I'm not sure how this is generated, but the upper nybble always seems to be 8 + uid[6] &= 0x0F; + uid[6] |= 0x80; +} + +static void nfc_generate_mf_ul_common(MfUltralightData* mfu_data) { + mfu_data->iso14443_3a_data->uid_len = 7; + nfc_generate_mf_ul_uid(mfu_data->iso14443_3a_data->uid); + mfu_data->iso14443_3a_data->atqa[0] = 0x44; + mfu_data->iso14443_3a_data->atqa[1] = 0x00; + mfu_data->iso14443_3a_data->sak = 0x00; +} + +static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { + *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; + *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; +} + +static void nfc_generate_mf_ul_copy_uid_with_bcc(MfUltralightData* mfu_data) { + memcpy(mfu_data->page[0].data, mfu_data->iso14443_3a_data->uid, 3); + memcpy(mfu_data->page[1].data, &mfu_data->iso14443_3a_data->uid[3], 4); + + nfc_generate_calc_bcc( + mfu_data->iso14443_3a_data->uid, &mfu_data->page[0].data[3], &mfu_data->page[2].data[0]); +} + +static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + nfc_generate_mf_ul_common(mfu_data); + + mfu_data->type = MfUltralightTypeUnknown; + mfu_data->pages_total = 16; + mfu_data->pages_read = 16; + nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + memset(&mfu_data->page[4], 0xff, sizeof(MfUltralightPage)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_with_config_common(MfUltralightData* mfu_data, uint8_t num_pages) { + nfc_generate_mf_ul_common(mfu_data); + + mfu_data->pages_total = num_pages; + mfu_data->pages_read = num_pages; + nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + uint16_t config_index = (num_pages - 4); + mfu_data->page[config_index].data[0] = 0x04; // STRG_MOD_EN + mfu_data->page[config_index].data[3] = 0xff; // AUTH0 + mfu_data->page[config_index + 1].data[1] = 0x05; // VCTID + memset(&mfu_data->page[config_index + 2], 0xff, sizeof(MfUltralightPage)); // Default PWD + if(num_pages > 20) { + mfu_data->page[config_index - 1].data[3] = MF_ULTRALIGHT_TEARING_FLAG_DEFAULT; + } +} + +static void nfc_generate_mf_ul_ev1_common(MfUltralightData* mfu_data, uint8_t num_pages) { + nfc_generate_mf_ul_with_config_common(mfu_data, num_pages); + memcpy(&mfu_data->version, version_bytes_mf0ulx1, sizeof(MfUltralightVersion)); + for(size_t i = 0; i < 3; ++i) { + mfu_data->tearing_flag[i].data = MF_ULTRALIGHT_TEARING_FLAG_DEFAULT; + } +} + +static void nfc_generate_mf_ul_11(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 20); + mfu_data->type = MfUltralightTypeUL11; + mfu_data->version.prod_subtype = 0x01; + mfu_data->version.storage_size = 0x0B; + mfu_data->page[16].data[0] = 0x00; // Low capacitance version does not have STRG_MOD_EN + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_h11(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 20); + mfu_data->type = MfUltralightTypeUL11; + mfu_data->version.prod_subtype = 0x02; + mfu_data->version.storage_size = 0x0B; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_21(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 41); + mfu_data->type = MfUltralightTypeUL21; + mfu_data->version.prod_subtype = 0x01; + mfu_data->version.storage_size = 0x0E; + mfu_data->page[37].data[0] = 0x00; // Low capacitance version does not have STRG_MOD_EN + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_h21(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 41); + mfu_data->type = MfUltralightTypeUL21; + mfu_data->version.prod_subtype = 0x02; + mfu_data->version.storage_size = 0x0E; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag203(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_common(mfu_data); + mfu_data->type = MfUltralightTypeNTAG203; + mfu_data->pages_total = 42; + mfu_data->pages_read = 42; + nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + mfu_data->page[2].data[1] = 0x48; // Internal byte + memcpy(&mfu_data->page[3], default_data_ntag203, sizeof(MfUltralightPage)); //-V1086 + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag21x_common(MfUltralightData* mfu_data, uint8_t num_pages) { + nfc_generate_mf_ul_with_config_common(mfu_data, num_pages); + memcpy(&mfu_data->version, version_bytes_ntag21x, sizeof(MfUltralightVersion)); + mfu_data->page[2].data[1] = 0x48; // Internal byte + // Capability container + mfu_data->page[3].data[0] = 0xE1; + mfu_data->page[3].data[1] = 0x10; +} + +static void nfc_generate_ntag213(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag21x_common(mfu_data, 45); + mfu_data->type = MfUltralightTypeNTAG213; + mfu_data->version.storage_size = 0x0F; + mfu_data->page[3].data[2] = 0x12; + // Default contents + memcpy(&mfu_data->page[4], default_data_ntag213, sizeof(default_data_ntag213)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag215(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag21x_common(mfu_data, 135); + mfu_data->type = MfUltralightTypeNTAG215; + mfu_data->version.storage_size = 0x11; + mfu_data->page[3].data[2] = 0x3E; + // Default contents + memcpy(&mfu_data->page[4], default_data_ntag215_216, sizeof(default_data_ntag215_216)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag216(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag21x_common(mfu_data, 231); + mfu_data->type = MfUltralightTypeNTAG216; + mfu_data->version.storage_size = 0x13; + mfu_data->page[3].data[2] = 0x6D; + // Default contents + memcpy(&mfu_data->page[4], default_data_ntag215_216, sizeof(default_data_ntag215_216)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_common( + MfUltralightData* mfu_data, + MfUltralightType type, + uint16_t num_pages) { + nfc_generate_mf_ul_common(mfu_data); + + mfu_data->type = type; + memcpy(&mfu_data->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c)); + mfu_data->pages_total = num_pages; + mfu_data->pages_read = num_pages; + memcpy( + mfu_data->page[0].data, + mfu_data->iso14443_3a_data->uid, + mfu_data->iso14443_3a_data->uid_len); + mfu_data->page[1].data[3] = mfu_data->iso14443_3a_data->sak; + mfu_data->page[2].data[0] = mfu_data->iso14443_3a_data->atqa[0]; + mfu_data->page[2].data[1] = mfu_data->iso14443_3a_data->atqa[1]; + + uint16_t config_register_page = 0; + uint16_t session_register_page = 0; + + // Sync with mifare_ultralight.c + switch(type) { + case MfUltralightTypeNTAGI2C1K: + config_register_page = 227; + session_register_page = 229; + break; + case MfUltralightTypeNTAGI2C2K: + config_register_page = 481; + session_register_page = 483; + break; + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + config_register_page = 232; + session_register_page = 234; + break; + default: + furi_crash("Unknown MFUL"); + break; + } + + memcpy( + &mfu_data->page[config_register_page], + default_config_ntag_i2c, + sizeof(default_config_ntag_i2c)); + memcpy( + &mfu_data->page[session_register_page], + default_config_ntag_i2c, + sizeof(default_config_ntag_i2c)); +} + +static void nfc_generate_ntag_i2c_1k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_common(mfu_data, MfUltralightTypeNTAGI2C1K, 231); + mfu_data->version.prod_ver_minor = 0x01; + mfu_data->version.storage_size = 0x13; + memcpy(&mfu_data->page[3], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); + mfu_data->page[3].data[2] = 0x6D; // Size of tag in CC + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_2k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_common(mfu_data, MfUltralightTypeNTAGI2C2K, 485); + mfu_data->version.prod_ver_minor = 0x01; + mfu_data->version.storage_size = 0x15; + memcpy(&mfu_data->page[3], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); + mfu_data->page[3].data[2] = 0xEA; // Size of tag in CC + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_plus_common( + MfUltralightData* mfu_data, + MfUltralightType type, + uint16_t num_pages) { + nfc_generate_ntag_i2c_common(mfu_data, type, num_pages); + + uint16_t config_index = 227; + mfu_data->page[config_index].data[3] = 0xff; // AUTH0 + + memset(&mfu_data->page[config_index + 2], 0xFF, sizeof(MfUltralightPage)); // Default PWD +} + +static void nfc_generate_ntag_i2c_plus_1k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_plus_common(mfu_data, MfUltralightTypeNTAGI2CPlus1K, 236); + mfu_data->version.prod_ver_minor = 0x02; + mfu_data->version.storage_size = 0x13; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_plus_2k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_plus_common(mfu_data, MfUltralightTypeNTAGI2CPlus2K, 492); + mfu_data->version.prod_ver_minor = 0x02; + mfu_data->version.storage_size = 0x15; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { + uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&uid[1], length - 1); +} + +static void + nfc_generate_mf_classic_common(MfClassicData* data, uint8_t uid_len, MfClassicType type) { + data->iso14443_3a_data->uid_len = uid_len; + data->iso14443_3a_data->atqa[0] = 0x44; + data->iso14443_3a_data->atqa[1] = 0x00; + data->iso14443_3a_data->sak = 0x08; + data->type = type; +} + +static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t block) { + // All keys are set to FFFF FFFF FFFFh at chip delivery and the bytes 6, 7 and 8 are set to FF0780h. + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data->block[block].data; + sec_tr->access_bits.data[0] = 0xFF; + sec_tr->access_bits.data[1] = 0x07; + sec_tr->access_bits.data[2] = 0x80; + sec_tr->access_bits.data[3] = 0x69; // Nice + + mf_classic_set_block_read(data, block, &data->block[block]); + mf_classic_set_key_found( + data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeA, 0xFFFFFFFFFFFF); + mf_classic_set_key_found( + data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeB, 0xFFFFFFFFFFFF); +} + +static void nfc_generate_mf_classic_block_0( + uint8_t* block, + uint8_t uid_len, + uint8_t sak, + uint8_t atqa0, + uint8_t atqa1) { + // Block length is always 16 bytes, and the UID can be either 4 or 7 bytes + furi_assert(uid_len == 4 || uid_len == 7); + furi_assert(block); + + if(uid_len == 4) { + // Calculate BCC + block[uid_len] = 0; + + for(int i = 0; i < uid_len; i++) { + block[uid_len] ^= block[i]; + } + } else { + uid_len -= 1; + } + + block[uid_len + 1] = sak; + block[uid_len + 2] = atqa0; + block[uid_len + 3] = atqa1; + + for(int i = uid_len + 4; i < 16; i++) { + block[i] = 0xFF; + } +} + +static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfClassicType type) { + MfClassicData* mfc_data = mf_classic_alloc(); + + nfc_generate_mf_classic_uid(mfc_data->block[0].data, uid_len); + nfc_generate_mf_classic_common(mfc_data, uid_len, type); + + // Set the UID + mfc_data->iso14443_3a_data->uid[0] = NXP_MANUFACTURER_ID; + for(int i = 1; i < uid_len; i++) { + mfc_data->iso14443_3a_data->uid[i] = mfc_data->block[0].data[i]; + } + + mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); + + uint16_t block_num = mf_classic_get_total_block_num(type); + if(type == MfClassicType4k) { + // Set every block to 0xFF + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0xFF, 16); + } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + } + // Set SAK to 18 + mfc_data->iso14443_3a_data->sak = 0x18; + } else if(type == MfClassicType1k) { + // Set every block to 0xFF + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0xFF, 16); + } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + } + // Set SAK to 08 + mfc_data->iso14443_3a_data->sak = 0x08; + } else if(type == MfClassicTypeMini) { + // Set every block to 0xFF + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0xFF, 16); + } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + } + // Set SAK to 09 + mfc_data->iso14443_3a_data->sak = 0x09; + } + + nfc_generate_mf_classic_block_0( + mfc_data->block[0].data, + uid_len, + mfc_data->iso14443_3a_data->sak, + mfc_data->iso14443_3a_data->atqa[0], + mfc_data->iso14443_3a_data->atqa[1]); + + mfc_data->type = type; + + nfc_device_set_data(nfc_device, NfcProtocolMfClassic, mfc_data); + mf_classic_free(mfc_data); +} + +static void nfc_generate_mf_classic_mini(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 4, MfClassicTypeMini); +} + +static void nfc_generate_mf_classic_1k_4b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 4, MfClassicType1k); +} + +static void nfc_generate_mf_classic_1k_7b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 7, MfClassicType1k); +} + +static void nfc_generate_mf_classic_4k_4b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 4, MfClassicType4k); +} + +static void nfc_generate_mf_classic_4k_7b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 7, MfClassicType4k); +} + +static const NfcDataGenerator nfc_data_generator[NfcDataGeneratorTypeNum] = { + [NfcDataGeneratorTypeMfUltralight] = + { + .name = "Mifare Ultralight", + .handler = nfc_generate_mf_ul_orig, + }, + [NfcDataGeneratorTypeMfUltralightEV1_11] = + { + .name = "Mifare Ultralight EV1 11", + .handler = nfc_generate_mf_ul_11, + }, + [NfcDataGeneratorTypeMfUltralightEV1_H11] = + { + .name = "Mifare Ultralight EV1 H11", + .handler = nfc_generate_mf_ul_h11, + }, + [NfcDataGeneratorTypeMfUltralightEV1_21] = + { + .name = "Mifare Ultralight EV1 21", + .handler = nfc_generate_mf_ul_21, + }, + [NfcDataGeneratorTypeMfUltralightEV1_H21] = + { + .name = "Mifare Ultralight EV1 H21", + .handler = nfc_generate_mf_ul_h21, + }, + [NfcDataGeneratorTypeNTAG203] = + { + .name = "NTAG203", + .handler = nfc_generate_ntag203, + }, + [NfcDataGeneratorTypeNTAG213] = + { + .name = "NTAG213", + .handler = nfc_generate_ntag213, + }, + [NfcDataGeneratorTypeNTAG215] = + { + .name = "NTAG215", + .handler = nfc_generate_ntag215, + }, + [NfcDataGeneratorTypeNTAG216] = + { + .name = "NTAG216", + .handler = nfc_generate_ntag216, + }, + [NfcDataGeneratorTypeNTAGI2C1k] = + { + .name = "NTAG I2C 1k", + .handler = nfc_generate_ntag_i2c_1k, + }, + [NfcDataGeneratorTypeNTAGI2C2k] = + { + .name = "NTAG I2C 2k", + .handler = nfc_generate_ntag_i2c_2k, + }, + [NfcDataGeneratorTypeNTAGI2CPlus1k] = + { + .name = "NTAG I2C Plus 1k", + .handler = nfc_generate_ntag_i2c_plus_1k, + }, + [NfcDataGeneratorTypeNTAGI2CPlus2k] = + { + .name = "NTAG I2C Plus 2k", + .handler = nfc_generate_ntag_i2c_plus_2k, + }, + [NfcDataGeneratorTypeMfClassicMini] = + { + .name = "Mifare Mini", + .handler = nfc_generate_mf_classic_mini, + }, + [NfcDataGeneratorTypeMfClassic1k_4b] = + { + .name = "Mifare Classic 1k 4byte UID", + .handler = nfc_generate_mf_classic_1k_4b_uid, + }, + [NfcDataGeneratorTypeMfClassic1k_7b] = + { + .name = "Mifare Classic 1k 7byte UID", + .handler = nfc_generate_mf_classic_1k_7b_uid, + }, + [NfcDataGeneratorTypeMfClassic4k_4b] = + { + .name = "Mifare Classic 4k 4byte UID", + .handler = nfc_generate_mf_classic_4k_4b_uid, + }, + [NfcDataGeneratorTypeMfClassic4k_7b] = + { + .name = "Mifare Classic 4k 7byte UID", + .handler = nfc_generate_mf_classic_4k_7b_uid, + }, +}; + +const char* nfc_data_generator_get_name(NfcDataGeneratorType type) { + return nfc_data_generator[type].name; +} + +void nfc_data_generator_fill_data(NfcDataGeneratorType type, NfcDevice* nfc_device) { + nfc_data_generator[type].handler(nfc_device); +} diff --git a/lib/nfc/helpers/nfc_data_generator.h b/lib/nfc/helpers/nfc_data_generator.h new file mode 100644 index 0000000000..01f5dac16e --- /dev/null +++ b/lib/nfc/helpers/nfc_data_generator.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + NfcDataGeneratorTypeMfUltralight, + NfcDataGeneratorTypeMfUltralightEV1_11, + NfcDataGeneratorTypeMfUltralightEV1_H11, + NfcDataGeneratorTypeMfUltralightEV1_21, + NfcDataGeneratorTypeMfUltralightEV1_H21, + NfcDataGeneratorTypeNTAG203, + NfcDataGeneratorTypeNTAG213, + NfcDataGeneratorTypeNTAG215, + NfcDataGeneratorTypeNTAG216, + NfcDataGeneratorTypeNTAGI2C1k, + NfcDataGeneratorTypeNTAGI2C2k, + NfcDataGeneratorTypeNTAGI2CPlus1k, + NfcDataGeneratorTypeNTAGI2CPlus2k, + + NfcDataGeneratorTypeMfClassicMini, + NfcDataGeneratorTypeMfClassic1k_4b, + NfcDataGeneratorTypeMfClassic1k_7b, + NfcDataGeneratorTypeMfClassic4k_4b, + NfcDataGeneratorTypeMfClassic4k_7b, + + NfcDataGeneratorTypeNum, + +} NfcDataGeneratorType; + +const char* nfc_data_generator_get_name(NfcDataGeneratorType type); + +void nfc_data_generator_fill_data(NfcDataGeneratorType type, NfcDevice* nfc_device); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/nfc_debug_log.c b/lib/nfc/helpers/nfc_debug_log.c deleted file mode 100644 index 0bfbc2c62f..0000000000 --- a/lib/nfc/helpers/nfc_debug_log.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "nfc_debug_log.h" - -#include -#include - -#define TAG "NfcDebugLog" - -#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.txt") - -struct NfcDebugLog { - Stream* file_stream; - FuriString* data_str; -}; - -NfcDebugLog* nfc_debug_log_alloc() { - NfcDebugLog* instance = malloc(sizeof(NfcDebugLog)); - - Storage* storage = furi_record_open(RECORD_STORAGE); - instance->file_stream = buffered_file_stream_alloc(storage); - - if(!buffered_file_stream_open( - instance->file_stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - instance->file_stream = NULL; - } - - if(!instance->file_stream) { - free(instance); - instance = NULL; - } else { - instance->data_str = furi_string_alloc(); - } - furi_record_close(RECORD_STORAGE); - - return instance; -} - -void nfc_debug_log_free(NfcDebugLog* instance) { - furi_assert(instance); - furi_assert(instance->file_stream); - furi_assert(instance->data_str); - - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - furi_string_free(instance->data_str); - - free(instance); -} - -void nfc_debug_log_process_data( - NfcDebugLog* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - furi_assert(instance); - furi_assert(instance->file_stream); - furi_assert(instance->data_str); - furi_assert(data); - UNUSED(crc_dropped); - - furi_string_printf(instance->data_str, "%lu %c:", furi_get_tick(), reader_to_tag ? 'R' : 'T'); - uint16_t data_len = len; - for(size_t i = 0; i < data_len; i++) { - furi_string_cat_printf(instance->data_str, " %02x", data[i]); - } - furi_string_push_back(instance->data_str, '\n'); - - stream_write_string(instance->file_stream, instance->data_str); -} diff --git a/lib/nfc/helpers/nfc_debug_log.h b/lib/nfc/helpers/nfc_debug_log.h deleted file mode 100644 index 261b8008b8..0000000000 --- a/lib/nfc/helpers/nfc_debug_log.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include - -typedef struct NfcDebugLog NfcDebugLog; - -NfcDebugLog* nfc_debug_log_alloc(); - -void nfc_debug_log_free(NfcDebugLog* instance); - -void nfc_debug_log_process_data( - NfcDebugLog* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped); diff --git a/lib/nfc/helpers/nfc_debug_pcap.c b/lib/nfc/helpers/nfc_debug_pcap.c deleted file mode 100644 index 6c7654ad57..0000000000 --- a/lib/nfc/helpers/nfc_debug_pcap.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "nfc_debug_pcap.h" - -#include -#include -#include -#include - -#define TAG "NfcDebugPcap" - -#define PCAP_MAGIC 0xa1b2c3d4 -#define PCAP_MAJOR 2 -#define PCAP_MINOR 4 -#define DLT_ISO_14443 264 - -#define DATA_PICC_TO_PCD 0xFF -#define DATA_PCD_TO_PICC 0xFE -#define DATA_PICC_TO_PCD_CRC_DROPPED 0xFB -#define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA - -#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.pcap") - -struct NfcDebugPcap { - Stream* file_stream; -}; - -static Stream* nfc_debug_pcap_open(Storage* storage) { - Stream* stream = NULL; - stream = buffered_file_stream_alloc(storage); - if(!buffered_file_stream_open(stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { - buffered_file_stream_close(stream); - stream_free(stream); - stream = NULL; - } else { - if(!stream_tell(stream)) { - struct { - uint32_t magic; - uint16_t major, minor; - uint32_t reserved[2]; - uint32_t snaplen; - uint32_t link_type; - } __attribute__((__packed__)) pcap_hdr = { - .magic = PCAP_MAGIC, - .major = PCAP_MAJOR, - .minor = PCAP_MINOR, - .snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE, - .link_type = DLT_ISO_14443, - }; - if(stream_write(stream, (uint8_t*)&pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) { - FURI_LOG_E(TAG, "Failed to write pcap header"); - buffered_file_stream_close(stream); - stream_free(stream); - stream = NULL; - } - } - } - return stream; -} - -NfcDebugPcap* nfc_debug_pcap_alloc() { - NfcDebugPcap* instance = malloc(sizeof(NfcDebugPcap)); - - Storage* storage = furi_record_open(RECORD_STORAGE); - instance->file_stream = nfc_debug_pcap_open(storage); - if(!instance->file_stream) { - free(instance); - instance = NULL; - } - furi_record_close(RECORD_STORAGE); - - return instance; -} - -void nfc_debug_pcap_free(NfcDebugPcap* instance) { - furi_assert(instance); - furi_assert(instance->file_stream); - - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - - free(instance); -} - -void nfc_debug_pcap_process_data( - NfcDebugPcap* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - furi_assert(instance); - furi_assert(data); - FuriHalRtcDateTime datetime; - furi_hal_rtc_get_datetime(&datetime); - - uint8_t event = 0; - if(reader_to_tag) { - if(crc_dropped) { - event = DATA_PCD_TO_PICC_CRC_DROPPED; - } else { - event = DATA_PCD_TO_PICC; - } - } else { - if(crc_dropped) { - event = DATA_PICC_TO_PCD_CRC_DROPPED; - } else { - event = DATA_PICC_TO_PCD; - } - } - - struct { - // https://wiki.wireshark.org/Development/LibpcapFileFormat#record-packet-header - uint32_t ts_sec; - uint32_t ts_usec; - uint32_t incl_len; - uint32_t orig_len; - // https://www.kaiser.cx/posts/pcap-iso14443/#_packet_data - uint8_t version; - uint8_t event; - uint16_t len; - } __attribute__((__packed__)) pkt_hdr = { - .ts_sec = furi_hal_rtc_datetime_to_timestamp(&datetime), - .ts_usec = 0, - .incl_len = len + 4, - .orig_len = len + 4, - .version = 0, - .event = event, - .len = len << 8 | len >> 8, - }; - stream_write(instance->file_stream, (uint8_t*)&pkt_hdr, sizeof(pkt_hdr)); - stream_write(instance->file_stream, data, len); -} diff --git a/lib/nfc/helpers/nfc_debug_pcap.h b/lib/nfc/helpers/nfc_debug_pcap.h deleted file mode 100644 index eeddc56112..0000000000 --- a/lib/nfc/helpers/nfc_debug_pcap.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include - -typedef struct NfcDebugPcap NfcDebugPcap; - -NfcDebugPcap* nfc_debug_pcap_alloc(); - -void nfc_debug_pcap_free(NfcDebugPcap* instance); - -void nfc_debug_pcap_process_data( - NfcDebugPcap* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped); diff --git a/lib/nfc/helpers/nfc_dict.c b/lib/nfc/helpers/nfc_dict.c new file mode 100644 index 0000000000..d4572a3d62 --- /dev/null +++ b/lib/nfc/helpers/nfc_dict.c @@ -0,0 +1,270 @@ +#include "nfc_dict.h" + +#include +#include +#include +#include +#include + +#include + +#define TAG "NfcDict" + +struct NfcDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + uint32_t total_keys; +}; + +typedef struct { + const char* path; + FS_OpenMode open_mode; +} NfcDictFile; + +bool nfc_dict_check_presence(const char* path) { + furi_assert(path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool dict_present = storage_common_stat(storage, path, NULL) == FSE_OK; + + furi_record_close(RECORD_STORAGE); + + return dict_present; +} + +NfcDict* nfc_dict_alloc(const char* path, NfcDictMode mode, size_t key_size) { + furi_assert(path); + + NfcDict* instance = malloc(sizeof(NfcDict)); + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + FS_OpenMode open_mode = FSOM_OPEN_EXISTING; + if(mode == NfcDictModeOpenAlways) { + open_mode = FSOM_OPEN_ALWAYS; + } + instance->key_size = key_size; + // Byte = 2 symbols + 1 end of line + instance->key_size_symbols = key_size * 2 + 1; + + bool dict_loaded = false; + do { + if(!buffered_file_stream_open(instance->stream, path, FSAM_READ_WRITE, open_mode)) { + buffered_file_stream_close(instance->stream); + break; + } + + // Check for new line ending + if(!stream_eof(instance->stream)) { + if(!stream_seek(instance->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(instance->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(instance->stream, '\n') != 1) break; + } + if(!stream_rewind(instance->stream)) break; + } + + // Read total amount of keys + FuriString* next_line; + next_line = furi_string_alloc(); + while(true) { + if(!stream_read_line(instance->stream, next_line)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != instance->key_size_symbols) continue; + instance->total_keys++; + } + furi_string_free(next_line); + stream_rewind(instance->stream); + + dict_loaded = true; + FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", instance->total_keys); + } while(false); + + if(!dict_loaded) { + buffered_file_stream_close(instance->stream); + free(instance); + instance = NULL; + } + + return instance; +} + +void nfc_dict_free(NfcDict* instance) { + furi_assert(instance); + furi_assert(instance->stream); + + buffered_file_stream_close(instance->stream); + stream_free(instance->stream); + free(instance); +} + +static void nfc_dict_int_to_str(NfcDict* instance, const uint8_t* key_int, FuriString* key_str) { + furi_string_reset(key_str); + for(size_t i = 0; i < instance->key_size; i++) { + furi_string_cat_printf(key_str, "%02X", key_int[i]); + } +} + +static void nfc_dict_str_to_int(NfcDict* instance, FuriString* key_str, uint64_t* key_int) { + uint8_t key_byte_tmp; + + *key_int = 0ULL; + for(uint8_t i = 0; i < instance->key_size * 2; i += 2) { + args_char_to_hex( + furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); + *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); + } +} + +uint32_t nfc_dict_get_total_keys(NfcDict* instance) { + furi_assert(instance); + + return instance->total_keys; +} + +bool nfc_dict_rewind(NfcDict* instance) { + furi_assert(instance); + furi_assert(instance->stream); + + return stream_rewind(instance->stream); +} + +static bool nfc_dict_get_next_key_str(NfcDict* instance, FuriString* key) { + furi_assert(instance); + furi_assert(instance->stream); + + bool key_read = false; + furi_string_reset(key); + while(!key_read) { + if(!stream_read_line(instance->stream, key)) break; + if(furi_string_get_char(key, 0) == '#') continue; + if(furi_string_size(key) != instance->key_size_symbols) continue; + furi_string_left(key, instance->key_size_symbols - 1); + key_read = true; + } + + return key_read; +} + +bool nfc_dict_get_next_key(NfcDict* instance, uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(instance->stream); + furi_assert(instance->key_size == key_size); + + FuriString* temp_key = furi_string_alloc(); + uint64_t key_int = 0; + bool key_read = nfc_dict_get_next_key_str(instance, temp_key); + if(key_read) { + nfc_dict_str_to_int(instance, temp_key, &key_int); + nfc_util_num2bytes(key_int, key_size, key); + } + furi_string_free(temp_key); + return key_read; +} + +static bool nfc_dict_is_key_present_str(NfcDict* instance, FuriString* key) { + furi_assert(instance); + furi_assert(instance->stream); + + FuriString* next_line; + next_line = furi_string_alloc(); + + bool key_found = false; + stream_rewind(instance->stream); + while(!key_found) { //-V654 + if(!stream_read_line(instance->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != instance->key_size_symbols) continue; + furi_string_left(next_line, instance->key_size_symbols - 1); + if(!furi_string_equal(key, next_line)) continue; + key_found = true; + } + + furi_string_free(next_line); + return key_found; +} + +bool nfc_dict_is_key_present(NfcDict* instance, const uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(key); + furi_assert(instance->stream); + furi_assert(instance->key_size == key_size); + + FuriString* temp_key = furi_string_alloc(); + nfc_dict_int_to_str(instance, key, temp_key); + bool key_found = nfc_dict_is_key_present_str(instance, temp_key); + furi_string_free(temp_key); + + return key_found; +} + +static bool nfc_dict_add_key_str(NfcDict* instance, FuriString* key) { + furi_assert(instance); + furi_assert(instance->stream); + + furi_string_cat_printf(key, "\n"); + + bool key_added = false; + do { + if(!stream_seek(instance->stream, 0, StreamOffsetFromEnd)) break; + if(!stream_insert_string(instance->stream, key)) break; + instance->total_keys++; + key_added = true; + } while(false); + + furi_string_left(key, instance->key_size_symbols - 1); + return key_added; +} + +bool nfc_dict_add_key(NfcDict* instance, const uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(key); + furi_assert(instance->stream); + furi_assert(instance->key_size == key_size); + + FuriString* temp_key = furi_string_alloc(); + nfc_dict_int_to_str(instance, key, temp_key); + bool key_added = nfc_dict_add_key_str(instance, temp_key); + furi_string_free(temp_key); + + return key_added; +} + +bool nfc_dict_delete_key(NfcDict* instance, const uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(instance->stream); + furi_assert(key); + furi_assert(instance->key_size == key_size); + + bool key_removed = false; + uint8_t* temp_key = malloc(key_size); + + nfc_dict_rewind(instance); + while(!key_removed) { + if(!nfc_dict_get_next_key(instance, temp_key, key_size)) break; + if(memcmp(temp_key, key, key_size) == 0) { + int32_t offset = (-1) * (instance->key_size_symbols); + stream_seek(instance->stream, offset, StreamOffsetFromCurrent); + if(!stream_delete(instance->stream, instance->key_size_symbols)) break; + instance->total_keys--; + key_removed = true; + } + } + nfc_dict_rewind(instance); + free(temp_key); + + return key_removed; +} diff --git a/lib/nfc/helpers/nfc_dict.h b/lib/nfc/helpers/nfc_dict.h new file mode 100644 index 0000000000..80f3ff6808 --- /dev/null +++ b/lib/nfc/helpers/nfc_dict.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + NfcDictModeOpenExisting, + NfcDictModeOpenAlways, +} NfcDictMode; + +typedef struct NfcDict NfcDict; + +/** Check dictionary presence + * + * @param path - dictionary path + * + * @return true if dictionary exists, false otherwise +*/ +bool nfc_dict_check_presence(const char* path); + +/** Open or create dictionary + * Depending on mode, dictionary will be opened or created. + * + * @param path - dictionary path + * @param mode - NfcDictMode value + * @param key_size - size of dictionary keys in bytes + * + * @return NfcDict dictionary instance +*/ +NfcDict* nfc_dict_alloc(const char* path, NfcDictMode mode, size_t key_size); + +/** Close dictionary + * + * @param instance - NfcDict dictionary instance +*/ +void nfc_dict_free(NfcDict* instance); + +/** Get total number of keys in dictionary + * + * @param instance - NfcDict dictionary instance + * + * @return total number of keys in dictionary +*/ +uint32_t nfc_dict_get_total_keys(NfcDict* instance); + +/** Rewind dictionary + * + * @param instance - NfcDict dictionary instance + * + * @return true if rewind was successful, false otherwise +*/ +bool nfc_dict_rewind(NfcDict* instance); + +/** Check if key is present in dictionary + * + * @param instance - NfcDict dictionary instance + * @param key - key to check + * @param key_size - size of key in bytes + * + * @return true if key is present, false otherwise +*/ +bool nfc_dict_is_key_present(NfcDict* instance, const uint8_t* key, size_t key_size); + +/** Get next key from dictionary + * This function will return next key from dictionary. If there are no more + * keys, it will return false, and nfc_dict_rewind() should be called. + * + * @param instance - NfcDict dictionary instance + * @param key - buffer to store key + * @param key_size - size of key in bytes + * + * @return true if key was successfully retrieved, false otherwise +*/ +bool nfc_dict_get_next_key(NfcDict* instance, uint8_t* key, size_t key_size); + +/** Add key to dictionary + * + * @param instance - NfcDict dictionary instance + * @param key - key to add + * @param key_size - size of key in bytes + * + * @return true if key was successfully added, false otherwise +*/ +bool nfc_dict_add_key(NfcDict* instance, const uint8_t* key, size_t key_size); + +/** Delete key from dictionary + * + * @param instance - NfcDict dictionary instance + * @param key - key to delete + * @param key_size - size of key in bytes + * + * @return true if key was successfully deleted, false otherwise +*/ +bool nfc_dict_delete_key(NfcDict* instance, const uint8_t* key, size_t key_size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/nfc_generators.c b/lib/nfc/helpers/nfc_generators.c deleted file mode 100644 index 50c89aba85..0000000000 --- a/lib/nfc/helpers/nfc_generators.c +++ /dev/null @@ -1,528 +0,0 @@ -#include -#include "nfc_generators.h" - -#define NXP_MANUFACTURER_ID (0x04) - -static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03}; -static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03}; -static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03}; -static const uint8_t default_data_ntag203[] = - {0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE}; -static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE}; -static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE}; -static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE}; -static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00}; - -static void nfc_generate_common_start(NfcDeviceData* data) { - nfc_device_data_clear(data); -} - -static void nfc_generate_mf_ul_uid(uint8_t* uid) { - uid[0] = NXP_MANUFACTURER_ID; - furi_hal_random_fill_buf(&uid[1], 6); - // I'm not sure how this is generated, but the upper nybble always seems to be 8 - uid[6] &= 0x0F; - uid[6] |= 0x80; -} - -static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { - uid[0] = NXP_MANUFACTURER_ID; - furi_hal_random_fill_buf(&uid[1], length - 1); -} - -static void nfc_generate_mf_classic_block_0( - uint8_t* block, - uint8_t uid_len, - uint8_t sak, - uint8_t atqa0, - uint8_t atqa1) { - // Block length is always 16 bytes, and the UID can be either 4 or 7 bytes - furi_assert(uid_len == 4 || uid_len == 7); - furi_assert(block); - - if(uid_len == 4) { - // Calculate BCC - block[uid_len] = 0; - - for(int i = 0; i < uid_len; i++) { - block[uid_len] ^= block[i]; - } - } else { - uid_len -= 1; - } - - block[uid_len + 1] = sak; - block[uid_len + 2] = atqa0; - block[uid_len + 3] = atqa1; - - for(int i = uid_len + 4; i < 16; i++) { - block[i] = 0xFF; - } -} - -static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t block) { - // All keys are set to FFFF FFFF FFFFh at chip delivery and the bytes 6, 7 and 8 are set to FF0780h. - MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data->block[block].value; - sec_tr->access_bits[0] = 0xFF; - sec_tr->access_bits[1] = 0x07; - sec_tr->access_bits[2] = 0x80; - sec_tr->access_bits[3] = 0x69; // Nice - - memset(sec_tr->key_a, 0xff, sizeof(sec_tr->key_a)); - memset(sec_tr->key_b, 0xff, sizeof(sec_tr->key_b)); - - mf_classic_set_block_read(data, block, &data->block[block]); - mf_classic_set_key_found( - data, mf_classic_get_sector_by_block(block), MfClassicKeyA, 0xFFFFFFFFFFFF); - mf_classic_set_key_found( - data, mf_classic_get_sector_by_block(block), MfClassicKeyB, 0xFFFFFFFFFFFF); -} - -static void nfc_generate_mf_ul_common(NfcDeviceData* data) { - data->nfc_data.type = FuriHalNfcTypeA; - data->nfc_data.interface = FuriHalNfcInterfaceRf; - data->nfc_data.uid_len = 7; - nfc_generate_mf_ul_uid(data->nfc_data.uid); - data->nfc_data.atqa[0] = 0x44; - data->nfc_data.atqa[1] = 0x00; - data->nfc_data.sak = 0x00; - data->protocol = NfcDeviceProtocolMifareUl; -} - -static void - nfc_generate_mf_classic_common(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { - data->nfc_data.type = FuriHalNfcTypeA; - data->nfc_data.interface = FuriHalNfcInterfaceRf; - data->nfc_data.uid_len = uid_len; - data->nfc_data.atqa[0] = 0x44; - data->nfc_data.atqa[1] = 0x00; - data->nfc_data.sak = 0x08; - data->protocol = NfcDeviceProtocolMifareClassic; - data->mf_classic_data.type = type; -} - -static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { - *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; - *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; -} - -static void nfc_generate_mf_ul_copy_uid_with_bcc(NfcDeviceData* data) { - MfUltralightData* mful = &data->mf_ul_data; - memcpy(mful->data, data->nfc_data.uid, 3); - memcpy(&mful->data[4], &data->nfc_data.uid[3], 4); - nfc_generate_calc_bcc(data->nfc_data.uid, &mful->data[3], &mful->data[8]); -} - -static void nfc_generate_mf_ul_orig(NfcDeviceData* data) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUnknown; - mful->data_size = 16 * 4; - mful->data_read = mful->data_size; - nfc_generate_mf_ul_copy_uid_with_bcc(data); - // TODO: what's internal byte on page 2? - memset(&mful->data[4 * 4], 0xFF, 4); -} - -static void nfc_generate_mf_ul_ntag203(NfcDeviceData* data) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG203; - mful->data_size = 42 * 4; - mful->data_read = mful->data_size; - nfc_generate_mf_ul_copy_uid_with_bcc(data); - mful->data[9] = 0x48; // Internal byte - memcpy(&mful->data[3 * 4], default_data_ntag203, sizeof(default_data_ntag203)); -} - -static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t num_pages) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->data_size = num_pages * 4; - mful->data_read = mful->data_size; - nfc_generate_mf_ul_copy_uid_with_bcc(data); - uint16_t config_index = (num_pages - 4) * 4; - mful->data[config_index] = 0x04; // STRG_MOD_EN - mful->data[config_index + 3] = 0xFF; // AUTH0 - mful->data[config_index + 5] = 0x05; // VCTID - memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD - if(num_pages > 20) mful->data[config_index - 1] = MF_UL_TEARING_FLAG_DEFAULT; -} - -static void nfc_generate_mf_ul_ev1_common(NfcDeviceData* data, uint8_t num_pages) { - nfc_generate_mf_ul_with_config_common(data, num_pages); - MfUltralightData* mful = &data->mf_ul_data; - memcpy(&mful->version, version_bytes_mf0ulx1, sizeof(version_bytes_mf0ulx1)); - for(size_t i = 0; i < 3; ++i) { - mful->tearing[i] = MF_UL_TEARING_FLAG_DEFAULT; - } - // TODO: what's internal byte on page 2? -} - -static void nfc_generate_mf_ul_11(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 20); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL11; - mful->version.prod_subtype = 0x01; - mful->version.storage_size = 0x0B; - mful->data[16 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN -} - -static void nfc_generate_mf_ul_h11(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 20); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL11; - mful->version.prod_subtype = 0x02; - mful->version.storage_size = 0x0B; -} - -static void nfc_generate_mf_ul_21(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 41); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL21; - mful->version.prod_subtype = 0x01; - mful->version.storage_size = 0x0E; - mful->data[37 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN -} - -static void nfc_generate_mf_ul_h21(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 41); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL21; - mful->version.prod_subtype = 0x02; - mful->version.storage_size = 0x0E; -} - -static void nfc_generate_ntag21x_common(NfcDeviceData* data, uint8_t num_pages) { - nfc_generate_mf_ul_with_config_common(data, num_pages); - MfUltralightData* mful = &data->mf_ul_data; - memcpy(&mful->version, version_bytes_ntag21x, sizeof(version_bytes_mf0ulx1)); - mful->data[9] = 0x48; // Internal byte - // Capability container - mful->data[12] = 0xE1; - mful->data[13] = 0x10; -} - -static void nfc_generate_ntag213(NfcDeviceData* data) { - nfc_generate_ntag21x_common(data, 45); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG213; - mful->version.storage_size = 0x0F; - mful->data[14] = 0x12; - // Default contents - memcpy(&mful->data[16], default_data_ntag213, sizeof(default_data_ntag213)); -} - -static void nfc_generate_ntag215(NfcDeviceData* data) { - nfc_generate_ntag21x_common(data, 135); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG215; - mful->version.storage_size = 0x11; - mful->data[14] = 0x3E; - // Default contents - memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216)); -} - -static void nfc_generate_ntag216(NfcDeviceData* data) { - nfc_generate_ntag21x_common(data, 231); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG216; - mful->version.storage_size = 0x13; - mful->data[14] = 0x6D; - // Default contents - memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216)); -} - -static void - nfc_generate_ntag_i2c_common(NfcDeviceData* data, MfUltralightType type, uint16_t num_pages) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->type = type; - memcpy(&mful->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c)); - mful->data_size = num_pages * 4; - mful->data_read = mful->data_size; - memcpy(mful->data, data->nfc_data.uid, data->nfc_data.uid_len); - mful->data[7] = data->nfc_data.sak; - mful->data[8] = data->nfc_data.atqa[0]; - mful->data[9] = data->nfc_data.atqa[1]; - - uint16_t config_register_page; - uint16_t session_register_page; - - // Sync with mifare_ultralight.c - switch(type) { - case MfUltralightTypeNTAGI2C1K: - config_register_page = 227; - session_register_page = 229; - break; - case MfUltralightTypeNTAGI2C2K: - config_register_page = 481; - session_register_page = 483; - break; - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - config_register_page = 232; - session_register_page = 234; - break; - default: - furi_crash("Unknown MFUL"); - break; - } - - memcpy( - &mful->data[config_register_page * 4], - default_config_ntag_i2c, - sizeof(default_config_ntag_i2c)); - memcpy( - &mful->data[session_register_page * 4], - default_config_ntag_i2c, - sizeof(default_config_ntag_i2c)); -} - -static void nfc_generate_ntag_i2c_1k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C1K, 231); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x01; - mful->version.storage_size = 0x13; - - memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); - mful->data[14] = 0x6D; // Size of tag in CC -} - -static void nfc_generate_ntag_i2c_2k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C2K, 485); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x01; - mful->version.storage_size = 0x15; - - memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); - mful->data[14] = 0xEA; // Size of tag in CC -} - -static void nfc_generate_ntag_i2c_plus_common( - NfcDeviceData* data, - MfUltralightType type, - uint16_t num_pages) { - nfc_generate_ntag_i2c_common(data, type, num_pages); - - MfUltralightData* mful = &data->mf_ul_data; - uint16_t config_index = 227 * 4; - mful->data[config_index + 3] = 0xFF; // AUTH0 - memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD -} - -static void nfc_generate_ntag_i2c_plus_1k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus1K, 236); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x02; - mful->version.storage_size = 0x13; -} - -static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus2K, 492); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x02; - mful->version.storage_size = 0x15; -} - -void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { - nfc_generate_common_start(data); - nfc_generate_mf_classic_uid(data->mf_classic_data.block[0].value, uid_len); - nfc_generate_mf_classic_common(data, uid_len, type); - - // Set the UID - data->nfc_data.uid[0] = NXP_MANUFACTURER_ID; - for(int i = 1; i < uid_len; i++) { - data->nfc_data.uid[i] = data->mf_classic_data.block[0].value[i]; - } - - MfClassicData* mfc = &data->mf_classic_data; - mf_classic_set_block_read(mfc, 0, &mfc->block[0]); - - if(type == MfClassicType4k) { - // Set every block to 0xFF - for(uint16_t i = 1; i < 256; i += 1) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc, i); - } else { - memset(&mfc->block[i].value, 0xFF, 16); - } - mf_classic_set_block_read(mfc, i, &mfc->block[i]); - } - // Set SAK to 18 - data->nfc_data.sak = 0x18; - } else if(type == MfClassicType1k) { - // Set every block to 0xFF - for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc, i); - } else { - memset(&mfc->block[i].value, 0xFF, 16); - } - mf_classic_set_block_read(mfc, i, &mfc->block[i]); - } - // Set SAK to 08 - data->nfc_data.sak = 0x08; - } else if(type == MfClassicTypeMini) { - // Set every block to 0xFF - for(uint16_t i = 1; i < MF_MINI_TOTAL_SECTORS_NUM * 4; i += 1) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc, i); - } else { - memset(&mfc->block[i].value, 0xFF, 16); - } - mf_classic_set_block_read(mfc, i, &mfc->block[i]); - } - // Set SAK to 09 - data->nfc_data.sak = 0x09; - } - - nfc_generate_mf_classic_block_0( - data->mf_classic_data.block[0].value, - uid_len, - data->nfc_data.sak, - data->nfc_data.atqa[0], - data->nfc_data.atqa[1]); - - mfc->type = type; -} - -static void nfc_generate_mf_mini(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 4, MfClassicTypeMini); -} - -static void nfc_generate_mf_classic_1k_4b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 4, MfClassicType1k); -} - -static void nfc_generate_mf_classic_1k_7b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 7, MfClassicType1k); -} - -static void nfc_generate_mf_classic_4k_4b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 4, MfClassicType4k); -} - -static void nfc_generate_mf_classic_4k_7b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 7, MfClassicType4k); -} - -static const NfcGenerator mf_ul_generator = { - .name = "Mifare Ultralight", - .generator_func = nfc_generate_mf_ul_orig, -}; - -static const NfcGenerator mf_ul_11_generator = { - .name = "Mifare Ultralight EV1 11", - .generator_func = nfc_generate_mf_ul_11, -}; - -static const NfcGenerator mf_ul_h11_generator = { - .name = "Mifare Ultralight EV1 H11", - .generator_func = nfc_generate_mf_ul_h11, -}; - -static const NfcGenerator mf_ul_21_generator = { - .name = "Mifare Ultralight EV1 21", - .generator_func = nfc_generate_mf_ul_21, -}; - -static const NfcGenerator mf_ul_h21_generator = { - .name = "Mifare Ultralight EV1 H21", - .generator_func = nfc_generate_mf_ul_h21, -}; - -static const NfcGenerator ntag203_generator = { - .name = "NTAG203", - .generator_func = nfc_generate_mf_ul_ntag203, -}; - -static const NfcGenerator ntag213_generator = { - .name = "NTAG213", - .generator_func = nfc_generate_ntag213, -}; - -static const NfcGenerator ntag215_generator = { - .name = "NTAG215", - .generator_func = nfc_generate_ntag215, -}; - -static const NfcGenerator ntag216_generator = { - .name = "NTAG216", - .generator_func = nfc_generate_ntag216, -}; - -static const NfcGenerator ntag_i2c_1k_generator = { - .name = "NTAG I2C 1k", - .generator_func = nfc_generate_ntag_i2c_1k, -}; - -static const NfcGenerator ntag_i2c_2k_generator = { - .name = "NTAG I2C 2k", - .generator_func = nfc_generate_ntag_i2c_2k, -}; - -static const NfcGenerator ntag_i2c_plus_1k_generator = { - .name = "NTAG I2C Plus 1k", - .generator_func = nfc_generate_ntag_i2c_plus_1k, -}; - -static const NfcGenerator ntag_i2c_plus_2k_generator = { - .name = "NTAG I2C Plus 2k", - .generator_func = nfc_generate_ntag_i2c_plus_2k, -}; - -static const NfcGenerator mifare_mini_generator = { - .name = "Mifare Mini", - .generator_func = nfc_generate_mf_mini, -}; - -static const NfcGenerator mifare_classic_1k_4b_uid_generator = { - .name = "Mifare Classic 1k 4byte UID", - .generator_func = nfc_generate_mf_classic_1k_4b_uid, -}; - -static const NfcGenerator mifare_classic_1k_7b_uid_generator = { - .name = "Mifare Classic 1k 7byte UID", - .generator_func = nfc_generate_mf_classic_1k_7b_uid, -}; - -static const NfcGenerator mifare_classic_4k_4b_uid_generator = { - .name = "Mifare Classic 4k 4byte UID", - .generator_func = nfc_generate_mf_classic_4k_4b_uid, -}; - -static const NfcGenerator mifare_classic_4k_7b_uid_generator = { - .name = "Mifare Classic 4k 7byte UID", - .generator_func = nfc_generate_mf_classic_4k_7b_uid, -}; - -const NfcGenerator* const nfc_generators[] = { - &mf_ul_generator, - &mf_ul_11_generator, - &mf_ul_h11_generator, - &mf_ul_21_generator, - &mf_ul_h21_generator, - &ntag203_generator, - &ntag213_generator, - &ntag215_generator, - &ntag216_generator, - &ntag_i2c_1k_generator, - &ntag_i2c_2k_generator, - &ntag_i2c_plus_1k_generator, - &ntag_i2c_plus_2k_generator, - &mifare_mini_generator, - &mifare_classic_1k_4b_uid_generator, - &mifare_classic_1k_7b_uid_generator, - &mifare_classic_4k_4b_uid_generator, - &mifare_classic_4k_7b_uid_generator, - NULL, -}; diff --git a/lib/nfc/helpers/nfc_generators.h b/lib/nfc/helpers/nfc_generators.h deleted file mode 100644 index 8cee67067c..0000000000 --- a/lib/nfc/helpers/nfc_generators.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "../nfc_device.h" - -typedef void (*NfcGeneratorFunc)(NfcDeviceData* data); - -typedef struct { - const char* name; - NfcGeneratorFunc generator_func; -} NfcGenerator; - -extern const NfcGenerator* const nfc_generators[]; - -void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type); diff --git a/lib/nfc/protocols/nfc_util.c b/lib/nfc/helpers/nfc_util.c similarity index 100% rename from lib/nfc/protocols/nfc_util.c rename to lib/nfc/helpers/nfc_util.c diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/helpers/nfc_util.h similarity index 100% rename from lib/nfc/protocols/nfc_util.h rename to lib/nfc/helpers/nfc_util.h diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c deleted file mode 100644 index 9bf37a60d2..0000000000 --- a/lib/nfc/helpers/reader_analyzer.c +++ /dev/null @@ -1,265 +0,0 @@ -#include "reader_analyzer.h" -#include -#include -#include - -#include "mfkey32.h" -#include "nfc_debug_pcap.h" -#include "nfc_debug_log.h" - -#define TAG "ReaderAnalyzer" - -#define READER_ANALYZER_MAX_BUFF_SIZE (1024) - -typedef struct { - bool reader_to_tag; - bool crc_dropped; - uint16_t len; -} ReaderAnalyzerHeader; - -typedef enum { - ReaderAnalyzerNfcDataMfClassic, -} ReaderAnalyzerNfcData; - -struct ReaderAnalyzer { - FuriHalNfcDevData nfc_data; - - bool alive; - FuriStreamBuffer* stream; - FuriThread* thread; - - ReaderAnalyzerParseDataCallback callback; - void* context; - - ReaderAnalyzerMode mode; - Mfkey32* mfkey32; - NfcDebugLog* debug_log; - NfcDebugPcap* pcap; -}; - -const FuriHalNfcDevData reader_analyzer_nfc_data[] = { - [ReaderAnalyzerNfcDataMfClassic] = - {.sak = 0x08, - .atqa = {0x44, 0x00}, - .interface = FuriHalNfcInterfaceRf, - .type = FuriHalNfcTypeA, - .uid_len = 7, - .uid = {0x04, 0x77, 0x70, 0x2A, 0x23, 0x4F, 0x80}, - .cuid = 0x2A234F80}, -}; - -void reader_analyzer_parse(ReaderAnalyzer* instance, uint8_t* buffer, size_t size) { - if(size < sizeof(ReaderAnalyzerHeader)) return; - - size_t bytes_i = 0; - while(bytes_i < size) { - ReaderAnalyzerHeader* header = (ReaderAnalyzerHeader*)&buffer[bytes_i]; - uint16_t len = header->len; - if(bytes_i + len > size) break; - bytes_i += sizeof(ReaderAnalyzerHeader); - if(instance->mfkey32) { - mfkey32_process_data( - instance->mfkey32, - &buffer[bytes_i], - len, - header->reader_to_tag, - header->crc_dropped); - } - if(instance->pcap) { - nfc_debug_pcap_process_data( - instance->pcap, &buffer[bytes_i], len, header->reader_to_tag, header->crc_dropped); - } - if(instance->debug_log) { - nfc_debug_log_process_data( - instance->debug_log, - &buffer[bytes_i], - len, - header->reader_to_tag, - header->crc_dropped); - } - bytes_i += len; - } -} - -int32_t reader_analyzer_thread(void* context) { - ReaderAnalyzer* reader_analyzer = context; - uint8_t buffer[READER_ANALYZER_MAX_BUFF_SIZE] = {}; - - while(reader_analyzer->alive || !furi_stream_buffer_is_empty(reader_analyzer->stream)) { - size_t ret = furi_stream_buffer_receive( - reader_analyzer->stream, buffer, READER_ANALYZER_MAX_BUFF_SIZE, 50); - if(ret) { - reader_analyzer_parse(reader_analyzer, buffer, ret); - } - } - - return 0; -} - -ReaderAnalyzer* reader_analyzer_alloc() { - ReaderAnalyzer* instance = malloc(sizeof(ReaderAnalyzer)); - - instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; - instance->alive = false; - instance->stream = - furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader)); - - instance->thread = - furi_thread_alloc_ex("ReaderAnalyzerWorker", 2048, reader_analyzer_thread, instance); - furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); - - return instance; -} - -static void reader_analyzer_mfkey_callback(Mfkey32Event event, void* context) { - furi_assert(context); - ReaderAnalyzer* instance = context; - - if(event == Mfkey32EventParamCollected) { - if(instance->callback) { - instance->callback(ReaderAnalyzerEventMfkeyCollected, instance->context); - } - } -} - -void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode) { - furi_assert(instance); - - furi_stream_buffer_reset(instance->stream); - if(mode & ReaderAnalyzerModeDebugLog) { - instance->debug_log = nfc_debug_log_alloc(); - } - if(mode & ReaderAnalyzerModeMfkey) { - instance->mfkey32 = mfkey32_alloc(instance->nfc_data.cuid); - if(instance->mfkey32) { - mfkey32_set_callback(instance->mfkey32, reader_analyzer_mfkey_callback, instance); - } - } - if(mode & ReaderAnalyzerModeDebugPcap) { - instance->pcap = nfc_debug_pcap_alloc(); - } - - instance->alive = true; - furi_thread_start(instance->thread); -} - -void reader_analyzer_stop(ReaderAnalyzer* instance) { - furi_assert(instance); - - instance->alive = false; - furi_thread_join(instance->thread); - - if(instance->debug_log) { - nfc_debug_log_free(instance->debug_log); - instance->debug_log = NULL; - } - if(instance->mfkey32) { - mfkey32_free(instance->mfkey32); - instance->mfkey32 = NULL; - } - if(instance->pcap) { - nfc_debug_pcap_free(instance->pcap); - instance->pcap = NULL; - } -} - -void reader_analyzer_free(ReaderAnalyzer* instance) { - furi_assert(instance); - - reader_analyzer_stop(instance); - furi_thread_free(instance->thread); - furi_stream_buffer_free(instance->stream); - free(instance); -} - -void reader_analyzer_set_callback( - ReaderAnalyzer* instance, - ReaderAnalyzerParseDataCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -NfcProtocol - reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len) { - furi_assert(instance); - furi_assert(buff_rx); - UNUSED(len); - NfcProtocol protocol = NfcDeviceProtocolUnknown; - - if((buff_rx[0] == 0x60) || (buff_rx[0] == 0x61)) { - protocol = NfcDeviceProtocolMifareClassic; - } - - return protocol; -} - -FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) { - furi_assert(instance); - instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; - return &instance->nfc_data; -} - -void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data) { - furi_assert(instance); - furi_assert(nfc_data); - - memcpy(&instance->nfc_data, nfc_data, sizeof(FuriHalNfcDevData)); -} - -static void reader_analyzer_write( - ReaderAnalyzer* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - ReaderAnalyzerHeader header = { - .reader_to_tag = reader_to_tag, .crc_dropped = crc_dropped, .len = len}; - size_t data_sent = 0; - data_sent = furi_stream_buffer_send( - instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever); - if(data_sent != sizeof(ReaderAnalyzerHeader)) { - FURI_LOG_W(TAG, "Sent %zu out of %zu bytes", data_sent, sizeof(ReaderAnalyzerHeader)); - } - data_sent = furi_stream_buffer_send(instance->stream, data, len, FuriWaitForever); - if(data_sent != len) { - FURI_LOG_W(TAG, "Sent %zu out of %u bytes", data_sent, len); - } -} - -static void - reader_analyzer_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { - UNUSED(crc_dropped); - ReaderAnalyzer* reader_analyzer = context; - uint16_t bytes = bits < 8 ? 1 : bits / 8; - reader_analyzer_write(reader_analyzer, data, bytes, false, crc_dropped); -} - -static void - reader_analyzer_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { - UNUSED(crc_dropped); - ReaderAnalyzer* reader_analyzer = context; - uint16_t bytes = bits < 8 ? 1 : bits / 8; - reader_analyzer_write(reader_analyzer, data, bytes, true, crc_dropped); -} - -void reader_analyzer_prepare_tx_rx( - ReaderAnalyzer* instance, - FuriHalNfcTxRxContext* tx_rx, - bool is_picc) { - furi_assert(instance); - furi_assert(tx_rx); - - if(is_picc) { - tx_rx->sniff_tx = reader_analyzer_write_rx; - tx_rx->sniff_rx = reader_analyzer_write_tx; - } else { - tx_rx->sniff_rx = reader_analyzer_write_rx; - tx_rx->sniff_tx = reader_analyzer_write_tx; - } - - tx_rx->sniff_context = instance; -} diff --git a/lib/nfc/helpers/reader_analyzer.h b/lib/nfc/helpers/reader_analyzer.h deleted file mode 100644 index 13bf4d77cc..0000000000 --- a/lib/nfc/helpers/reader_analyzer.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include - -typedef enum { - ReaderAnalyzerModeDebugLog = 0x01, - ReaderAnalyzerModeMfkey = 0x02, - ReaderAnalyzerModeDebugPcap = 0x04, -} ReaderAnalyzerMode; - -typedef enum { - ReaderAnalyzerEventMfkeyCollected, -} ReaderAnalyzerEvent; - -typedef struct ReaderAnalyzer ReaderAnalyzer; - -typedef void (*ReaderAnalyzerParseDataCallback)(ReaderAnalyzerEvent event, void* context); - -ReaderAnalyzer* reader_analyzer_alloc(); - -void reader_analyzer_free(ReaderAnalyzer* instance); - -void reader_analyzer_set_callback( - ReaderAnalyzer* instance, - ReaderAnalyzerParseDataCallback callback, - void* context); - -void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode); - -void reader_analyzer_stop(ReaderAnalyzer* instance); - -NfcProtocol - reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len); - -FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance); - -void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data); - -void reader_analyzer_prepare_tx_rx( - ReaderAnalyzer* instance, - FuriHalNfcTxRxContext* tx_rx, - bool is_picc); diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c new file mode 100644 index 0000000000..a7c4fe1a6d --- /dev/null +++ b/lib/nfc/nfc.c @@ -0,0 +1,649 @@ +#ifndef FW_CFG_unit_tests + +#include "nfc.h" + +#include +#include + +#define TAG "Nfc" + +#define NFC_MAX_BUFFER_SIZE (256) + +typedef enum { + NfcStateIdle, + NfcStateRunning, +} NfcState; + +typedef enum { + NfcPollerStateStart, + NfcPollerStateReady, + NfcPollerStateReset, + NfcPollerStateStop, + + NfcPollerStateNum, +} NfcPollerState; + +typedef enum { + NfcCommStateIdle, + NfcCommStateWaitBlockTxTimer, + NfcCommStateReadyTx, + NfcCommStateWaitTxEnd, + NfcCommStateWaitRxStart, + NfcCommStateWaitRxEnd, + NfcCommStateFailed, +} NfcCommState; + +typedef enum { + NfcConfigurationStateIdle, + NfcConfigurationStateDone, +} NfcConfigurationState; + +struct Nfc { + NfcState state; + NfcPollerState poller_state; + NfcCommState comm_state; + NfcConfigurationState config_state; + NfcMode mode; + + uint32_t fdt_listen_fc; + uint32_t mask_rx_time_fc; + uint32_t fdt_poll_fc; + uint32_t fdt_poll_poll_us; + uint32_t guard_time_us; + NfcEventCallback callback; + void* context; + + uint8_t tx_buffer[NFC_MAX_BUFFER_SIZE]; + size_t tx_bits; + uint8_t rx_buffer[NFC_MAX_BUFFER_SIZE]; + size_t rx_bits; + + FuriThread* worker_thread; +}; + +typedef bool (*NfcWorkerPollerStateHandler)(Nfc* instance); + +static const FuriHalNfcTech nfc_tech_table[NfcModeNum][NfcTechNum] = { + [NfcModePoller] = + { + [NfcTechIso14443a] = FuriHalNfcTechIso14443a, + [NfcTechIso14443b] = FuriHalNfcTechIso14443b, + [NfcTechIso15693] = FuriHalNfcTechIso15693, + [NfcTechFelica] = FuriHalNfcTechFelica, + }, + [NfcModeListener] = + { + [NfcTechIso14443a] = FuriHalNfcTechIso14443a, + [NfcTechIso14443b] = FuriHalNfcTechInvalid, + [NfcTechIso15693] = FuriHalNfcTechIso15693, + [NfcTechFelica] = FuriHalNfcTechInvalid, + }, +}; + +static NfcError nfc_process_hal_error(FuriHalNfcError error) { + NfcError ret = NfcErrorNone; + + switch(error) { + case FuriHalNfcErrorNone: + ret = NfcErrorNone; + break; + case FuriHalNfcErrorIncompleteFrame: + ret = NfcErrorIncompleteFrame; + break; + case FuriHalNfcErrorDataFormat: + ret = NfcErrorDataFormat; + break; + + default: + ret = NfcErrorInternal; + } + + return ret; +} + +static int32_t nfc_worker_listener(void* context) { + furi_assert(context); + + Nfc* instance = context; + furi_assert(instance->callback); + furi_assert(instance->config_state == NfcConfigurationStateDone); + + instance->state = NfcStateRunning; + + furi_hal_nfc_event_start(); + + NfcEventData event_data = {}; + event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + NfcEvent nfc_event = {.data = event_data}; + NfcCommand command = NfcCommandContinue; + + while(true) { + FuriHalNfcEvent event = furi_hal_nfc_listener_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventAbortRequest) { + nfc_event.type = NfcEventTypeUserAbort; + instance->callback(nfc_event, instance->context); + break; + } + if(event & FuriHalNfcEventFieldOn) { + nfc_event.type = NfcEventTypeFieldOn; + instance->callback(nfc_event, instance->context); + } + if(event & FuriHalNfcEventFieldOff) { + nfc_event.type = NfcEventTypeFieldOff; + instance->callback(nfc_event, instance->context); + furi_hal_nfc_listener_idle(); + } + if(event & FuriHalNfcEventListenerActive) { + nfc_event.type = NfcEventTypeListenerActivated; + instance->callback(nfc_event, instance->context); + } + if(event & FuriHalNfcEventRxEnd) { + furi_hal_nfc_timer_block_tx_start(instance->fdt_listen_fc); + + nfc_event.type = NfcEventTypeRxEnd; + furi_hal_nfc_listener_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + bit_buffer_copy_bits(event_data.buffer, instance->rx_buffer, instance->rx_bits); + command = instance->callback(nfc_event, instance->context); + if(command == NfcCommandStop) { + break; + } else if(command == NfcCommandReset) { + furi_hal_nfc_listener_enable_rx(); + } else if(command == NfcCommandSleep) { + furi_hal_nfc_listener_sleep(); + } + } + } + + furi_hal_nfc_reset_mode(); + instance->config_state = NfcConfigurationStateIdle; + + bit_buffer_free(event_data.buffer); + furi_hal_nfc_low_power_mode_start(); + return 0; +} + +bool nfc_worker_poller_start_handler(Nfc* instance) { + furi_hal_nfc_poller_field_on(); + if(instance->guard_time_us) { + furi_hal_nfc_timer_block_tx_start_us(instance->guard_time_us); + FuriHalNfcEvent event = furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + furi_assert(event & FuriHalNfcEventTimerBlockTxExpired); + } + instance->poller_state = NfcPollerStateReady; + + return false; +} + +bool nfc_worker_poller_ready_handler(Nfc* instance) { + NfcCommand command = NfcCommandContinue; + + NfcEvent event = {.type = NfcEventTypePollerReady}; + command = instance->callback(event, instance->context); + if(command == NfcCommandReset) { + instance->poller_state = NfcPollerStateReset; + } else if(command == NfcCommandStop) { + instance->poller_state = NfcPollerStateStop; + } + + return false; +} + +bool nfc_worker_poller_reset_handler(Nfc* instance) { + furi_hal_nfc_low_power_mode_start(); + furi_delay_ms(100); + furi_hal_nfc_low_power_mode_stop(); + instance->poller_state = NfcPollerStateStart; + + return false; +} + +bool nfc_worker_poller_stop_handler(Nfc* instance) { + furi_hal_nfc_reset_mode(); + instance->config_state = NfcConfigurationStateIdle; + + furi_hal_nfc_low_power_mode_start(); + // Wait after field is off some time to reset tag power + furi_delay_ms(10); + instance->poller_state = NfcPollerStateStart; + + return true; +} + +static const NfcWorkerPollerStateHandler nfc_worker_poller_state_handlers[NfcPollerStateNum] = { + [NfcPollerStateStart] = nfc_worker_poller_start_handler, + [NfcPollerStateReady] = nfc_worker_poller_ready_handler, + [NfcPollerStateReset] = nfc_worker_poller_reset_handler, + [NfcPollerStateStop] = nfc_worker_poller_stop_handler, +}; + +static int32_t nfc_worker_poller(void* context) { + furi_assert(context); + + Nfc* instance = context; + furi_assert(instance->callback); + instance->state = NfcStateRunning; + instance->poller_state = NfcPollerStateStart; + + furi_hal_nfc_event_start(); + + bool exit = false; + while(!exit) { + exit = nfc_worker_poller_state_handlers[instance->poller_state](instance); + } + + return 0; +} + +Nfc* nfc_alloc() { + furi_check(furi_hal_nfc_acquire() == FuriHalNfcErrorNone); + + Nfc* instance = malloc(sizeof(Nfc)); + instance->state = NfcStateIdle; + instance->comm_state = NfcCommStateIdle; + instance->config_state = NfcConfigurationStateIdle; + + instance->worker_thread = furi_thread_alloc(); + furi_thread_set_name(instance->worker_thread, "NfcWorker"); + furi_thread_set_context(instance->worker_thread, instance); + furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHighest); + furi_thread_set_stack_size(instance->worker_thread, 8 * 1024); + + return instance; +} + +void nfc_free(Nfc* instance) { + furi_assert(instance); + furi_assert(instance->state == NfcStateIdle); + + furi_thread_free(instance->worker_thread); + free(instance); + + furi_hal_nfc_release(); +} + +void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) { + furi_assert(instance); + furi_assert(mode < NfcModeNum); + furi_assert(tech < NfcTechNum); + furi_assert(instance->config_state == NfcConfigurationStateIdle); + + FuriHalNfcTech hal_tech = nfc_tech_table[mode][tech]; + if(hal_tech == FuriHalNfcTechInvalid) { + furi_crash("Unsupported mode for given tech"); + } + FuriHalNfcMode hal_mode = (mode == NfcModePoller) ? FuriHalNfcModePoller : + FuriHalNfcModeListener; + furi_hal_nfc_low_power_mode_stop(); + furi_hal_nfc_set_mode(hal_mode, hal_tech); + + instance->mode = mode; + instance->config_state = NfcConfigurationStateDone; +} + +void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) { + furi_assert(instance); + instance->fdt_poll_fc = fdt_poll_fc; +} + +void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) { + furi_assert(instance); + instance->fdt_listen_fc = fdt_listen_fc; +} + +void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) { + furi_assert(instance); + instance->fdt_poll_poll_us = fdt_poll_poll_us; +} + +void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) { + furi_assert(instance); + instance->guard_time_us = guard_time_us; +} + +void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) { + furi_assert(instance); + instance->mask_rx_time_fc = mask_rx_time_fc; +} + +void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { + furi_assert(instance); + furi_assert(instance->worker_thread); + furi_assert(callback); + furi_assert(instance->config_state == NfcConfigurationStateDone); + + instance->callback = callback; + instance->context = context; + if(instance->mode == NfcModePoller) { + furi_thread_set_callback(instance->worker_thread, nfc_worker_poller); + } else { + furi_thread_set_callback(instance->worker_thread, nfc_worker_listener); + } + instance->comm_state = NfcCommStateIdle; + furi_thread_start(instance->worker_thread); +} + +void nfc_stop(Nfc* instance) { + furi_assert(instance); + furi_assert(instance->state == NfcStateRunning); + + if(instance->mode == NfcModeListener) { + furi_hal_nfc_abort(); + } + furi_thread_join(instance->worker_thread); + + instance->state = NfcStateIdle; +} + +NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + NfcError ret = NfcErrorNone; + + while(furi_hal_nfc_timer_block_tx_is_running()) { + } + + FuriHalNfcError error = + furi_hal_nfc_listener_tx(bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer)); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in listener TX"); + ret = nfc_process_hal_error(error); + } + + return ret; +} + +static NfcError nfc_poller_trx_state_machine(Nfc* instance, uint32_t fwt_fc) { + FuriHalNfcEvent event = 0; + NfcError error = NfcErrorNone; + + while(true) { + event = furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) { + if(instance->comm_state == NfcCommStateWaitBlockTxTimer) { + instance->comm_state = NfcCommStateReadyTx; + } + } + if(event & FuriHalNfcEventTxEnd) { + if(instance->comm_state == NfcCommStateWaitTxEnd) { + if(fwt_fc) { + furi_hal_nfc_timer_fwt_start(fwt_fc); + } + furi_hal_nfc_timer_block_tx_start_us(instance->fdt_poll_poll_us); + instance->comm_state = NfcCommStateWaitRxStart; + } + } + if(event & FuriHalNfcEventRxStart) { + if(instance->comm_state == NfcCommStateWaitRxStart) { + furi_hal_nfc_timer_block_tx_stop(); + furi_hal_nfc_timer_fwt_stop(); + instance->comm_state = NfcCommStateWaitRxEnd; + } + } + if(event & FuriHalNfcEventRxEnd) { + furi_hal_nfc_timer_block_tx_start(instance->fdt_poll_fc); + furi_hal_nfc_timer_fwt_stop(); + instance->comm_state = NfcCommStateWaitBlockTxTimer; + break; + } + if(event & FuriHalNfcEventTimerFwtExpired) { + if(instance->comm_state == NfcCommStateWaitRxStart) { + error = NfcErrorTimeout; + FURI_LOG_D(TAG, "FWT Timeout"); + if(furi_hal_nfc_timer_block_tx_is_running()) { + instance->comm_state = NfcCommStateWaitBlockTxTimer; + } else { + instance->comm_state = NfcCommStateReadyTx; + } + break; + } + } + } + + return error; +} + +NfcError nfc_iso14443a_poller_trx_custom_parity( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + bit_buffer_write_bytes_with_parity( + tx_buffer, instance->tx_buffer, sizeof(instance->tx_buffer), &instance->tx_bits); + error = + furi_hal_nfc_iso14443a_poller_tx_custom_parity(instance->tx_buffer, instance->tx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + if(instance->rx_bits >= 9) { + if((instance->rx_bits % 9) != 0) { + ret = NfcErrorDataFormat; + break; + } + } + + bit_buffer_copy_bytes_with_parity(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError + nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + error = + furi_hal_nfc_poller_tx(bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer)); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + + bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError nfc_iso14443a_listener_set_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + furi_assert(instance); + + FuriHalNfcError error = + furi_hal_nfc_iso14443a_listener_set_col_res_data(uid, uid_len, atqa, sak); + instance->comm_state = NfcCommStateIdle; + return nfc_process_hal_error(error); +} + +NfcError nfc_iso14443a_poller_trx_short_frame( + Nfc* instance, + NfcIso14443aShortFrame frame, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(rx_buffer); + + FuriHalNfcaShortFrame short_frame = (frame == NfcIso14443aShortFrameAllReqa) ? + FuriHalNfcaShortFrameAllReq : + FuriHalNfcaShortFrameSensReq; + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + error = furi_hal_nfc_iso14443a_poller_trx_short_frame(short_frame); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + + bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError nfc_iso14443a_poller_trx_sdd_frame( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + error = furi_hal_nfc_iso14443a_tx_sdd_frame( + bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer)); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + + bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + + const uint8_t* tx_data = bit_buffer_get_data(tx_buffer); + const uint8_t* tx_parity = bit_buffer_get_parity(tx_buffer); + size_t tx_bits = bit_buffer_get_size(tx_buffer); + + error = furi_hal_nfc_iso14443a_listener_tx_custom_parity(tx_data, tx_parity, tx_bits); + ret = nfc_process_hal_error(error); + + return ret; +} + +NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { + furi_assert(instance); + + while(furi_hal_nfc_timer_block_tx_is_running()) { + } + + FuriHalNfcError error = furi_hal_nfc_iso15693_listener_tx_sof(); + NfcError ret = nfc_process_hal_error(error); + + return ret; +} + +#endif // APP_UNIT_TESTS diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h new file mode 100644 index 0000000000..1e8f315a50 --- /dev/null +++ b/lib/nfc/nfc.h @@ -0,0 +1,364 @@ +/** + * @file nfc.h + * @brief Transport layer Nfc library. + * + * The Nfc layer is responsible for setting the operating mode (poller or listener) + * and technology (ISO14443-3A/B, ISO15693, ...), data exchange between higher + * protocol-specific levels and underlying NFC hardware, as well as timings handling. + * + * In applications using the NFC protocol system there is no need to neiter explicitly + * create an Nfc instance nor call any of its functions, as it is all handled + * automatically under the hood. + * + * If the NFC protocol system is not suitable for the application's intended purpose + * or there is need of having direct access to the NFC transport layer, then an Nfc + * instance must be allocated and the below functions shall be used to implement + * the required logic. + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Nfc opaque type definition. + */ +typedef struct Nfc Nfc; + +/** + * @brief Enumeration of possible Nfc event types. + * + * Not all technologies implement all events (this is due to hardware limitations). + */ +typedef enum { + NfcEventTypeUserAbort, /**< User code explicitly aborted the current operation. */ + NfcEventTypeFieldOn, /**< Reader's field was detected by the NFC hardware. */ + NfcEventTypeFieldOff, /**< Reader's field was lost. */ + NfcEventTypeTxStart, /**< Data transmission has started. */ + NfcEventTypeTxEnd, /**< Data transmission has ended. */ + NfcEventTypeRxStart, /**< Data reception has started. */ + NfcEventTypeRxEnd, /**< Data reception has ended. */ + + NfcEventTypeListenerActivated, /**< The listener has been activated by the reader. */ + NfcEventTypePollerReady, /**< The card has been activated by the poller. */ +} NfcEventType; + +/** + * @brief Nfc event data structure. + */ +typedef struct { + BitBuffer* buffer; /**< Pointer to the received data buffer. */ +} NfcEventData; + +/** + * @brief Nfc event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + NfcEventType type; /**< Type of the emitted event. */ + NfcEventData data; /**< Event-specific data. */ +} NfcEvent; + +/** + * @brief Enumeration of possible Nfc commands. + * + * The event callback must return one of these to determine the next action. + */ +typedef enum { + NfcCommandContinue, /**< Continue operation normally. */ + NfcCommandReset, /**< Reset the current state. */ + NfcCommandStop, /**< Stop the current operation. */ + NfcCommandSleep, /**< Switch Nfc hardware to low-power mode. */ +} NfcCommand; + +/** + * @brief Nfc event callback type. + * + * A function of this type must be passed as the callback parameter upon start of a an Nfc instance. + * + * @param [in] event Nfc event, passed by value, complete with protocol type and data. + * @param [in,out] context pointer to the user-specific context (set when starting an Nfc instance). + * @returns command which the event producer must execute. + */ +typedef NfcCommand (*NfcEventCallback)(NfcEvent event, void* context); + +/** + * @brief Enumeration of possible operating modes. + * + * Not all technologies implement the listener operating mode. + */ +typedef enum { + NfcModePoller, /**< Configure the Nfc instance as a poller. */ + NfcModeListener, /**< Configure the Nfc instance as a listener. */ + + NfcModeNum, /**< Operating mode count. Internal use. */ +} NfcMode; + +/** + * @brief Enumeration of available technologies. + */ +typedef enum { + NfcTechIso14443a, /**< Configure the Nfc instance to use the ISO14443-3A technology. */ + NfcTechIso14443b, /**< Configure the Nfc instance to use the ISO14443-3B technology. */ + NfcTechIso15693, /**< Configure the Nfc instance to use the ISO15693 technology. */ + NfcTechFelica, /**< Configure the Nfc instance to use the FeliCa technology. */ + + NfcTechNum, /**< Technologies count. Internal use. */ +} NfcTech; + +/** + * @brief Enumeration of possible Nfc error codes. + */ +typedef enum { + NfcErrorNone, /**< No error has occurred. */ + NfcErrorInternal, /**< An unknown error has occured on the lower level. */ + NfcErrorTimeout, /**< Operation is taking too long (e.g. card does not respond). */ + NfcErrorIncompleteFrame, /**< An incomplete data frame has been received. */ + NfcErrorDataFormat, /**< Data has not been parsed due to wrong/unknown format. */ +} NfcError; + +/** + * @brief Allocate an Nfc instance. + * + * Will exclusively take over the NFC HAL until deleted. + * + * @returns pointer to the allocated Nfc instance. + */ +Nfc* nfc_alloc(); + +/** + * @brief Delete an Nfc instance. + * + * Will release the NFC HAL lock, making it available for use by others. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_free(Nfc* instance); + +/** + * @brief Configure the Nfc instance to work in a particular mode. + * + * Not all technologies implement the listener operating mode. + * + * @param[in,out] instance pointer to the instance to be configured. + * @param[in] mode required operating mode. + * @param[in] tech required technology configuration. + */ +void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech); + +/** + * @brief Set poller frame delay time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] fdt_poll_fc frame delay time, in carrier cycles. +*/ +void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc); + +/** + * @brief Set listener frame delay time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] fdt_listen_fc frame delay time, in carrier cycles. + */ +void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc); + +/** + * @brief Set mask receive time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] mask_rx_time mask receive time, in carrier cycles. + */ +void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc); + +/** + * @brief Set frame delay time. + * + * Frame delay time is the minimum time between two consecutive poll frames. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] fdt_poll_poll_us frame delay time, in microseconds. + */ +void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us); + +/** + * @brief Set guard time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] guard_time_us guard time, in microseconds. + */ +void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us); + +/** + * @brief Start the Nfc instance. + * + * The instance must be configured to work with a specific technology + * in a specific operating mode with a nfc_config() call before starting. + * + * Once started, the user code will be receiving events through the provided + * callback which must handle them according to the logic required. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_start(Nfc* instance, NfcEventCallback callback, void* context); + +/** + * @brief Stop Nfc instance. + * + * The instance can only be stopped if it is running. + * + * @param[in,out] instance pointer to the instance to be stopped. + */ +void nfc_stop(Nfc* instance); + +/** + * @brief Transmit and receive a data frame in poller mode. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * The data being transmitted and received may be either bit- or byte-oriented. + * It shall not contain any technology-specific sequences as start or stop bits + * and/or other special symbols, as this is handled on the underlying HAL level. + * + * Must ONLY be used inside the callback function. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError + nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt); + +/** + * @brief Transmit a data frame in listener mode. + * + * Used to transmit a response to the reader request in listener mode. + * + * The data being transmitted may be either bit- or byte-oriented. + * It shall not contain any technology-specific sequences as start or stop bits + * and/or other special symbols, as this is handled on the underlying HAL level. + * + * Must ONLY be used inside the callback function. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer); + +/* + * Technology-specific functions. + * + * In a perfect world, this would not be necessary. + * However, the current implementation employs NFC hardware that partially implements + * certain protocols (e.g. ISO14443-3A), thus requiring methods to access such features. + */ + +/******************* ISO14443-3A specific API *******************/ + +/** + * @brief Enumeration of possible ISO14443-3A short frame types. + */ +typedef enum { + NfcIso14443aShortFrameSensReq, + NfcIso14443aShortFrameAllReqa, +} NfcIso14443aShortFrame; + +/** + * @brief Transmit an ISO14443-3A short frame and receive the response in poller mode. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] frame type of short frame to be sent. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_poller_trx_short_frame( + Nfc* instance, + NfcIso14443aShortFrame frame, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Transmit an ISO14443-3A SDD frame and receive the response in poller mode. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_poller_trx_sdd_frame( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Transmit an ISO14443-3A data frame with custom parity bits and receive the response in poller mode. + * + * Same as nfc_poller_trx(), but uses the parity bits provided by the user code + * instead of calculating them automatically. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_poller_trx_custom_parity( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Transmit an ISO14443-3A frame with custom parity bits in listener mode. + * + * Same as nfc_listener_tx(), but uses the parity bits provided by the user code + * instead of calculating them automatically. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer); + +/** + * @brief Set ISO14443-3A collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in,out] instance pointer to the instance to be configured. + * @param[in] uid pointer to a byte array containing the UID. + * @param[in] uid_len UID length in bytes (must be supported by the protocol). + * @param[in] atqa ATQA byte value. + * @param[in] sak SAK byte value. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_listener_set_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak); + +/** + * @brief Send ISO15693 Start of Frame pattern in listener mode + * + * @param[in,out] instance pointer to the instance to be configured. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_common.h b/lib/nfc/nfc_common.h new file mode 100644 index 0000000000..c3cccf697d --- /dev/null +++ b/lib/nfc/nfc_common.h @@ -0,0 +1,22 @@ +/** + * @file nfc_common.h + * @brief Various common NFC-related macros. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* NFC file format version which changed ATQA format. Deprecated. */ +#define NFC_LSB_ATQA_FORMAT_VERSION (2) +/* NFC file format version which is still supported as backwards compatible. */ +#define NFC_MINIMUM_SUPPORTED_FORMAT_VERSION NFC_LSB_ATQA_FORMAT_VERSION +/* NFC file format version which implemented the unified loading process. */ +#define NFC_UNIFIED_FORMAT_VERSION (4) +/* Current NFC file format version. */ +#define NFC_CURRENT_FORMAT_VERSION NFC_UNIFIED_FORMAT_VERSION + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index c5c8b43384..267824d16e 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1,1735 +1,356 @@ -#include "nfc_device.h" -#include "assets_icons.h" -#include "nfc_types.h" +#include "nfc_device_i.h" -#include -#include -#include +#include #include -#define TAG "NfcDevice" -#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/.cache") -#define NFC_DEVICE_KEYS_EXTENSION ".keys" +#include "nfc_common.h" +#include "protocols/nfc_device_defs.h" -static const char* nfc_file_header = "Flipper NFC device"; -static const uint32_t nfc_file_version = 3; +#define NFC_FILE_HEADER "Flipper NFC device" +#define NFC_DEV_TYPE_ERROR "Protocol type mismatch" -static const char* nfc_keys_file_header = "Flipper NFC keys"; -static const uint32_t nfc_keys_file_version = 1; +#define NFC_DEVICE_UID_KEY "UID" +#define NFC_DEVICE_TYPE_KEY "Device type" -// Protocols format versions -static const uint32_t nfc_mifare_classic_data_format_version = 2; -static const uint32_t nfc_mifare_ultralight_data_format_version = 1; +#define NFC_DEVICE_UID_MAX_LEN (10U) NfcDevice* nfc_device_alloc() { - NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); - nfc_dev->storage = furi_record_open(RECORD_STORAGE); - nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); - nfc_dev->load_path = furi_string_alloc(); - nfc_dev->dev_data.parsed_data = furi_string_alloc(); - nfc_dev->folder = furi_string_alloc(); - - // Rename cache folder name for backward compatibility - if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { - storage_common_rename(nfc_dev->storage, "/ext/nfc/cache", NFC_DEVICE_KEYS_FOLDER); - } - return nfc_dev; -} + NfcDevice* instance = malloc(sizeof(NfcDevice)); + instance->protocol = NfcProtocolInvalid; -void nfc_device_free(NfcDevice* nfc_dev) { - furi_assert(nfc_dev); - nfc_device_clear(nfc_dev); - furi_record_close(RECORD_STORAGE); - furi_record_close(RECORD_DIALOGS); - furi_string_free(nfc_dev->load_path); - furi_string_free(nfc_dev->dev_data.parsed_data); - furi_string_free(nfc_dev->folder); - free(nfc_dev); + return instance; } -static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_string) { - if(dev->format == NfcDeviceSaveFormatUid) { - furi_string_set(format_string, "UID"); - } else if(dev->format == NfcDeviceSaveFormatBankCard) { - furi_string_set(format_string, "Bank card"); - } else if(dev->format == NfcDeviceSaveFormatMifareUl) { - furi_string_set(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); - } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { - furi_string_set(format_string, "Mifare Classic"); - } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { - furi_string_set(format_string, "Mifare DESFire"); - } else if(dev->format == NfcDeviceSaveFormatNfcV) { - furi_string_set(format_string, "ISO15693"); - } else { - furi_string_set(format_string, "Unknown"); - } -} +void nfc_device_free(NfcDevice* instance) { + furi_assert(instance); -static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_string) { - if(furi_string_start_with_str(format_string, "UID")) { - dev->format = NfcDeviceSaveFormatUid; - dev->dev_data.protocol = NfcDeviceProtocolUnknown; - return true; - } - if(furi_string_start_with_str(format_string, "Bank card")) { - dev->format = NfcDeviceSaveFormatBankCard; - dev->dev_data.protocol = NfcDeviceProtocolEMV; - return true; - } - // Check Mifare Ultralight types - for(MfUltralightType type = MfUltralightTypeUnknown; type < MfUltralightTypeNum; type++) { - if(furi_string_equal(format_string, nfc_mf_ul_type(type, true))) { - dev->format = NfcDeviceSaveFormatMifareUl; - dev->dev_data.protocol = NfcDeviceProtocolMifareUl; - dev->dev_data.mf_ul_data.type = type; - return true; - } - } - if(furi_string_start_with_str(format_string, "Mifare Classic")) { - dev->format = NfcDeviceSaveFormatMifareClassic; - dev->dev_data.protocol = NfcDeviceProtocolMifareClassic; - return true; - } - if(furi_string_start_with_str(format_string, "Mifare DESFire")) { - dev->format = NfcDeviceSaveFormatMifareDesfire; - dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; - return true; - } - if(furi_string_start_with_str(format_string, "ISO15693")) { - dev->format = NfcDeviceSaveFormatNfcV; - dev->dev_data.protocol = NfcDeviceProtocolNfcV; - return true; - } - return false; + nfc_device_clear(instance); + free(instance); } -static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - MfUltralightData* data = &dev->dev_data.mf_ul_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); +void nfc_device_clear(NfcDevice* instance) { + furi_assert(instance); - // Save Mifare Ultralight specific data - do { - if(!flipper_format_write_comment_cstr(file, "Mifare Ultralight specific data")) break; - if(!flipper_format_write_uint32( - file, "Data format version", &nfc_mifare_ultralight_data_format_version, 1)) - break; - if(!flipper_format_write_hex(file, "Signature", data->signature, sizeof(data->signature))) - break; - if(!flipper_format_write_hex( - file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) - break; - // Write conters and tearing flags data - bool counters_saved = true; - for(uint8_t i = 0; i < 3; i++) { - furi_string_printf(temp_str, "Counter %d", i); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { - counters_saved = false; - break; - } - furi_string_printf(temp_str, "Tearing %d", i); - if(!flipper_format_write_hex( - file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { - counters_saved = false; - break; - } - } - if(!counters_saved) break; - // Write pages data - uint32_t pages_total = data->data_size / 4; - if(!flipper_format_write_uint32(file, "Pages total", &pages_total, 1)) break; - uint32_t pages_read = data->data_read / 4; - if(!flipper_format_write_uint32(file, "Pages read", &pages_read, 1)) break; - bool pages_saved = true; - for(uint16_t i = 0; i < data->data_size; i += 4) { - furi_string_printf(temp_str, "Page %d", i / 4); - if(!flipper_format_write_hex(file, furi_string_get_cstr(temp_str), &data->data[i], 4)) { - pages_saved = false; - break; - } + if(instance->protocol == NfcProtocolInvalid) { + furi_assert(instance->protocol_data == NULL); + } else if(instance->protocol < NfcProtocolNum) { + if(instance->protocol_data) { + nfc_devices[instance->protocol]->free(instance->protocol_data); + instance->protocol_data = NULL; } - if(!pages_saved) break; - - // Write authentication counter - uint32_t auth_counter = data->curr_authlim; - if(!flipper_format_write_uint32(file, "Failed authentication attempts", &auth_counter, 1)) - break; - - saved = true; - } while(false); - - furi_string_free(temp_str); - return saved; + instance->protocol = NfcProtocolInvalid; + } } -bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - MfUltralightData* data = &dev->dev_data.mf_ul_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); - uint32_t data_format_version = 0; - - do { - // Read Mifare Ultralight format version - if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { - if(!flipper_format_rewind(file)) break; - } - - // Read signature - if(!flipper_format_read_hex(file, "Signature", data->signature, sizeof(data->signature))) - break; - // Read Mifare version - if(!flipper_format_read_hex( - file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) - break; - // Read counters and tearing flags - bool counters_parsed = true; - for(uint8_t i = 0; i < 3; i++) { - furi_string_printf(temp_str, "Counter %d", i); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { - counters_parsed = false; - break; - } - furi_string_printf(temp_str, "Tearing %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { - counters_parsed = false; - break; - } - } - if(!counters_parsed) break; - // Read pages - uint32_t pages_total = 0; - if(!flipper_format_read_uint32(file, "Pages total", &pages_total, 1)) break; - uint32_t pages_read = 0; - if(data_format_version < nfc_mifare_ultralight_data_format_version) { - pages_read = pages_total; - } else { - if(!flipper_format_read_uint32(file, "Pages read", &pages_read, 1)) break; - } - data->data_size = pages_total * 4; - data->data_read = pages_read * 4; - if(data->data_size > MF_UL_MAX_DUMP_SIZE || data->data_read > MF_UL_MAX_DUMP_SIZE) break; - bool pages_parsed = true; - for(uint16_t i = 0; i < pages_total; i++) { - furi_string_printf(temp_str, "Page %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), &data->data[i * 4], 4)) { - pages_parsed = false; - break; - } - } - if(!pages_parsed) break; +void nfc_device_reset(NfcDevice* instance) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - // Read authentication counter - uint32_t auth_counter; - if(!flipper_format_read_uint32(file, "Failed authentication attempts", &auth_counter, 1)) - auth_counter = 0; - data->curr_authlim = auth_counter; - - data->auth_success = mf_ul_is_full_capture(data); - - parsed = true; - } while(false); - - furi_string_free(temp_str); - return parsed; + if(instance->protocol_data) { + nfc_devices[instance->protocol]->reset(instance->protocol_data); + } } -static bool nfc_device_save_mifare_df_key_settings( - FlipperFormat* file, - MifareDesfireKeySettings* ks, - const char* prefix) { - bool saved = false; - FuriString* key; - key = furi_string_alloc(); - - do { - furi_string_printf(key, "%s Change Key ID", prefix); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) - break; - furi_string_printf(key, "%s Config Changeable", prefix); - if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) - break; - furi_string_printf(key, "%s Free Create Delete", prefix); - if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) - break; - furi_string_printf(key, "%s Free Directory List", prefix); - if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) - break; - furi_string_printf(key, "%s Key Changeable", prefix); - if(!flipper_format_write_bool( - file, furi_string_get_cstr(key), &ks->master_key_changeable, 1)) - break; - if(ks->flags) { - furi_string_printf(key, "%s Flags", prefix); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->flags, 1)) break; - } - furi_string_printf(key, "%s Max Keys", prefix); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->max_keys, 1)) break; - for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { - furi_string_printf(key, "%s Key %d Version", prefix, kv->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &kv->version, 1)) break; - } - saved = true; - } while(false); +NfcProtocol nfc_device_get_protocol(const NfcDevice* instance) { + furi_assert(instance); + return instance->protocol; +} - furi_string_free(key); - return saved; +const NfcDeviceData* nfc_device_get_data(const NfcDevice* instance, NfcProtocol protocol) { + return nfc_device_get_data_ptr(instance, protocol); } -bool nfc_device_load_mifare_df_key_settings( - FlipperFormat* file, - MifareDesfireKeySettings* ks, - const char* prefix) { - bool parsed = false; - FuriString* key; - key = furi_string_alloc(); +const char* nfc_device_get_protocol_name(NfcProtocol protocol) { + furi_assert(protocol < NfcProtocolNum); - do { - furi_string_printf(key, "%s Change Key ID", prefix); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) break; - furi_string_printf(key, "%s Config Changeable", prefix); - if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) - break; - furi_string_printf(key, "%s Free Create Delete", prefix); - if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) - break; - furi_string_printf(key, "%s Free Directory List", prefix); - if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) - break; - furi_string_printf(key, "%s Key Changeable", prefix); - if(!flipper_format_read_bool( - file, furi_string_get_cstr(key), &ks->master_key_changeable, 1)) - break; - furi_string_printf(key, "%s Flags", prefix); - if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->flags, 1)) break; - } - furi_string_printf(key, "%s Max Keys", prefix); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->max_keys, 1)) break; - ks->flags |= ks->max_keys >> 4; - ks->max_keys &= 0xF; - MifareDesfireKeyVersion** kv_head = &ks->key_version_head; - for(int key_id = 0; key_id < ks->max_keys; key_id++) { - furi_string_printf(key, "%s Key %d Version", prefix, key_id); - uint8_t version; - if(flipper_format_read_hex(file, furi_string_get_cstr(key), &version, 1)) { - MifareDesfireKeyVersion* kv = malloc(sizeof(MifareDesfireKeyVersion)); - memset(kv, 0, sizeof(MifareDesfireKeyVersion)); - kv->id = key_id; - kv->version = version; - *kv_head = kv; - kv_head = &kv->next; - } - } - parsed = true; - } while(false); - - furi_string_free(key); - return parsed; + return nfc_devices[protocol]->protocol_name; } -static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { - bool saved = false; - FuriString *prefix, *key; - prefix = - furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - key = furi_string_alloc(); - uint8_t* tmp = NULL; +const char* nfc_device_get_name(const NfcDevice* instance, NfcDeviceNameType name_type) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - do { - if(app->key_settings) { - if(!nfc_device_save_mifare_df_key_settings( - file, app->key_settings, furi_string_get_cstr(prefix))) - break; - } - if(!app->file_head) break; - uint32_t n_files = 0; - for(MifareDesfireFile* f = app->file_head; f; f = f->next) { - n_files++; - } - tmp = malloc(n_files); - int i = 0; - for(MifareDesfireFile* f = app->file_head; f; f = f->next) { - tmp[i++] = f->id; - } - furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; - bool saved_files = true; - for(MifareDesfireFile* f = app->file_head; f; f = f->next) { - saved_files = false; - furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; - furi_string_printf( - key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; - furi_string_printf( - key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex( - file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) - break; - uint16_t size = 0; - if(f->type == MifareDesfireFileTypeStandard || - f->type == MifareDesfireFileTypeBackup) { - size = f->settings.data.size; - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.data.size, 1)) - break; - } else if(f->type == MifareDesfireFileTypeValue) { - furi_string_printf( - key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_bool( - file, - furi_string_get_cstr(key), - &f->settings.value.limited_credit_enabled, - 1)) - break; - size = 4; - } else if( - f->type == MifareDesfireFileTypeLinearRecord || - f->type == MifareDesfireFileTypeCyclicRecord) { - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.record.size, 1)) - break; - furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.record.max, 1)) - break; - furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) - break; - size = f->settings.record.size * f->settings.record.cur; - } - if(f->contents) { - furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), f->contents, size)) - break; - } - saved_files = true; - } - if(!saved_files) { - break; - } - saved = true; - } while(false); - - free(tmp); - furi_string_free(prefix); - furi_string_free(key); - return saved; + return nfc_devices[instance->protocol]->get_name(instance->protocol_data, name_type); } -bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { - bool parsed = false; - FuriString *prefix, *key; - prefix = - furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - key = furi_string_alloc(); - uint8_t* tmp = NULL; - MifareDesfireFile* f = NULL; +const uint8_t* nfc_device_get_uid(const NfcDevice* instance, size_t* uid_len) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - do { - app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); - memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!nfc_device_load_mifare_df_key_settings( - file, app->key_settings, furi_string_get_cstr(prefix))) { - free(app->key_settings); - app->key_settings = NULL; - break; - } - furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); - uint32_t n_files; - if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &n_files)) break; - tmp = malloc(n_files); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; - MifareDesfireFile** file_head = &app->file_head; - bool parsed_files = true; - for(uint32_t i = 0; i < n_files; i++) { - parsed_files = false; - f = malloc(sizeof(MifareDesfireFile)); - memset(f, 0, sizeof(MifareDesfireFile)); - f->id = tmp[i]; - furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; - furi_string_printf( - key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; - furi_string_printf( - key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) - break; - if(f->type == MifareDesfireFileTypeStandard || - f->type == MifareDesfireFileTypeBackup) { - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.data.size, 1)) - break; - } else if(f->type == MifareDesfireFileTypeValue) { - furi_string_printf( - key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_bool( - file, - furi_string_get_cstr(key), - &f->settings.value.limited_credit_enabled, - 1)) - break; - } else if( - f->type == MifareDesfireFileTypeLinearRecord || - f->type == MifareDesfireFileTypeCyclicRecord) { - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.record.size, 1)) - break; - furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.record.max, 1)) - break; - furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) - break; - } - furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); - if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { - uint32_t size; - if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &size)) break; - f->contents = malloc(size); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), f->contents, size)) - break; - } - *file_head = f; - file_head = &f->next; - f = NULL; - parsed_files = true; - } - if(!parsed_files) { - break; - } - parsed = true; - } while(false); - - if(f) { - free(f->contents); - free(f); - } - free(tmp); - furi_string_free(prefix); - furi_string_free(key); - return parsed; + return nfc_devices[instance->protocol]->get_uid(instance->protocol_data, uid_len); } -static bool nfc_device_save_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - MifareDesfireData* data = &dev->dev_data.mf_df_data; - uint8_t* tmp = NULL; - - do { - if(!flipper_format_write_comment_cstr(file, "Mifare DESFire specific data")) break; - if(!flipper_format_write_hex( - file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) - break; - if(data->free_memory) { - if(!flipper_format_write_uint32(file, "PICC Free Memory", &data->free_memory->bytes, 1)) - break; - } - if(data->master_key_settings) { - if(!nfc_device_save_mifare_df_key_settings(file, data->master_key_settings, "PICC")) - break; - } - uint32_t n_apps = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - n_apps++; - } - if(!flipper_format_write_uint32(file, "Application Count", &n_apps, 1)) break; - if(n_apps) { - tmp = malloc(n_apps * 3); - int i = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - memcpy(tmp + i, app->id, 3); //-V769 - i += 3; - } - if(!flipper_format_write_hex(file, "Application IDs", tmp, n_apps * 3)) break; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - if(!nfc_device_save_mifare_df_app(file, app)) break; - } - } - saved = true; - } while(false); +bool nfc_device_set_uid(NfcDevice* instance, const uint8_t* uid, size_t uid_len) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - free(tmp); - return saved; + return nfc_devices[instance->protocol]->set_uid(instance->protocol_data, uid, uid_len); } -bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - MifareDesfireData* data = &dev->dev_data.mf_df_data; - memset(data, 0, sizeof(MifareDesfireData)); - uint8_t* tmp = NULL; +void nfc_device_set_data( + NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); - do { - if(!flipper_format_read_hex( - file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) - break; - if(flipper_format_key_exist(file, "PICC Free Memory")) { - data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); - memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory)); - if(!flipper_format_read_uint32( - file, "PICC Free Memory", &data->free_memory->bytes, 1)) { - free(data->free_memory); - break; - } - } - if(flipper_format_key_exist(file, "PICC Change Key ID")) { - data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); - memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!nfc_device_load_mifare_df_key_settings(file, data->master_key_settings, "PICC")) { - free(data->master_key_settings); - data->master_key_settings = NULL; - break; - } - } - uint32_t n_apps; - if(!flipper_format_read_uint32(file, "Application Count", &n_apps, 1)) break; - if(n_apps) { - tmp = malloc(n_apps * 3); - if(!flipper_format_read_hex(file, "Application IDs", tmp, n_apps * 3)) break; - bool parsed_apps = true; - MifareDesfireApplication** app_head = &data->app_head; - for(uint32_t i = 0; i < n_apps; i++) { - MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); - memset(app, 0, sizeof(MifareDesfireApplication)); - memcpy(app->id, &tmp[i * 3], 3); - if(!nfc_device_load_mifare_df_app(file, app)) { - free(app); - parsed_apps = false; - break; - } - *app_head = app; - app_head = &app->next; - } - if(!parsed_apps) { - // accept non-parsed apps, just log a warning: - FURI_LOG_W(TAG, "Non-parsed apps found!"); - } - } - parsed = true; - } while(false); + nfc_device_clear(instance); - free(tmp); - return parsed; -} + instance->protocol = protocol; + instance->protocol_data = nfc_devices[protocol]->alloc(); -static bool nfc_device_save_slix_data( - FlipperFormat* file, - NfcDevice* dev, - SlixTypeFeatures features, - const char* type) { - bool saved = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + nfc_devices[protocol]->copy(instance->protocol_data, protocol_data); +} - do { - char msg[64]; - snprintf(msg, sizeof(msg), "%s specific data", type); - if(!flipper_format_write_comment_cstr(file, msg)) break; - if(!flipper_format_write_comment_cstr( - file, "Passwords are optional. If password is omitted, any password is accepted")) - break; +void nfc_device_copy_data( + const NfcDevice* instance, + NfcProtocol protocol, + NfcDeviceData* protocol_data) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); + furi_assert(protocol_data); - if(features & SlixFeatureRead) { - if(data->flags & NfcVSlixDataFlagsHasKeyRead) { - if(!flipper_format_write_hex( - file, "Password Read", data->key_read, sizeof(data->key_read))) - break; - } - } - if(features & SlixFeatureWrite) { - if(data->flags & NfcVSlixDataFlagsHasKeyWrite) { - if(!flipper_format_write_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) - break; - } - } - if(features & SlixFeaturePrivacy) { - if(data->flags & NfcVSlixDataFlagsHasKeyPrivacy) { - if(!flipper_format_write_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - } - } - if(features & SlixFeatureDestroy) { - if(data->flags & NfcVSlixDataFlagsHasKeyDestroy) { - if(!flipper_format_write_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - } - } - if(features & SlixFeatureEas) { - if(data->flags & NfcVSlixDataFlagsHasKeyEas) { - if(!flipper_format_write_hex( - file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - } - } - if(features & SlixFeatureSignature) { - if(!flipper_format_write_comment_cstr( - file, - "This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.")) - break; - if(!flipper_format_write_hex( - file, "Signature", data->signature, sizeof(data->signature))) - break; - } - if(features & SlixFeaturePrivacy) { - bool privacy = (data->flags & NfcVSlixDataFlagsPrivacy) ? true : false; - if(!flipper_format_write_bool(file, "Privacy Mode", &privacy, 1)) break; - } - if(features & SlixFeatureProtection) { - if(!flipper_format_write_comment_cstr(file, "Protection pointer configuration")) break; - if(!flipper_format_write_hex(file, "Protection pointer", &data->pp_pointer, 1)) break; - if(!flipper_format_write_hex(file, "Protection condition", &data->pp_condition, 1)) - break; - } - saved = true; - } while(false); + if(instance->protocol != protocol) { + furi_crash(NFC_DEV_TYPE_ERROR); + } - return saved; + nfc_devices[protocol]->copy(protocol_data, instance->protocol_data); } -bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev, SlixTypeFeatures features) { - bool parsed = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - memset(data, 0, sizeof(NfcVSlixData)); +bool nfc_device_is_equal_data( + const NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); + furi_assert(protocol_data); - do { - data->flags = 0; - - if(features & SlixFeatureRead) { - if(flipper_format_key_exist(file, "Password Read")) { - if(!flipper_format_read_hex( - file, "Password Read", data->key_read, sizeof(data->key_read))) { - FURI_LOG_D(TAG, "Failed reading Password Read"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyRead; - } - } - if(features & SlixFeatureWrite) { - if(flipper_format_key_exist(file, "Password Write")) { - if(!flipper_format_read_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) { - FURI_LOG_D(TAG, "Failed reading Password Write"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyWrite; - } - } - if(features & SlixFeaturePrivacy) { - if(flipper_format_key_exist(file, "Password Privacy")) { - if(!flipper_format_read_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) { - FURI_LOG_D(TAG, "Failed reading Password Privacy"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyPrivacy; - } - } - if(features & SlixFeatureDestroy) { - if(flipper_format_key_exist(file, "Password Destroy")) { - if(!flipper_format_read_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) { - FURI_LOG_D(TAG, "Failed reading Password Destroy"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyDestroy; - } - } - if(features & SlixFeatureEas) { - if(flipper_format_key_exist(file, "Password EAS")) { - if(!flipper_format_read_hex( - file, "Password EAS", data->key_eas, sizeof(data->key_eas))) { - FURI_LOG_D(TAG, "Failed reading Password EAS"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyEas; - } - } - if(features & SlixFeatureSignature) { - if(!flipper_format_read_hex( - file, "Signature", data->signature, sizeof(data->signature))) { - FURI_LOG_D(TAG, "Failed reading Signature"); - break; - } - } - if(features & SlixFeaturePrivacy) { - bool privacy; - if(!flipper_format_read_bool(file, "Privacy Mode", &privacy, 1)) { - FURI_LOG_D(TAG, "Failed reading Privacy Mode"); - break; - } - if(privacy) { - data->flags |= NfcVSlixDataFlagsPrivacy; - } - } - if(features & SlixFeatureProtection) { - if(!flipper_format_read_hex(file, "Protection pointer", &(data->pp_pointer), 1)) { - FURI_LOG_D(TAG, "Failed reading Protection pointer"); - break; - } - if(!flipper_format_read_hex(file, "Protection condition", &(data->pp_condition), 1)) { - FURI_LOG_D(TAG, "Failed reading Protection condition"); - break; - } - } - parsed = true; - } while(false); - - return parsed; + return instance->protocol == protocol && + nfc_devices[protocol]->is_equal(instance->protocol_data, protocol_data); } -static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - NfcVData* data = &dev->dev_data.nfcv_data; +bool nfc_device_is_equal(const NfcDevice* instance, const NfcDevice* other) { + furi_assert(instance); + furi_assert(other); - do { - uint32_t temp_uint32 = 0; - uint8_t temp_uint8 = 0; - - if(!flipper_format_write_comment_cstr(file, "Data Storage Format Identifier")) break; - if(!flipper_format_write_hex(file, "DSFID", &(data->dsfid), 1)) break; - if(!flipper_format_write_comment_cstr(file, "Application Family Identifier")) break; - if(!flipper_format_write_hex(file, "AFI", &(data->afi), 1)) break; - if(!flipper_format_write_hex(file, "IC Reference", &(data->ic_ref), 1)) break; - temp_uint32 = data->block_num; - if(!flipper_format_write_comment_cstr(file, "Number of memory blocks, usually 0 to 256")) - break; - if(!flipper_format_write_uint32(file, "Block Count", &temp_uint32, 1)) break; - if(!flipper_format_write_comment_cstr(file, "Size of a single memory block, usually 4")) - break; - if(!flipper_format_write_hex(file, "Block Size", &(data->block_size), 1)) break; - if(!flipper_format_write_hex( - file, "Data Content", data->data, data->block_num * data->block_size)) - break; - if(!flipper_format_write_comment_cstr( - file, - "First byte: DSFID (0x01) / AFI (0x02) / EAS (0x04) / PPL (0x08) lock info, others: block lock info")) - break; - if(!flipper_format_write_hex( - file, "Security Status", data->security_status, 1 + data->block_num)) - break; - if(!flipper_format_write_comment_cstr( - file, - "Subtype of this card (0 = ISO15693, 1 = SLIX, 2 = SLIX-S, 3 = SLIX-L, 4 = SLIX2)")) - break; - temp_uint8 = (uint8_t)data->sub_type; - if(!flipper_format_write_hex(file, "Subtype", &temp_uint8, 1)) break; + return nfc_device_is_equal_data(instance, other->protocol, other->protocol_data); +} - switch(data->sub_type) { - case NfcVTypePlain: - if(!flipper_format_write_comment_cstr(file, "End of ISO15693 parameters")) break; - saved = true; - break; - case NfcVTypeSlix: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix, "SLIX"); - break; - case NfcVTypeSlixS: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixS, "SLIX-S"); - break; - case NfcVTypeSlixL: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixL, "SLIX-L"); - break; - case NfcVTypeSlix2: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix2, "SLIX2"); - break; - default: - break; - } - } while(false); +void nfc_device_set_loading_callback( + NfcDevice* instance, + NfcLoadingCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); - return saved; + instance->loading_callback = callback; + instance->loading_callback_context = context; } -bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - NfcVData* data = &dev->dev_data.nfcv_data; +bool nfc_device_save(NfcDevice* instance, const char* path) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); + furi_assert(path); - memset(data, 0x00, sizeof(NfcVData)); + bool saved = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, true); + } do { - uint32_t temp_uint32 = 0; - uint8_t temp_value = 0; + // Open file + if(!flipper_format_buffered_file_open_always(ff, path)) break; - if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) { - FURI_LOG_D(TAG, "Failed reading DSFID"); - break; - } - if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) { - FURI_LOG_D(TAG, "Failed reading AFI"); - break; - } - if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) { - FURI_LOG_D(TAG, "Failed reading IC Reference"); - break; - } - if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) { - FURI_LOG_D(TAG, "Failed reading Block Count"); - break; - } - data->block_num = temp_uint32; - if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) { - FURI_LOG_D(TAG, "Failed reading Block Size"); - break; - } - if(!flipper_format_read_hex( - file, "Data Content", data->data, data->block_num * data->block_size)) { - FURI_LOG_D(TAG, "Failed reading Data Content"); + // Write header + if(!flipper_format_write_header_cstr(ff, NFC_FILE_HEADER, NFC_CURRENT_FORMAT_VERSION)) break; - } - /* optional, as added later */ - if(flipper_format_key_exist(file, "Security Status")) { - if(!flipper_format_read_hex( - file, "Security Status", data->security_status, 1 + data->block_num)) { - FURI_LOG_D(TAG, "Failed reading Security Status"); - break; + // Write allowed device types + furi_string_printf(temp_str, "%s can be ", NFC_DEVICE_TYPE_KEY); + for(NfcProtocol protocol = 0; protocol < NfcProtocolNum; ++protocol) { + furi_string_cat(temp_str, nfc_devices[protocol]->protocol_name); + if(protocol < NfcProtocolNum - 1) { + furi_string_cat(temp_str, ", "); } } - if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) { - FURI_LOG_D(TAG, "Failed reading Subtype"); - break; - } - data->sub_type = temp_value; - switch(data->sub_type) { - case NfcVTypePlain: - parsed = true; - break; - case NfcVTypeSlix: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix); - break; - case NfcVTypeSlixS: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixS); - break; - case NfcVTypeSlixL: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixL); - break; - case NfcVTypeSlix2: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix2); - break; - default: + if(!flipper_format_write_comment(ff, temp_str)) break; + + // Write device type + if(!flipper_format_write_string_cstr( + ff, NFC_DEVICE_TYPE_KEY, nfc_devices[instance->protocol]->protocol_name)) break; - } - } while(false); - return parsed; -} + // Write UID + furi_string_printf(temp_str, "%s is common for all formats", NFC_DEVICE_UID_KEY); + if(!flipper_format_write_comment(ff, temp_str)) break; -static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - EmvData* data = &dev->dev_data.emv_data; - uint32_t data_temp = 0; + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(instance, &uid_len); + if(!flipper_format_write_hex(ff, NFC_DEVICE_UID_KEY, uid, uid_len)) break; + + // Write protocol-dependent data + if(!nfc_devices[instance->protocol]->save(instance->protocol_data, ff)) break; - do { - // Write Bank card specific data - if(!flipper_format_write_comment_cstr(file, "Bank card specific data")) break; - if(!flipper_format_write_hex(file, "AID", data->aid, data->aid_len)) break; - if(!flipper_format_write_string_cstr(file, "Name", data->name)) break; - if(!flipper_format_write_hex(file, "Number", data->number, data->number_len)) break; - if(data->exp_mon) { - uint8_t exp_data[2] = {data->exp_mon, data->exp_year}; - if(!flipper_format_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break; - } - if(data->country_code) { - data_temp = data->country_code; - if(!flipper_format_write_uint32(file, "Country code", &data_temp, 1)) break; - } - if(data->currency_code) { - data_temp = data->currency_code; - if(!flipper_format_write_uint32(file, "Currency code", &data_temp, 1)) break; - } saved = true; } while(false); - return saved; -} - -bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - EmvData* data = &dev->dev_data.emv_data; - memset(data, 0, sizeof(EmvData)); - uint32_t data_cnt = 0; - FuriString* temp_str; - temp_str = furi_string_alloc(); - - do { - // Load essential data - if(!flipper_format_get_value_count(file, "AID", &data_cnt)) break; - data->aid_len = data_cnt; - if(!flipper_format_read_hex(file, "AID", data->aid, data->aid_len)) break; - if(!flipper_format_read_string(file, "Name", temp_str)) break; - strlcpy(data->name, furi_string_get_cstr(temp_str), sizeof(data->name)); - if(!flipper_format_get_value_count(file, "Number", &data_cnt)) break; - data->number_len = data_cnt; - if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) break; - parsed = true; - // Load optional data - uint8_t exp_data[2] = {}; - if(flipper_format_read_hex(file, "Exp data", exp_data, 2)) { - data->exp_mon = exp_data[0]; - data->exp_year = exp_data[1]; - } - if(flipper_format_read_uint32(file, "Country code", &data_cnt, 1)) { - data->country_code = data_cnt; - } - if(flipper_format_read_uint32(file, "Currency code", &data_cnt, 1)) { - data->currency_code = data_cnt; - } - } while(false); + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, false); + } furi_string_free(temp_str); - return parsed; -} + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); -static void nfc_device_write_mifare_classic_block( - FuriString* block_str, - MfClassicData* data, - uint8_t block_num) { - furi_string_reset(block_str); - bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); - if(is_sec_trailer) { - uint8_t sector_num = mf_classic_get_sector_by_block(block_num); - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); - // Write key A - for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) { - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { - furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - // Write Access bytes - for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) { - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - // Write key B - for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) { - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { - furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - } else { - // Write data block - for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - } - furi_string_trim(block_str); + return saved; } -static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - MfClassicData* data = &dev->dev_data.mf_classic_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); - uint16_t blocks = 0; +static bool nfc_device_load_uid( + FlipperFormat* ff, + uint8_t* uid, + uint32_t* uid_len, + const uint32_t uid_maxlen) { + bool loaded = false; - // Save Mifare Classic specific data do { - if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; - - if(data->type == MfClassicTypeMini) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; - blocks = 20; - } else if(data->type == MfClassicType1k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; - blocks = 64; - } else if(data->type == MfClassicType4k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; - blocks = 256; - } - if(!flipper_format_write_uint32( - file, "Data format version", &nfc_mifare_classic_data_format_version, 1)) - break; - if(!flipper_format_write_comment_cstr( - file, "Mifare Classic blocks, \'??\' means unknown data")) - break; - bool block_saved = true; - FuriString* block_str; - block_str = furi_string_alloc(); - for(size_t i = 0; i < blocks; i++) { - furi_string_printf(temp_str, "Block %d", i); - nfc_device_write_mifare_classic_block(block_str, data, i); - if(!flipper_format_write_string(file, furi_string_get_cstr(temp_str), block_str)) { - block_saved = false; - break; - } - } - furi_string_free(block_str); - if(!block_saved) break; - saved = true; + uint32_t uid_len_current; + if(!flipper_format_get_value_count(ff, NFC_DEVICE_UID_KEY, &uid_len_current)) break; + if(uid_len_current > uid_maxlen) break; + if(!flipper_format_read_hex(ff, NFC_DEVICE_UID_KEY, uid, uid_len_current)) break; + + *uid_len = uid_len_current; + loaded = true; } while(false); - furi_string_free(temp_str); - return saved; + return loaded; } -static void nfc_device_load_mifare_classic_block( - FuriString* block_str, - MfClassicData* data, - uint8_t block_num) { - furi_string_trim(block_str); - MfClassicBlock block_tmp = {}; - bool is_sector_trailer = mf_classic_is_sector_trailer(block_num); - uint8_t sector_num = mf_classic_get_sector_by_block(block_num); - uint16_t block_unknown_bytes_mask = 0; - - furi_string_trim(block_str); - for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { - char hi = furi_string_get_char(block_str, 3 * i); - char low = furi_string_get_char(block_str, 3 * i + 1); - uint8_t byte = 0; - if(hex_char_to_uint8(hi, low, &byte)) { - block_tmp.value[i] = byte; - } else { - FURI_BIT_SET(block_unknown_bytes_mask, i); - } - } +static bool nfc_device_load_unified(NfcDevice* instance, FlipperFormat* ff, uint32_t version) { + bool loaded = false; - if(block_unknown_bytes_mask == 0xffff) { - // All data is unknown, exit - return; - } - - if(is_sector_trailer) { - MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp; - // Load Key A - // Key A mask 0b0000000000111111 = 0x003f - if((block_unknown_bytes_mask & 0x003f) == 0) { - uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a, sizeof(sec_tr_tmp->key_a)); - mf_classic_set_key_found(data, sector_num, MfClassicKeyA, key); - } - // Load Access Bits - // Access bits mask 0b0000001111000000 = 0x03c0 - if((block_unknown_bytes_mask & 0x03c0) == 0) { - mf_classic_set_block_read(data, block_num, &block_tmp); - } - // Load Key B - // Key B mask 0b1111110000000000 = 0xfc00 - if((block_unknown_bytes_mask & 0xfc00) == 0) { - uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b, sizeof(sec_tr_tmp->key_b)); - mf_classic_set_key_found(data, sector_num, MfClassicKeyB, key); - } - } else { - if(block_unknown_bytes_mask == 0) { - mf_classic_set_block_read(data, block_num, &block_tmp); - } - } -} - -static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - MfClassicData* data = &dev->dev_data.mf_classic_data; - FuriString* temp_str; - uint32_t data_format_version = 0; - temp_str = furi_string_alloc(); - uint16_t data_blocks = 0; - memset(data, 0, sizeof(MfClassicData)); + FuriString* temp_str = furi_string_alloc(); do { - // Read Mifare Classic type - if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!furi_string_cmp(temp_str, "MINI")) { - data->type = MfClassicTypeMini; - data_blocks = 20; - } else if(!furi_string_cmp(temp_str, "1K")) { - data->type = MfClassicType1k; - data_blocks = 64; - } else if(!furi_string_cmp(temp_str, "4K")) { - data->type = MfClassicType4k; - data_blocks = 256; - } else { - break; - } - - bool old_format = false; - // Read Mifare Classic format version - if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { - // Load unread sectors with zero keys access for backward compatibility - if(!flipper_format_rewind(file)) break; - old_format = true; - } else { - if(data_format_version < nfc_mifare_classic_data_format_version) { - old_format = true; - } - } + // Read Nfc device type + if(!flipper_format_read_string(ff, NFC_DEVICE_TYPE_KEY, temp_str)) break; - // Read Mifare Classic blocks - bool block_read = true; - FuriString* block_str; - block_str = furi_string_alloc(); - for(size_t i = 0; i < data_blocks; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { - block_read = false; + // Detect protocol + NfcProtocol protocol; + for(protocol = 0; protocol < NfcProtocolNum; ++protocol) { + if(furi_string_equal(temp_str, nfc_devices[protocol]->protocol_name)) { break; } - nfc_device_load_mifare_classic_block(block_str, data, i); - } - furi_string_free(block_str); - if(!block_read) break; - - // Set keys and blocks as unknown for backward compatibility - if(old_format) { - data->key_a_mask = 0ULL; - data->key_b_mask = 0ULL; - memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); } - parsed = true; - } while(false); - - furi_string_free(temp_str); - return parsed; -} - -static void nfc_device_get_key_cache_file_path(NfcDevice* dev, FuriString* file_path) { - uint8_t* uid = dev->dev_data.nfc_data.uid; - uint8_t uid_len = dev->dev_data.nfc_data.uid_len; - furi_string_set(file_path, NFC_DEVICE_KEYS_FOLDER "/"); - for(size_t i = 0; i < uid_len; i++) { - furi_string_cat_printf(file_path, "%02X", uid[i]); - } - furi_string_cat_printf(file_path, NFC_DEVICE_KEYS_EXTENSION); -} + if(protocol == NfcProtocolNum) break; -static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) { - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - MfClassicData* data = &dev->dev_data.mf_classic_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); + nfc_device_clear(instance); - nfc_device_get_key_cache_file_path(dev, temp_str); - bool save_success = false; - do { - if(!storage_simply_mkdir(dev->storage, NFC_DEVICE_KEYS_FOLDER)) break; - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(temp_str))) break; - if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; - if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version)) - break; - if(data->type == MfClassicTypeMini) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; - } else if(data->type == MfClassicType1k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; - } else if(data->type == MfClassicType4k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; - } - if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; - if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; - uint8_t sector_num = mf_classic_get_total_sectors_num(data->type); - bool key_save_success = true; - for(size_t i = 0; (i < sector_num) && (key_save_success); i++) { - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); - if(FURI_BIT(data->key_a_mask, i)) { - furi_string_printf(temp_str, "Key A sector %d", i); - key_save_success = flipper_format_write_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); - } - if(!key_save_success) break; - if(FURI_BIT(data->key_b_mask, i)) { - furi_string_printf(temp_str, "Key B sector %d", i); - key_save_success = flipper_format_write_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); - } - } - save_success = key_save_success; - } while(false); + instance->protocol = protocol; + instance->protocol_data = nfc_devices[protocol]->alloc(); - flipper_format_free(file); - furi_string_free(temp_str); - return save_success; -} + // Load UID + uint8_t uid[NFC_DEVICE_UID_MAX_LEN]; + uint32_t uid_len; -bool nfc_device_load_key_cache(NfcDevice* dev) { - furi_assert(dev); - FuriString* temp_str; - temp_str = furi_string_alloc(); + if(!nfc_device_load_uid(ff, uid, &uid_len, NFC_DEVICE_UID_MAX_LEN)) break; + if(!nfc_device_set_uid(instance, uid, uid_len)) break; - MfClassicData* data = &dev->dev_data.mf_classic_data; - nfc_device_get_key_cache_file_path(dev, temp_str); - FlipperFormat* file = flipper_format_file_alloc(dev->storage); + // Load data + if(!nfc_devices[protocol]->load(instance->protocol_data, ff, version)) break; - bool load_success = false; - do { - if(storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) != FSE_OK) - break; - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; - uint32_t version = 0; - if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, nfc_keys_file_header)) break; - if(version != nfc_keys_file_version) break; - if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!furi_string_cmp(temp_str, "MINI")) { - data->type = MfClassicTypeMini; - } else if(!furi_string_cmp(temp_str, "1K")) { - data->type = MfClassicType1k; - } else if(!furi_string_cmp(temp_str, "4K")) { - data->type = MfClassicType4k; - } else { - break; - } - if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; - if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; - uint8_t sectors = mf_classic_get_total_sectors_num(data->type); - bool key_read_success = true; - for(size_t i = 0; (i < sectors) && (key_read_success); i++) { - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); - if(FURI_BIT(data->key_a_mask, i)) { - furi_string_printf(temp_str, "Key A sector %d", i); - key_read_success = flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); - } - if(!key_read_success) break; - if(FURI_BIT(data->key_b_mask, i)) { - furi_string_printf(temp_str, "Key B sector %d", i); - key_read_success = flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); - } - } - load_success = key_read_success; + loaded = true; } while(false); - furi_string_free(temp_str); - flipper_format_free(file); + if(!loaded) { + nfc_device_clear(instance); + } - return load_success; + furi_string_free(temp_str); + return loaded; } -void nfc_device_set_name(NfcDevice* dev, const char* name) { - furi_assert(dev); +static bool nfc_device_load_legacy(NfcDevice* instance, FlipperFormat* ff, uint32_t version) { + bool loaded = false; - strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); -} + FuriString* temp_str = furi_string_alloc(); -static void nfc_device_get_path_without_ext(FuriString* orig_path, FuriString* shadow_path) { - // TODO: this won't work if there is ".nfc" anywhere in the path other than - // at the end - size_t ext_start = furi_string_search(orig_path, NFC_APP_FILENAME_EXTENSION); - furi_string_set_n(shadow_path, orig_path, 0, ext_start); -} + do { + // Read Nfc device type + if(!flipper_format_read_string(ff, NFC_DEVICE_TYPE_KEY, temp_str)) break; -static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow_path) { - nfc_device_get_path_without_ext(orig_path, shadow_path); - furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); -} + nfc_device_clear(instance); -static void nfc_device_get_folder_from_path(FuriString* path, FuriString* folder) { - size_t last_slash = furi_string_search_rchar(path, '/'); - if(last_slash == FURI_STRING_FAILURE) { - // No slashes in the path, treat the whole path as a folder - furi_string_set(folder, path); - } else { - furi_string_set_n(folder, path, 0, last_slash); - } -} + // Detect protocol + for(NfcProtocol protocol = 0; protocol < NfcProtocolNum; protocol++) { + instance->protocol = protocol; + instance->protocol_data = nfc_devices[protocol]->alloc(); -bool nfc_device_save(NfcDevice* dev, const char* dev_name) { - furi_assert(dev); + // Verify protocol + if(nfc_devices[protocol]->verify(instance->protocol_data, temp_str)) { + uint8_t uid[NFC_DEVICE_UID_MAX_LEN]; + uint32_t uid_len; - bool saved = false; - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - FuriHalNfcDevData* data = &dev->dev_data.nfc_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); + // Load data + loaded = nfc_device_load_uid(ff, uid, &uid_len, NFC_DEVICE_UID_MAX_LEN) && + nfc_device_set_uid(instance, uid, uid_len) && + nfc_devices[protocol]->load(instance->protocol_data, ff, version); + break; + } - do { - // Create directory if necessary - FuriString* folder = furi_string_alloc(); - // Get folder from filename (filename is in the form of "folder/filename.nfc", so the folder is "folder/") - furi_string_set(temp_str, dev_name); - // Get folder from filename - nfc_device_get_folder_from_path(temp_str, folder); - FURI_LOG_I("Nfc", "Saving to folder %s", furi_string_get_cstr(folder)); - if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(folder))) { - FURI_LOG_E("Nfc", "Failed to create folder %s", furi_string_get_cstr(folder)); - break; - } - furi_string_free(folder); - // First remove nfc device file if it was saved - // Open file - if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; - // Write header - if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; - // Write nfc device type - if(!flipper_format_write_comment_cstr( - file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693")) - break; - nfc_device_prepare_format_string(dev, temp_str); - if(!flipper_format_write_string(file, "Device type", temp_str)) break; - // Write UID - if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break; - if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; - - if(dev->format != NfcDeviceSaveFormatNfcV) { - // Write ATQA, SAK - if(!flipper_format_write_comment_cstr(file, "ISO14443 specific fields")) break; - // Save ATQA in MSB order for correct companion apps display - uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; - if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; - if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + nfc_device_clear(instance); } - // Save more data if necessary - if(dev->format == NfcDeviceSaveFormatMifareUl) { - if(!nfc_device_save_mifare_ul_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { - if(!nfc_device_save_mifare_df_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatNfcV) { - if(!nfc_device_save_nfcv_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatBankCard) { - if(!nfc_device_save_bank_card_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { - // Save data - if(!nfc_device_save_mifare_classic_data(file, dev)) break; - // Save keys cache - if(!nfc_device_save_mifare_classic_keys(dev)) break; - } - saved = true; - } while(0); + } while(false); - if(!saved) { //-V547 - dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); - } furi_string_free(temp_str); - flipper_format_free(file); - return saved; + return loaded; } -bool nfc_device_save_shadow(NfcDevice* dev, const char* path) { - dev->shadow_file_exist = true; - // Replace extension from .nfc to .shd if necessary - FuriString* orig_path = furi_string_alloc(); - furi_string_set_str(orig_path, path); - FuriString* shadow_path = furi_string_alloc(); - nfc_device_get_shadow_path(orig_path, shadow_path); +bool nfc_device_load(NfcDevice* instance, const char* path) { + furi_assert(instance); + furi_assert(path); - bool file_saved = nfc_device_save(dev, furi_string_get_cstr(shadow_path)); - furi_string_free(orig_path); - furi_string_free(shadow_path); + bool loaded = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - return file_saved; -} - -static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dialog) { - bool parsed = false; - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - FuriHalNfcDevData* data = &dev->dev_data.nfc_data; - uint32_t data_cnt = 0; FuriString* temp_str; temp_str = furi_string_alloc(); - bool deprecated_version = false; - - // Version 2 of file format had ATQA bytes swapped - uint32_t version_with_lsb_atqa = 2; - if(dev->loading_cb) { - dev->loading_cb(dev->loading_cb_ctx, true); + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, true); } do { - // Check existence of shadow file - nfc_device_get_shadow_path(path, temp_str); - dev->shadow_file_exist = - storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK; - // Open shadow file if it exists. If not - open original - if(dev->shadow_file_exist) { - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; - } else { - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; - } + if(!flipper_format_buffered_file_open_existing(ff, path)) break; + // Read and verify file header uint32_t version = 0; - if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, nfc_file_header)) break; - if(version != nfc_file_version) { - if(version < version_with_lsb_atqa) { - deprecated_version = true; - break; - } - } - // Read Nfc device type - if(!flipper_format_read_string(file, "Device type", temp_str)) break; - if(!nfc_device_parse_format_string(dev, temp_str)) break; - // Read and parse UID, ATQA and SAK - if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break; - if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break; - data->uid_len = data_cnt; - if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; - if(dev->format != NfcDeviceSaveFormatNfcV) { - if(version == version_with_lsb_atqa) { - if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; - } else { - uint8_t atqa[2] = {}; - if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; - data->atqa[0] = atqa[1]; - data->atqa[1] = atqa[0]; - } - if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; - } - // Load CUID - uint8_t* cuid_start = data->uid; - if(data->uid_len == 7) { - cuid_start = &data->uid[3]; - } - data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | - (cuid_start[3]); - // Parse other data - if(dev->format == NfcDeviceSaveFormatMifareUl) { - if(!nfc_device_load_mifare_ul_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { - if(!nfc_device_load_mifare_classic_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { - if(!nfc_device_load_mifare_df_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatNfcV) { - if(!nfc_device_load_nfcv_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatBankCard) { - if(!nfc_device_load_bank_card_data(file, dev)) break; - } - parsed = true; - } while(false); - - if(dev->loading_cb) { - dev->loading_cb(dev->loading_cb_ctx, false); - } - - if((!parsed) && (show_dialog)) { - if(deprecated_version) { - dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); - } else { - dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); - } - } + if(!flipper_format_read_header(ff, temp_str, &version)) break; - furi_string_free(temp_str); - flipper_format_free(file); - return parsed; -} - -bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { - furi_assert(dev); - furi_assert(file_path); - - // Load device data - furi_string_set(dev->load_path, file_path); - bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog); - if(dev_load) { - // Set device name - FuriString* filename; - filename = furi_string_alloc(); - path_extract_filename_no_ext(file_path, filename); - nfc_device_set_name(dev, furi_string_get_cstr(filename)); - furi_string_free(filename); - } - - return dev_load; -} - -bool nfc_file_select(NfcDevice* dev) { - furi_assert(dev); - const char* folder = furi_string_get_cstr(dev->folder); - - // Input events and views are managed by file_browser - - const DialogsFileBrowserOptions browser_options = { - .extension = NFC_APP_FILENAME_EXTENSION, - .skip_assets = true, - .hide_dot_files = true, - .icon = &I_Nfc_10px, - .hide_ext = true, - .item_loader_callback = NULL, - .item_loader_context = NULL, - .base_path = folder, - }; - - bool res = - dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options); - - if(res) { - FuriString* filename; - filename = furi_string_alloc(); - path_extract_filename(dev->load_path, filename, true); - strncpy(dev->dev_name, furi_string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); - res = nfc_device_load_data(dev, dev->load_path, true); - if(res) { - nfc_device_set_name(dev, dev->dev_name); - } - furi_string_free(filename); - } - - return res; -} - -void nfc_device_data_clear(NfcDeviceData* dev_data) { - if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) { - mf_df_clear(&dev_data->mf_df_data); - } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { - memset(&dev_data->mf_classic_data, 0, sizeof(MfClassicData)); - } else if(dev_data->protocol == NfcDeviceProtocolMifareUl) { - mf_ul_reset(&dev_data->mf_ul_data); - } else if(dev_data->protocol == NfcDeviceProtocolEMV) { - memset(&dev_data->emv_data, 0, sizeof(EmvData)); - } + if(furi_string_cmp_str(temp_str, NFC_FILE_HEADER)) break; + if(version < NFC_MINIMUM_SUPPORTED_FORMAT_VERSION) break; - furi_string_reset(dev_data->parsed_data); + // Select loading method + loaded = (version < NFC_UNIFIED_FORMAT_VERSION) ? + nfc_device_load_legacy(instance, ff, version) : + nfc_device_load_unified(instance, ff, version); - memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData)); - dev_data->protocol = NfcDeviceProtocolUnknown; - furi_string_reset(dev_data->parsed_data); -} - -void nfc_device_clear(NfcDevice* dev) { - furi_assert(dev); - - nfc_device_set_name(dev, ""); - nfc_device_data_clear(&dev->dev_data); - dev->format = NfcDeviceSaveFormatUid; - furi_string_reset(dev->load_path); -} - -bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { - furi_assert(dev); - - bool deleted = false; - FuriString* file_path; - file_path = furi_string_alloc(); - - do { - // Delete original file - if(use_load_path && !furi_string_empty(dev->load_path)) { - furi_string_set(file_path, dev->load_path); - } else { - furi_string_printf( - file_path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_FILENAME_EXTENSION); - } - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; - // Delete shadow file if it exists - if(dev->shadow_file_exist) { - if(use_load_path && !furi_string_empty(dev->load_path)) { - nfc_device_get_shadow_path(dev->load_path, file_path); - } else { - furi_string_printf( - file_path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_SHADOW_EXTENSION); - } - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; - } - deleted = true; - } while(0); + } while(false); - if(!deleted) { - dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, false); } - furi_string_free(file_path); - return deleted; -} - -bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { - furi_assert(dev); - furi_assert(dev->shadow_file_exist); - - bool restored = false; - FuriString* path; - - path = furi_string_alloc(); - - do { - if(use_load_path && !furi_string_empty(dev->load_path)) { - nfc_device_get_shadow_path(dev->load_path, path); - } else { - furi_string_printf( - path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_SHADOW_EXTENSION); - } - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(path))) break; - dev->shadow_file_exist = false; - if(use_load_path && !furi_string_empty(dev->load_path)) { - furi_string_set(path, dev->load_path); - } else { - furi_string_printf( - path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_FILENAME_EXTENSION); - } - if(!nfc_device_load_data(dev, path, true)) break; - restored = true; - } while(0); - - furi_string_free(path); - return restored; -} - -void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context) { - furi_assert(dev); + furi_string_free(temp_str); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); - dev->loading_cb = callback; - dev->loading_cb_ctx = context; + return loaded; } diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 76de0b61c6..6636e7c768 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -1,126 +1,265 @@ +/** + * @file nfc_device.h + * @brief Abstract interface for managing NFC device data. + * + * Under the hood, it makes use of the protocol-specific functions that each one of them provides + * and abstracts it with a protocol-independent API. + * + * It does not perform any signal processing, but merely serves as a container with some handy + * operations such as loading and saving from and to a file. + */ #pragma once +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "protocols/nfc_device_base.h" +#include "protocols/nfc_protocol.h" #ifdef __cplusplus extern "C" { #endif -#define NFC_DEV_NAME_MAX_LEN 22 -#define NFC_READER_DATA_MAX_SIZE 64 -#define NFC_DICT_KEY_BATCH_SIZE 10 - -#define NFC_APP_FILENAME_PREFIX "NFC" -#define NFC_APP_FILENAME_EXTENSION ".nfc" -#define NFC_APP_SHADOW_EXTENSION ".shd" +/** + * @brief NfcDevice opaque type definition. + */ +typedef struct NfcDevice NfcDevice; +/** + * @brief Loading callback function signature. + * + * A function with such signature can be set as a callback to indicate + * the completion (or a failure) of nfc_device_load() and nfc_device_save() functions. + * + * This facility is commonly used to control GUI elements, such as progress dialogs. + * + * @param[in] context user-defined context that was passed in nfc_device_set_loading_callback(). + * @param[in] state true if the data was loaded successfully, false otherwise. + */ typedef void (*NfcLoadingCallback)(void* context, bool state); -typedef enum { - NfcDeviceProtocolUnknown, - NfcDeviceProtocolEMV, - NfcDeviceProtocolMifareUl, - NfcDeviceProtocolMifareClassic, - NfcDeviceProtocolMifareDesfire, - NfcDeviceProtocolNfcV -} NfcProtocol; - -typedef enum { - NfcDeviceSaveFormatUid, - NfcDeviceSaveFormatBankCard, - NfcDeviceSaveFormatMifareUl, - NfcDeviceSaveFormatMifareClassic, - NfcDeviceSaveFormatMifareDesfire, - NfcDeviceSaveFormatNfcV, -} NfcDeviceSaveFormat; - -typedef struct { - uint8_t data[NFC_READER_DATA_MAX_SIZE]; - uint16_t size; -} NfcReaderRequestData; - -typedef struct { - MfClassicDict* dict; - uint8_t current_sector; -} NfcMfClassicDictAttackData; - -typedef enum { - NfcReadModeAuto, - NfcReadModeMfClassic, - NfcReadModeMfUltralight, - NfcReadModeMfDesfire, - NfcReadModeNFCA, -} NfcReadMode; - -typedef struct { - FuriHalNfcDevData nfc_data; - NfcProtocol protocol; - NfcReadMode read_mode; - union { - NfcReaderRequestData reader_data; - NfcMfClassicDictAttackData mf_classic_dict_attack_data; - MfUltralightAuth mf_ul_auth; - }; - union { - EmvData emv_data; - MfUltralightData mf_ul_data; - MfClassicData mf_classic_data; - MifareDesfireData mf_df_data; - NfcVData nfcv_data; - }; - FuriString* parsed_data; -} NfcDeviceData; - -typedef struct { - Storage* storage; - DialogsApp* dialogs; - NfcDeviceData dev_data; - char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; - FuriString* load_path; - FuriString* folder; - NfcDeviceSaveFormat format; - bool shadow_file_exist; - - NfcLoadingCallback loading_cb; - void* loading_cb_ctx; -} NfcDevice; - +/** + * @brief Allocate an NfcDevice instance. + * + * A newly created instance does not hold any data and thus is considered invalid. The most common + * use case would be to set its data by calling nfc_device_set_data() right afterwards. + * + * @returns pointer to the allocated instance. + */ NfcDevice* nfc_device_alloc(); -void nfc_device_free(NfcDevice* nfc_dev); +/** + * @brief Delete an NfcDevice instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_device_free(NfcDevice* instance); + +/** + * @brief Clear an NfcDevice instance. + * + * All data contained in the instance will be deleted and the instance itself will become invalid + * as if it was just allocated. + * + * @param[in,out] instance pointer to the instance to be cleared. + */ +void nfc_device_clear(NfcDevice* instance); + +/** + * @brief Reset an NfcDevice instance. + * + * The data contained in the instance will be reset according to the protocol-defined procedure. + * Unlike the nfc_device_clear() function, the instance will remain valid. + * + * @param[in,out] instance pointer to the instance to be reset. + */ +void nfc_device_reset(NfcDevice* instance); + +/** + * @brief Get the protocol identifier from an NfcDevice instance. + * + * If the instance is invalid, the return value will be NfcProtocolInvalid. + * + * @param[in] instance pointer to the instance to be queried. + * @returns protocol identifier contained in the instance. + */ +NfcProtocol nfc_device_get_protocol(const NfcDevice* instance); + +/** + * @brief Get the protocol-specific data from an NfcDevice instance. + * + * The protocol parameter's behaviour is a bit tricky. The function will check + * whether there is such a protocol somewhere in the protocol hierarchy and return + * the data exactly from that level. + * + * Example: Call nfc_device_get_data() on an instance with Mf DESFire protocol. + * The protocol hierarchy will look like the following: + * + * `Mf DESFire --> ISO14443-4A --> ISO14443-3A` + * + * Thus, the following values of the protocol parameter are valid: + * + * * NfcProtocolIso14443_3a + * * NfcProtocolIso14443_4a + * * NfcProtocolMfDesfire + * + * and passing them to the call would result in the respective data being returned. + * + * However, supplying a protocol identifier which is not in the hierarchy will + * result in a crash. This is to improve type safety. + * + * @param instance pointer to the instance to be queried + * @param protocol protocol identifier of the data to be retrieved. + * @returns pointer to the instance's data. + */ +const NfcDeviceData* nfc_device_get_data(const NfcDevice* instance, NfcProtocol protocol); + +/** + * @brief Get the protocol name by its identifier. + * + * This function does not require an instance as its return result depends only + * the protocol identifier. + * + * @param[in] protocol numeric identifier of the protocol in question. + * @returns pointer to a statically allocated string containing the protocol name. + */ +const char* nfc_device_get_protocol_name(NfcProtocol protocol); -void nfc_device_set_name(NfcDevice* dev, const char* name); +/** + * @brief Get the name of an NfcDevice instance. + * + * The return value may change depending on the instance's internal state and the name_type parameter. + * + * @param[in] instance pointer to the instance to be queried. + * @param[in] name_type type of the name to be displayed. + * @returns pointer to a statically allocated string containing the device name. + */ +const char* nfc_device_get_name(const NfcDevice* instance, NfcDeviceNameType name_type); -bool nfc_device_save(NfcDevice* dev, const char* dev_name); +/** + * @brief Get the unique identifier (UID) of an NfcDevice instance. + * + * The UID length is protocol-dependent. Additionally, a particular protocol might support + * several UID lengths. + * + * @param[in] instance pointer to the instance to be queried. + * @param[out] uid_len pointer to the variable to contain the UID length. + * @returns pointer to the byte array containing the instance's UID. + */ +const uint8_t* nfc_device_get_uid(const NfcDevice* instance, size_t* uid_len); -bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name); +/** + * @brief Set the unique identifier (UID) of an NfcDevice instance. + * + * The UID length must be supported by the instance's protocol. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] uid pointer to the byte array containing the new UID. + * @param[in] uid_len length of the UID. + * @return true if the UID was valid and set, false otherwise. + */ +bool nfc_device_set_uid(NfcDevice* instance, const uint8_t* uid, size_t uid_len); -bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog); +/** + * @brief Set the data and protocol of an NfcDevice instance. + * + * Any data previously contained in the instance will be deleted. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] protocol numeric identifier of the data's protocol. + * @param[in] protocol_data pointer to the protocol-specific data. + */ +void nfc_device_set_data( + NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data); -bool nfc_device_load_key_cache(NfcDevice* dev); +/** + * @brief Copy (export) the data contained in an NfcDevice instance to an outside NfcDeviceData instance. + * + * This function does the inverse of nfc_device_set_data(). -bool nfc_file_select(NfcDevice* dev); + * The protocol identifier passed as the protocol parameter MUST match the one + * stored in the instance, otherwise a crash will occur. + * This is to improve type safety. + * + * @param[in] instance pointer to the instance to be copied from. + * @param[in] protocol numeric identifier of the instance's protocol. + * @param[out] protocol_data pointer to the destination data. + */ +void nfc_device_copy_data( + const NfcDevice* instance, + NfcProtocol protocol, + NfcDeviceData* protocol_data); -void nfc_device_data_clear(NfcDeviceData* dev); +/** + * @brief Check whether an NfcDevice instance holds certain data. + * + * This function's behaviour is similar to nfc_device_is_equal(), with the difference + * that it takes NfcProtocol and NfcDeviceData* instead of the second NfcDevice*. + * + * The following code snippets [1] and [2] are equivalent: + * + * [1] + * ```c + * bool is_equal = nfc_device_is_equal(device1, device2); + * ``` + * [2] + * ```c + * NfcProtocol protocol = nfc_device_get_protocol(device2); + * const NfcDeviceData* data = nfc_device_get_data(device2, protocol); + * bool is_equal = nfc_device_is_equal_data(device1, protocol, data); + * ``` + * + * @param[in] instance pointer to the instance to be compared. + * @param[in] protocol protocol identifier of the data to be compared. + * @param[in] protocol_data pointer to the NFC device data to be compared. + * @returns true if the instance is of the right type and the data matches, false otherwise. + */ +bool nfc_device_is_equal_data( + const NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data); -void nfc_device_clear(NfcDevice* dev); +/** + * @brief Compare two NfcDevice instances to determine whether they are equal. + * + * @param[in] instance pointer to the first instance to be compared. + * @param[in] other pointer to the second instance to be compared. + * @returns true if both instances are considered equal, false otherwise. + */ +bool nfc_device_is_equal(const NfcDevice* instance, const NfcDevice* other); -bool nfc_device_delete(NfcDevice* dev, bool use_load_path); +/** + * @brief Set the loading callback function. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] callback pointer to a function to be called when the load operation completes. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_device_set_loading_callback( + NfcDevice* instance, + NfcLoadingCallback callback, + void* context); -bool nfc_device_restore(NfcDevice* dev, bool use_load_path); +/** + * @brief Save NFC device data form an NfcDevice instance to a file. + * + * @param[in] instance pointer to the instance to be saved. + * @param[in] path pointer to a character string with a full file path. + * @returns true if the data was successfully saved, false otherwise. + */ +bool nfc_device_save(NfcDevice* instance, const char* path); -void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context); +/** + * @brief Load NFC device data to an NfcDevice instance from a file. + * + * @param[in,out] instance pointer to the instance to be loaded into. + * @param[in] path pointer to a character string with a full file path. + * @returns true if the data was successfully loaded, false otherwise. + */ +bool nfc_device_load(NfcDevice* instance, const char* path); #ifdef __cplusplus } diff --git a/lib/nfc/nfc_device_i.c b/lib/nfc/nfc_device_i.c new file mode 100644 index 0000000000..e98b04251e --- /dev/null +++ b/lib/nfc/nfc_device_i.c @@ -0,0 +1,37 @@ +#include "nfc_device_i.h" +#include "protocols/nfc_device_defs.h" + +#include + +static NfcDeviceData* + nfc_device_search_base_protocol_data(const NfcDevice* instance, NfcProtocol protocol) { + NfcProtocol protocol_tmp = instance->protocol; + NfcDeviceData* dev_data_tmp = instance->protocol_data; + + while(true) { + dev_data_tmp = nfc_devices[protocol_tmp]->get_base_data(dev_data_tmp); + protocol_tmp = nfc_protocol_get_parent(protocol_tmp); + if(protocol_tmp == protocol) { + break; + } + } + + return dev_data_tmp; +} + +NfcDeviceData* nfc_device_get_data_ptr(const NfcDevice* instance, NfcProtocol protocol) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); + + NfcDeviceData* dev_data = NULL; + + if(instance->protocol == protocol) { + dev_data = instance->protocol_data; + } else if(nfc_protocol_has_parent(instance->protocol, protocol)) { + dev_data = nfc_device_search_base_protocol_data(instance, protocol); + } else { + furi_crash("Incorrect protocol"); + } + + return dev_data; +} diff --git a/lib/nfc/nfc_device_i.h b/lib/nfc/nfc_device_i.h new file mode 100644 index 0000000000..1a9017d03f --- /dev/null +++ b/lib/nfc/nfc_device_i.h @@ -0,0 +1,46 @@ +/** + * @file nfc_device_i.h + * @brief NfcDevice private types and definitions. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#pragma once + +#include "nfc_device.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcDevice structure definition. + */ +struct NfcDevice { + NfcProtocol protocol; /**< Numeric identifier of the data's protocol*/ + NfcDeviceData* protocol_data; /**< Pointer to the NFC device data. */ + + NfcLoadingCallback + loading_callback; /**< Pointer to the function to be called upon loading completion. */ + void* loading_callback_context; /**< Pointer to the context to be passed to the loading callback. */ +}; + +/** + * @brief Get the mutable (non-const) data from an NfcDevice instance. + * + * The behaviour is the same as with nfc_device_get_data(), but the + * return pointer is non-const, allowing for changing data it is pointing to. + * + * @see nfc_device.h + * + * Under the hood, nfc_device_get_data() calls this and then adds const-ness to the return value. + * + * @param instance pointer to the instance to be queried + * @param protocol protocol identifier of the data to be retrieved. + * @returns pointer to the instance's (mutable) data. + */ +NfcDeviceData* nfc_device_get_data_ptr(const NfcDevice* instance, NfcProtocol protocol); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_listener.c b/lib/nfc/nfc_listener.c new file mode 100644 index 0000000000..57b00a8755 --- /dev/null +++ b/lib/nfc/nfc_listener.c @@ -0,0 +1,144 @@ +#include "nfc_listener.h" + +#include +#include + +#include + +typedef struct NfcListenerListElement { + NfcProtocol protocol; + NfcGenericInstance* listener; + const NfcListenerBase* listener_api; + struct NfcListenerListElement* child; +} NfcListenerListElement; + +typedef struct { + NfcListenerListElement* head; + NfcListenerListElement* tail; +} NfcListenerList; + +struct NfcListener { + NfcProtocol protocol; + Nfc* nfc; + NfcListenerList list; + NfcDevice* nfc_dev; +}; + +static void nfc_listener_list_alloc(NfcListener* instance) { + instance->list.head = malloc(sizeof(NfcListenerListElement)); + instance->list.head->protocol = instance->protocol; + + instance->list.head->listener_api = nfc_listeners_api[instance->protocol]; + instance->list.head->child = NULL; + instance->list.tail = instance->list.head; + + // Build linked list + do { + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->list.head->protocol); + if(parent_protocol == NfcProtocolInvalid) break; + + NfcListenerListElement* parent = malloc(sizeof(NfcListenerListElement)); + parent->protocol = parent_protocol; + parent->listener_api = nfc_listeners_api[parent_protocol]; + parent->child = instance->list.head; + + instance->list.head = parent; + } while(true); + + // Allocate listener instances + NfcListenerListElement* iter = instance->list.head; + NfcDeviceData* data_tmp = nfc_device_get_data_ptr(instance->nfc_dev, iter->protocol); + iter->listener = iter->listener_api->alloc(instance->nfc, data_tmp); + + do { + if(iter->child == NULL) break; + data_tmp = nfc_device_get_data_ptr(instance->nfc_dev, iter->child->protocol); + iter->child->listener = iter->child->listener_api->alloc(iter->listener, data_tmp); + iter->listener_api->set_callback( + iter->listener, iter->child->listener_api->run, iter->child->listener); + + iter = iter->child; + } while(true); +} + +static void nfc_listener_list_free(NfcListener* instance) { + // Free listener instances + do { + instance->list.head->listener_api->free(instance->list.head->listener); + NfcListenerListElement* child = instance->list.head->child; + free(instance->list.head); + if(child == NULL) break; + instance->list.head = child; + } while(true); +} + +NfcListener* nfc_listener_alloc(Nfc* nfc, NfcProtocol protocol, const NfcDeviceData* data) { + furi_assert(nfc); + furi_assert(protocol < NfcProtocolNum); + furi_assert(data); + furi_assert(nfc_listeners_api[protocol]); + + NfcListener* instance = malloc(sizeof(NfcListener)); + instance->nfc = nfc; + instance->protocol = protocol; + instance->nfc_dev = nfc_device_alloc(); + nfc_device_set_data(instance->nfc_dev, protocol, data); + nfc_listener_list_alloc(instance); + + return instance; +} + +void nfc_listener_free(NfcListener* instance) { + furi_assert(instance); + + nfc_listener_list_free(instance); + nfc_device_free(instance->nfc_dev); + free(instance); +} + +NfcCommand nfc_listener_start_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcListener* instance = context; + furi_assert(instance->list.head); + + NfcCommand command = NfcCommandContinue; + NfcGenericEvent generic_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + + NfcListenerListElement* head_listener = instance->list.head; + command = head_listener->listener_api->run(generic_event, head_listener->listener); + + return command; +} + +void nfc_listener_start(NfcListener* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + + NfcListenerListElement* tail_element = instance->list.tail; + tail_element->listener_api->set_callback(tail_element->listener, callback, context); + nfc_start(instance->nfc, nfc_listener_start_callback, instance); +} + +void nfc_listener_stop(NfcListener* instance) { + furi_assert(instance); + + nfc_stop(instance->nfc); +} + +NfcProtocol nfc_listener_get_protocol(const NfcListener* instance) { + furi_assert(instance); + + return instance->protocol; +} + +const NfcDeviceData* nfc_listener_get_data(const NfcListener* instance, NfcProtocol protocol) { + furi_assert(instance); + furi_assert(instance->protocol == protocol); + + NfcListenerListElement* tail_element = instance->list.tail; + return tail_element->listener_api->get_data(tail_element->listener); +} diff --git a/lib/nfc/nfc_listener.h b/lib/nfc/nfc_listener.h new file mode 100644 index 0000000000..dbfeb23bff --- /dev/null +++ b/lib/nfc/nfc_listener.h @@ -0,0 +1,94 @@ +/** + * @file nfc_listener.h + * @brief NFC card emulation library. + * + * Once started, it will respond to supported commands from an NFC reader, thus imitating + * (or emulating) an NFC card. The responses will depend on the data that was supplied to + * the listener, so various card types and different cards of the same type can be emulated. + * + * It will also make any changes necessary to the emulated data in response to the + * reader commands if the protocol supports it. + * + * When running, NfcListener will generate events that the calling code must handle + * by providing a callback function. The events passed to the callback are protocol-specific + * and may include errors, state changes, data reception, special function requests and more. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcListener opaque type definition. + */ +typedef struct NfcListener NfcListener; + +/** + * @brief Allocate an NfcListener instance. + * + * @param[in] nfc pointer to an Nfc instance. + * @param[in] protocol identifier of the protocol to be used. + * @param[in] data pointer to the data to use during emulation. + * @returns pointer to an allocated instance. + * + * @see nfc.h + */ +NfcListener* nfc_listener_alloc(Nfc* nfc, NfcProtocol protocol, const NfcDeviceData* data); + +/** + * @brief Delete an NfcListener instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_listener_free(NfcListener* instance); + +/** + * @brief Start an NfcListener instance. + * + * The callback logic is protocol-specific, so it cannot be described here in detail. + * However, the callback return value ALWAYS determines what the listener should do next: + * to continue whatever it was doing prior to the callback run or to stop. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_listener_start(NfcListener* instance, NfcGenericCallback callback, void* context); + +/** + * @brief Stop an NfcListener instance. + * + * The emulation process can be stopped explicitly (the other way is via the callback return value). + * + * @param[in,out] instance pointer to the instance to be stopped. + */ +void nfc_listener_stop(NfcListener* instance); + +/** + * @brief Get the protocol identifier an NfcListener instance was created with. + * + * @param[in] instance pointer to the instance to be queried. + * @returns identifier of the protocol used by the instance. + */ +NfcProtocol nfc_listener_get_protocol(const NfcListener* instance); + +/** + * @brief Get the data that was that was provided for emulation. + * + * The protocol identifier passed as the protocol parameter MUST match the one + * stored in the instance, otherwise a crash will occur. + * This is to improve type safety. + * + * @param[in] instance pointer to the instance to be queried. + * @param[in] protocol assumed protocol identifier of the data to be retrieved. + * @returns pointer to the NFC device data. + */ +const NfcDeviceData* nfc_listener_get_data(const NfcListener* instance, NfcProtocol protocol); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_poller.c b/lib/nfc/nfc_poller.c new file mode 100644 index 0000000000..ffe0318a0d --- /dev/null +++ b/lib/nfc/nfc_poller.c @@ -0,0 +1,211 @@ +#include "nfc_poller.h" + +#include + +#include + +typedef enum { + NfcPollerSessionStateIdle, + NfcPollerSessionStateActive, + NfcPollerSessionStateStopRequest, +} NfcPollerSessionState; + +typedef struct NfcPollerListElement { + NfcProtocol protocol; + NfcGenericInstance* poller; + const NfcPollerBase* poller_api; + struct NfcPollerListElement* child; +} NfcPollerListElement; + +typedef struct { + NfcPollerListElement* head; + NfcPollerListElement* tail; +} NfcPollerList; + +struct NfcPoller { + NfcProtocol protocol; + Nfc* nfc; + NfcPollerList list; + NfcPollerSessionState session_state; + bool protocol_detected; +}; + +static void nfc_poller_list_alloc(NfcPoller* instance) { + instance->list.head = malloc(sizeof(NfcPollerListElement)); + instance->list.head->protocol = instance->protocol; + instance->list.head->poller_api = nfc_pollers_api[instance->protocol]; + instance->list.head->child = NULL; + instance->list.tail = instance->list.head; + + do { + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->list.head->protocol); + if(parent_protocol == NfcProtocolInvalid) break; + + NfcPollerListElement* parent = malloc(sizeof(NfcPollerListElement)); + parent->protocol = parent_protocol; + parent->poller_api = nfc_pollers_api[parent_protocol]; + parent->child = instance->list.head; + instance->list.head = parent; + } while(true); + + NfcPollerListElement* iter = instance->list.head; + iter->poller = iter->poller_api->alloc(instance->nfc); + + do { + if(iter->child == NULL) break; + iter->child->poller = iter->child->poller_api->alloc(iter->poller); + iter->poller_api->set_callback( + iter->poller, iter->child->poller_api->run, iter->child->poller); + + iter = iter->child; + } while(true); +} + +static void nfc_poller_list_free(NfcPoller* instance) { + do { + instance->list.head->poller_api->free(instance->list.head->poller); + NfcPollerListElement* child = instance->list.head->child; + free(instance->list.head); + if(child == NULL) break; + instance->list.head = child; + } while(true); +} + +NfcPoller* nfc_poller_alloc(Nfc* nfc, NfcProtocol protocol) { + furi_assert(nfc); + furi_assert(protocol < NfcProtocolNum); + + NfcPoller* instance = malloc(sizeof(NfcPoller)); + instance->session_state = NfcPollerSessionStateIdle; + instance->nfc = nfc; + instance->protocol = protocol; + nfc_poller_list_alloc(instance); + + return instance; +} + +void nfc_poller_free(NfcPoller* instance) { + furi_assert(instance); + + nfc_poller_list_free(instance); + free(instance); +} + +static NfcCommand nfc_poller_start_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcPoller* instance = context; + + NfcCommand command = NfcCommandContinue; + NfcGenericEvent poller_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + + if(event.type == NfcEventTypePollerReady) { + NfcPollerListElement* head_poller = instance->list.head; + command = head_poller->poller_api->run(poller_event, head_poller->poller); + } + + if(instance->session_state == NfcPollerSessionStateStopRequest) { + command = NfcCommandStop; + } + + return command; +} + +void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(instance->session_state == NfcPollerSessionStateIdle); + + NfcPollerListElement* tail_poller = instance->list.tail; + tail_poller->poller_api->set_callback(tail_poller->poller, callback, context); + + instance->session_state = NfcPollerSessionStateActive; + nfc_start(instance->nfc, nfc_poller_start_callback, instance); +} + +void nfc_poller_stop(NfcPoller* instance) { + furi_assert(instance); + furi_assert(instance->nfc); + + instance->session_state = NfcPollerSessionStateStopRequest; + nfc_stop(instance->nfc); + instance->session_state = NfcPollerSessionStateIdle; +} + +static NfcCommand nfc_poller_detect_tail_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + + NfcPoller* instance = context; + NfcPollerListElement* tail_poller = instance->list.tail; + instance->protocol_detected = tail_poller->poller_api->detect(event, tail_poller->poller); + + return NfcCommandStop; +} + +static NfcCommand nfc_poller_detect_head_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcPoller* instance = context; + NfcPollerListElement* tail_poller = instance->list.tail; + NfcPollerListElement* head_poller = instance->list.head; + + NfcCommand command = NfcCommandContinue; + NfcGenericEvent poller_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + + if(event.type == NfcEventTypePollerReady) { + if(tail_poller == head_poller) { + instance->protocol_detected = + tail_poller->poller_api->detect(poller_event, tail_poller->poller); + command = NfcCommandStop; + } else { + command = head_poller->poller_api->run(poller_event, head_poller->poller); + } + } + + return command; +} + +bool nfc_poller_detect(NfcPoller* instance) { + furi_assert(instance); + furi_assert(instance->session_state == NfcPollerSessionStateIdle); + + instance->session_state = NfcPollerSessionStateActive; + NfcPollerListElement* tail_poller = instance->list.tail; + NfcPollerListElement* iter = instance->list.head; + + if(tail_poller != instance->list.head) { + while(iter->child != tail_poller) iter = iter->child; + iter->poller_api->set_callback(iter->poller, nfc_poller_detect_tail_callback, instance); + } + + nfc_start(instance->nfc, nfc_poller_detect_head_callback, instance); + nfc_stop(instance->nfc); + + if(tail_poller != instance->list.head) { + iter->poller_api->set_callback( + iter->poller, tail_poller->poller_api->run, tail_poller->poller); + } + + return instance->protocol_detected; +} + +NfcProtocol nfc_poller_get_protocol(const NfcPoller* instance) { + furi_assert(instance); + + return instance->protocol; +} + +const NfcDeviceData* nfc_poller_get_data(const NfcPoller* instance) { + furi_assert(instance); + + NfcPollerListElement* tail_poller = instance->list.tail; + return tail_poller->poller_api->get_data(tail_poller->poller); +} diff --git a/lib/nfc/nfc_poller.h b/lib/nfc/nfc_poller.h new file mode 100644 index 0000000000..8ae01a8e43 --- /dev/null +++ b/lib/nfc/nfc_poller.h @@ -0,0 +1,104 @@ +/** + * @file nfc_poller.h + * @brief NFC card reading library. + * + * Once started, it will try to activate and read a card using the designated protocol, + * which is usually obtained by creating and starting an NfcScanner first. + * + * @see nfc_scanner.h + * + * When running, NfcPoller will generate events that the calling code must handle + * by providing a callback function. The events passed to the callback are protocol-specific + * and may include errors, state changes, data reception, special function requests and more. + * + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcPoller opaque type definition. + */ +typedef struct NfcPoller NfcPoller; + +/** + * @brief Allocate an NfcPoller instance. + * + * @param[in] nfc pointer to an Nfc instance. + * @param[in] protocol identifier of the protocol to be used. + * @returns pointer to an allocated instance. + * + * @see nfc.h + */ +NfcPoller* nfc_poller_alloc(Nfc* nfc, NfcProtocol protocol); + +/** + * @brief Delete an NfcPoller instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_poller_free(NfcPoller* instance); + +/** + * @brief Start an NfcPoller instance. + * + * The callback logic is protocol-specific, so it cannot be described here in detail. + * However, the callback return value ALWAYS determines what the poller should do next: + * to continue whatever it was doing prior to the callback run or to stop. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context); + +/** + * @brief Stop an NfcPoller instance. + * + * The reading process can be stopped explicitly (the other way is via the callback return value). + * + * @param[in,out] instance pointer to the instance to be stopped. + */ +void nfc_poller_stop(NfcPoller* instance); + +/** + * @brief Detect whether there is a card supporting a particular protocol in the vicinity. + * + * The behaviour of this function is protocol-defined, in general, it will do whatever is + * necessary to determine whether a card supporting the current protocol is in the vicinity + * and whether it is functioning normally. + * + * It is used automatically inside NfcScanner, so there is usually no need + * to call it explicitly. + * + * @see nfc_scanner.h + * + * @param[in,out] instance pointer to the instance to perform the detection with. + * @returns true if a supported card was detected, false otherwise. + */ +bool nfc_poller_detect(NfcPoller* instance); + +/** + * @brief Get the protocol identifier an NfcPoller instance was created with. + * + * @param[in] instance pointer to the instance to be queried. + * @returns identifier of the protocol used by the instance. + */ +NfcProtocol nfc_poller_get_protocol(const NfcPoller* instance); + +/** + * @brief Get the data that was that was gathered during the reading process. + * + * @param[in] instance pointer to the instance to be queried. + * @returns pointer to the NFC device data. + */ +const NfcDeviceData* nfc_poller_get_data(const NfcPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_scanner.c b/lib/nfc/nfc_scanner.c new file mode 100644 index 0000000000..0056dfb1cc --- /dev/null +++ b/lib/nfc/nfc_scanner.c @@ -0,0 +1,267 @@ +#include "nfc_scanner.h" +#include "nfc_poller.h" + +#include + +#include + +#define TAG "NfcScanner" + +typedef enum { + NfcScannerStateIdle, + NfcScannerStateTryBasePollers, + NfcScannerStateFindChildrenProtocols, + NfcScannerStateDetectChildrenProtocols, + NfcScannerStateComplete, + + NfcScannerStateNum, +} NfcScannerState; + +typedef enum { + NfcScannerSessionStateIdle, + NfcScannerSessionStateActive, + NfcScannerSessionStateStopRequest, +} NfcScannerSessionState; + +struct NfcScanner { + Nfc* nfc; + NfcScannerState state; + NfcScannerSessionState session_state; + + NfcScannerCallback callback; + void* context; + + NfcEvent nfc_event; + + NfcProtocol first_detected_protocol; + + size_t base_protocols_num; + size_t base_protocols_idx; + NfcProtocol base_protocols[NfcProtocolNum]; + + size_t detected_base_protocols_num; + NfcProtocol detected_base_protocols[NfcProtocolNum]; + + size_t children_protocols_num; + size_t children_protocols_idx; + NfcProtocol children_protocols[NfcProtocolNum]; + + size_t detected_protocols_num; + NfcProtocol detected_protocols[NfcProtocolNum]; + + NfcProtocol current_protocol; + + FuriThread* scan_worker; +}; + +static void nfc_scanner_reset(NfcScanner* instance) { + instance->base_protocols_idx = 0; + instance->base_protocols_num = 0; + + instance->children_protocols_idx = 0; + instance->children_protocols_num = 0; + + instance->detected_protocols_num = 0; + instance->detected_base_protocols_num = 0; + + instance->current_protocol = 0; +} + +typedef void (*NfcScannerStateHandler)(NfcScanner* instance); + +void nfc_scanner_state_handler_idle(NfcScanner* instance) { + for(size_t i = 0; i < NfcProtocolNum; i++) { + NfcProtocol parent_protocol = nfc_protocol_get_parent(i); + if(parent_protocol == NfcProtocolInvalid) { + instance->base_protocols[instance->base_protocols_num] = i; + instance->base_protocols_num++; + } + } + FURI_LOG_D(TAG, "Found %zu base protocols", instance->base_protocols_num); + + instance->first_detected_protocol = NfcProtocolInvalid; + instance->state = NfcScannerStateTryBasePollers; +} + +void nfc_scanner_state_handler_try_base_pollers(NfcScanner* instance) { + do { + instance->current_protocol = instance->base_protocols[instance->base_protocols_idx]; + + if(instance->first_detected_protocol == instance->current_protocol) { + instance->state = NfcScannerStateFindChildrenProtocols; + break; + } + + NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->current_protocol); + bool protocol_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + + if(protocol_detected) { + instance->detected_protocols[instance->detected_protocols_num] = + instance->current_protocol; + instance->detected_protocols_num++; + + instance->detected_base_protocols[instance->detected_base_protocols_num] = + instance->current_protocol; + instance->detected_base_protocols_num++; + + if(instance->first_detected_protocol == NfcProtocolInvalid) { + instance->first_detected_protocol = instance->current_protocol; + instance->current_protocol = NfcProtocolInvalid; + } + } + + instance->base_protocols_idx = + (instance->base_protocols_idx + 1) % instance->base_protocols_num; + } while(false); +} + +void nfc_scanner_state_handler_find_children_protocols(NfcScanner* instance) { + for(size_t i = 0; i < NfcProtocolNum; i++) { + for(size_t j = 0; j < instance->detected_base_protocols_num; j++) { + if(nfc_protocol_has_parent(i, instance->detected_base_protocols[j])) { + instance->children_protocols[instance->children_protocols_num] = i; + instance->children_protocols_num++; + } + } + } + + if(instance->children_protocols_num > 0) { + instance->state = NfcScannerStateDetectChildrenProtocols; + } else { + instance->state = NfcScannerStateComplete; + } + FURI_LOG_D(TAG, "Found %zu children", instance->children_protocols_num); +} + +void nfc_scanner_state_handler_detect_children_protocols(NfcScanner* instance) { + furi_assert(instance->children_protocols_num); + + instance->current_protocol = instance->children_protocols[instance->children_protocols_idx]; + + NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->current_protocol); + bool protocol_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + + if(protocol_detected) { + instance->detected_protocols[instance->detected_protocols_num] = + instance->current_protocol; + instance->detected_protocols_num++; + } + + instance->children_protocols_idx++; + if(instance->children_protocols_idx == instance->children_protocols_num) { + instance->state = NfcScannerStateComplete; + } +} + +static void nfc_scanner_filter_detected_protocols(NfcScanner* instance) { + size_t filtered_protocols_num = 0; + NfcProtocol filtered_protocols[NfcProtocolNum] = {}; + + for(size_t i = 0; i < instance->detected_protocols_num; i++) { + bool is_parent = false; + for(size_t j = i; j < instance->detected_protocols_num; j++) { + is_parent = nfc_protocol_has_parent( + instance->detected_protocols[j], instance->detected_protocols[i]); + if(is_parent) break; + } + if(!is_parent) { + filtered_protocols[filtered_protocols_num] = instance->detected_protocols[i]; + filtered_protocols_num++; + } + } + + instance->detected_protocols_num = filtered_protocols_num; + memcpy(instance->detected_protocols, filtered_protocols, filtered_protocols_num); +} + +void nfc_scanner_state_handler_complete(NfcScanner* instance) { + if(instance->detected_protocols_num > 1) { + nfc_scanner_filter_detected_protocols(instance); + } + FURI_LOG_I(TAG, "Detected %zu protocols", instance->detected_protocols_num); + + NfcScannerEvent event = { + .type = NfcScannerEventTypeDetected, + .data = + { + .protocol_num = instance->detected_protocols_num, + .protocols = instance->detected_protocols, + }, + }; + + instance->callback(event, instance->context); + furi_delay_ms(100); +} + +static NfcScannerStateHandler nfc_scanner_state_handlers[NfcScannerStateNum] = { + [NfcScannerStateIdle] = nfc_scanner_state_handler_idle, + [NfcScannerStateTryBasePollers] = nfc_scanner_state_handler_try_base_pollers, + [NfcScannerStateFindChildrenProtocols] = nfc_scanner_state_handler_find_children_protocols, + [NfcScannerStateDetectChildrenProtocols] = nfc_scanner_state_handler_detect_children_protocols, + [NfcScannerStateComplete] = nfc_scanner_state_handler_complete, +}; + +static int32_t nfc_scanner_worker(void* context) { + furi_assert(context); + + NfcScanner* instance = context; + + while(instance->session_state == NfcScannerSessionStateActive) { + nfc_scanner_state_handlers[instance->state](instance); + } + + nfc_scanner_reset(instance); + + return 0; +} + +NfcScanner* nfc_scanner_alloc(Nfc* nfc) { + furi_assert(nfc); + + NfcScanner* instance = malloc(sizeof(NfcScanner)); + instance->nfc = nfc; + + return instance; +} + +void nfc_scanner_free(NfcScanner* instance) { + furi_assert(instance); + + free(instance); +} + +void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(instance->session_state == NfcScannerSessionStateIdle); + furi_assert(instance->scan_worker == NULL); + + instance->callback = callback; + instance->context = context; + instance->session_state = NfcScannerSessionStateActive; + + instance->scan_worker = furi_thread_alloc(); + furi_thread_set_name(instance->scan_worker, "NfcScanWorker"); + furi_thread_set_context(instance->scan_worker, instance); + furi_thread_set_stack_size(instance->scan_worker, 4 * 1024); + furi_thread_set_callback(instance->scan_worker, nfc_scanner_worker); + + furi_thread_start(instance->scan_worker); +} + +void nfc_scanner_stop(NfcScanner* instance) { + furi_assert(instance); + furi_assert(instance->scan_worker); + + instance->session_state = NfcScannerSessionStateStopRequest; + furi_thread_join(instance->scan_worker); + instance->session_state = NfcScannerSessionStateIdle; + + furi_thread_free(instance->scan_worker); + instance->scan_worker = NULL; + instance->callback = NULL; + instance->context = NULL; + instance->state = NfcScannerStateIdle; +} diff --git a/lib/nfc/nfc_scanner.h b/lib/nfc/nfc_scanner.h new file mode 100644 index 0000000000..a1b4aabcda --- /dev/null +++ b/lib/nfc/nfc_scanner.h @@ -0,0 +1,97 @@ +/** + * @file nfc_scanner.h + * @brief NFC card detection library. + * + * Once started, a NfcScanner instance will iterate over all available protocols + * and return a list of one or more detected protocol identifiers via a user-provided callback. + * + * The NfcScanner behaviour is greedy, i.e. it will not stop scanning upon detection of + * a just one protocol and will try others as well until all possibilities are exhausted. + * This is to allow for multi-protocol card support. + * + * If no supported cards are in the vicinity, the scanning process will continue + * until stopped explicitly. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcScanner opaque type definition. + */ +typedef struct NfcScanner NfcScanner; + +/** + * @brief Event type passed to the user callback. + */ +typedef enum { + NfcScannerEventTypeDetected, /**< One or more protocols have been detected. */ +} NfcScannerEventType; + +/** + * @brief Event data passed to the user callback. + */ +typedef struct { + size_t protocol_num; /**< Number of detected protocols (one or more). */ + NfcProtocol* protocols; /**< Pointer to the array of detected protocol identifiers. */ +} NfcScannerEventData; + +/** + * @brief Event passed to the user callback. + */ +typedef struct { + NfcScannerEventType type; /**< Type of event. Determines how the data must be handled. */ + NfcScannerEventData data; /**< Event-specific data. Handled accordingly to the even type. */ +} NfcScannerEvent; + +/** + * @brief User callback function signature. + * + * A function with such signature must be provided by the user upon calling nfc_scanner_start(). + * + * @param[in] event occurred event, complete with type and data. + * @param[in] context pointer to the context data provided in nfc_scanner_start() call. + */ +typedef void (*NfcScannerCallback)(NfcScannerEvent event, void* context); + +/** + * @brief Allocate an NfcScanner instance. + * + * @param[in] nfc pointer to an Nfc instance. + * @returns pointer to the allocated NfcScanner instance. + * + * @see nfc.h + */ +NfcScanner* nfc_scanner_alloc(Nfc* nfc); + +/** + * @brief Delete an NfcScanner instance. + * + * @param[in,out] pointer to the instance to be deleted. + */ +void nfc_scanner_free(NfcScanner* instance); + +/** + * @brief Start an NfcScanner. + * + * @param[in,out] pointer to the instance to be started. + * @param[in] callback pointer to the callback function (will be called upon a detection event). + * @param[in] context pointer to the caller-specific context (will be passed to the callback). + */ +void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* context); + +/** + * @brief Stop an NfcScanner. + * + * @param[in,out] pointer to the instance to be stopped. + */ +void nfc_scanner_stop(NfcScanner* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_types.c b/lib/nfc/nfc_types.c deleted file mode 100644 index 96b92640f0..0000000000 --- a/lib/nfc/nfc_types.c +++ /dev/null @@ -1,69 +0,0 @@ -#include "nfc_types.h" - -const char* nfc_get_dev_type(FuriHalNfcType type) { - if(type == FuriHalNfcTypeA) { - return "NFC-A"; - } else if(type == FuriHalNfcTypeB) { - return "NFC-B"; - } else if(type == FuriHalNfcTypeF) { - return "NFC-F"; - } else if(type == FuriHalNfcTypeV) { - return "NFC-V"; - } else { - return "Unknown"; - } -} - -const char* nfc_guess_protocol(NfcProtocol protocol) { - if(protocol == NfcDeviceProtocolEMV) { - return "EMV bank card"; - } else if(protocol == NfcDeviceProtocolMifareUl) { - return "Mifare Ultral/NTAG"; - } else if(protocol == NfcDeviceProtocolMifareClassic) { - return "Mifare Classic"; - } else if(protocol == NfcDeviceProtocolMifareDesfire) { - return "Mifare DESFire"; - } else { - return "Unrecognized"; - } -} - -const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { - if(type == MfUltralightTypeNTAG213) { - return "NTAG213"; - } else if(type == MfUltralightTypeNTAG215) { - return "NTAG215"; - } else if(type == MfUltralightTypeNTAG216) { - return "NTAG216"; - } else if(type == MfUltralightTypeNTAGI2C1K) { - return "NTAG I2C 1K"; - } else if(type == MfUltralightTypeNTAGI2C2K) { - return "NTAG I2C 2K"; - } else if(type == MfUltralightTypeNTAGI2CPlus1K) { - return "NTAG I2C Plus 1K"; - } else if(type == MfUltralightTypeNTAGI2CPlus2K) { - return "NTAG I2C Plus 2K"; - } else if(type == MfUltralightTypeNTAG203) { - return "NTAG203"; - } else if(type == MfUltralightTypeULC) { - return "Mifare Ultralight C"; - } else if(type == MfUltralightTypeUL11 && full_name) { - return "Mifare Ultralight 11"; - } else if(type == MfUltralightTypeUL21 && full_name) { - return "Mifare Ultralight 21"; - } else { - return "Mifare Ultralight"; - } -} - -const char* nfc_mf_classic_type(MfClassicType type) { - if(type == MfClassicTypeMini) { - return "Mifare Mini 0.3K"; - } else if(type == MfClassicType1k) { - return "Mifare Classic 1K"; - } else if(type == MfClassicType4k) { - return "Mifare Classic 4K"; - } else { - return "Mifare Classic"; - } -} diff --git a/lib/nfc/nfc_types.h b/lib/nfc/nfc_types.h deleted file mode 100644 index 5ebb2d27bc..0000000000 --- a/lib/nfc/nfc_types.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "nfc_device.h" - -#ifdef __cplusplus -extern "C" { -#endif - -const char* nfc_get_dev_type(FuriHalNfcType type); - -const char* nfc_guess_protocol(NfcProtocol protocol); - -const char* nfc_mf_ul_type(MfUltralightType type, bool full_name); - -const char* nfc_mf_classic_type(MfClassicType type); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c deleted file mode 100644 index 36776d8047..0000000000 --- a/lib/nfc/nfc_worker.c +++ /dev/null @@ -1,1325 +0,0 @@ -#include "nfc_worker_i.h" -#include - -#include -#include "parsers/nfc_supported_card.h" - -#define TAG "NfcWorker" - -/***************************** NFC Worker API *******************************/ - -NfcWorker* nfc_worker_alloc() { - NfcWorker* nfc_worker = malloc(sizeof(NfcWorker)); - - // Worker thread attributes - nfc_worker->thread = furi_thread_alloc_ex("NfcWorker", 8192, nfc_worker_task, nfc_worker); - - nfc_worker->callback = NULL; - nfc_worker->context = NULL; - nfc_worker->storage = furi_record_open(RECORD_STORAGE); - - // Initialize rfal - while(furi_hal_nfc_is_busy()) { - furi_delay_ms(10); - } - nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); - - nfc_worker->reader_analyzer = reader_analyzer_alloc(nfc_worker->storage); - - return nfc_worker; -} - -void nfc_worker_free(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - - furi_thread_free(nfc_worker->thread); - - furi_record_close(RECORD_STORAGE); - - reader_analyzer_free(nfc_worker->reader_analyzer); - - free(nfc_worker); -} - -NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker) { - return nfc_worker->state; -} - -void nfc_worker_start( - NfcWorker* nfc_worker, - NfcWorkerState state, - NfcDeviceData* dev_data, - NfcWorkerCallback callback, - void* context) { - furi_assert(nfc_worker); - furi_assert(dev_data); - while(furi_hal_nfc_is_busy()) { - furi_delay_ms(10); - } - furi_hal_nfc_deinit(); - furi_hal_nfc_init(); - - nfc_worker->callback = callback; - nfc_worker->context = context; - nfc_worker->dev_data = dev_data; - nfc_worker_change_state(nfc_worker, state); - furi_thread_start(nfc_worker->thread); -} - -void nfc_worker_stop(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->thread); - if(furi_thread_get_state(nfc_worker->thread) != FuriThreadStateStopped) { - furi_hal_nfc_stop(); - nfc_worker_change_state(nfc_worker, NfcWorkerStateStop); - furi_thread_join(nfc_worker->thread); - } -} - -void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) { - nfc_worker->state = state; -} - -/***************************** NFC Worker Thread *******************************/ - -int32_t nfc_worker_task(void* context) { - NfcWorker* nfc_worker = context; - - furi_hal_nfc_exit_sleep(); - - if(nfc_worker->state == NfcWorkerStateRead) { - if(nfc_worker->dev_data->read_mode == NfcReadModeAuto) { - nfc_worker_read(nfc_worker); - } else { - nfc_worker_read_type(nfc_worker); - } - } else if(nfc_worker->state == NfcWorkerStateUidEmulate) { - nfc_worker_emulate_uid(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { - nfc_worker_emulate_apdu(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { - nfc_worker_emulate_mf_ultralight(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { - nfc_worker_emulate_mf_classic(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicWrite) { - nfc_worker_write_mf_classic(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { - nfc_worker_update_mf_classic(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { - nfc_worker_mf_ultralight_read_auth(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker_mf_classic_dict_attack(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - nfc_worker_analyze_reader(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVEmulate) { - nfc_worker_nfcv_emulate(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVSniff) { - nfc_worker_nfcv_sniff(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVUnlock) { - nfc_worker_nfcv_unlock(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { - nfc_worker_nfcv_unlock(nfc_worker); - } - furi_hal_nfc_sleep(); - nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); - - return 0; -} - -static bool nfc_worker_read_nfcv(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - bool read_success = false; - NfcVReader reader = {}; - - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - - furi_hal_nfc_sleep(); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; - if(!nfcv_read_card(&reader, nfc_data, nfcv_data)) break; - - read_success = true; - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - - return read_success; -} - -void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - nfcv_emu_init(nfc_data, nfcv_data); - while(nfc_worker->state == NfcWorkerStateNfcVEmulate) { - if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); - if(nfcv_data->modified) { - nfc_worker->callback(NfcWorkerEventNfcVContentChanged, nfc_worker->context); - nfcv_data->modified = false; - } - } - } - } - nfcv_emu_deinit(nfcv_data); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - nfcv_data->sub_type = NfcVTypeSniff; - nfcv_emu_init(nfc_data, nfcv_data); - - while(nfc_worker->state == NfcWorkerStateNfcVSniff) { - if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); - } - } - } - nfcv_emu_deinit(nfcv_data); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - FuriHalNfcTxRxContext tx_rx = {}; - uint8_t* key_data = nfcv_data->sub_data.slix.key_privacy; - uint32_t key = 0; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - furi_hal_nfc_sleep(); - - while((nfc_worker->state == NfcWorkerStateNfcVUnlock) || - (nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave)) { - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_ll_txrx_on(); - furi_hal_nfc_ll_poll(); - if(furi_hal_nfc_ll_set_mode( - FuriHalNfcModePollNfcv, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48) != - FuriHalNfcReturnOk) { - break; - } - - furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER); - furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER); - furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); - furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCV); - - FURI_LOG_D(TAG, "Detect presence"); - ReturnCode ret = slix_get_random(nfcv_data); - - if(ret == ERR_NONE) { - /* there is some chip, responding with a RAND */ - nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; - FURI_LOG_D(TAG, " Chip detected. In privacy?"); - ret = nfcv_inventory(NULL); - - if(ret == ERR_NONE) { - /* chip is also visible, so no action required, just save */ - if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { - NfcVReader reader = {}; - - if(!nfcv_read_card(&reader, &nfc_worker->dev_data->nfc_data, nfcv_data)) { - FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); - snprintf(nfcv_data->error, sizeof(nfcv_data->error), "Read card\nfailed"); - nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); - } else { - FURI_LOG_D(TAG, " => success, wait for chip to disappear."); - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - } - } else { - FURI_LOG_D(TAG, " => success, wait for chip to disappear."); - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - } - - while(slix_get_random(NULL) == ERR_NONE) { - furi_delay_ms(100); - } - - FURI_LOG_D(TAG, " => chip is already visible, wait for chip to disappear.\r\n"); - nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); - while(slix_get_random(NULL) == ERR_NONE) { - furi_delay_ms(100); - } - - key_data[0] = 0; - key_data[1] = 0; - key_data[2] = 0; - key_data[3] = 0; - - } else { - /* chip is invisible, try to unlock */ - FURI_LOG_D(TAG, " chip is invisible, unlocking"); - - if(nfcv_data->auth_method == NfcVAuthMethodManual) { - key |= key_data[0] << 24; - key |= key_data[1] << 16; - key |= key_data[2] << 8; - key |= key_data[3] << 0; - - ret = slix_unlock(nfcv_data, 4); - } else { - key = 0x7FFD6E5B; - key_data[0] = (key >> 24) & 0xFF; - key_data[1] = (key >> 16) & 0xFF; - key_data[2] = (key >> 8) & 0xFF; - key_data[3] = (key >> 0) & 0xFF; - ret = slix_unlock(nfcv_data, 4); - - if(ret != ERR_NONE) { - /* main key failed, trying second one */ - FURI_LOG_D(TAG, " trying second key after resetting"); - - /* reset chip */ - furi_hal_nfc_ll_txrx_off(); - furi_delay_ms(20); - furi_hal_nfc_ll_txrx_on(); - - if(slix_get_random(nfcv_data) != ERR_NONE) { - FURI_LOG_D(TAG, " reset failed"); - } - - key = 0x0F0F0F0F; - key_data[0] = (key >> 24) & 0xFF; - key_data[1] = (key >> 16) & 0xFF; - key_data[2] = (key >> 8) & 0xFF; - key_data[3] = (key >> 0) & 0xFF; - ret = slix_unlock(nfcv_data, 4); - } - } - if(ret != ERR_NONE) { - /* unlock failed */ - FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); - snprintf( - nfcv_data->error, sizeof(nfcv_data->error), "Passwords not\naccepted"); - nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); - - /* reset chip */ - furi_hal_nfc_ll_txrx_off(); - furi_delay_ms(20); - furi_hal_nfc_ll_txrx_on(); - - /* wait for disappearing */ - while(slix_get_random(NULL) == ERR_NONE) { - furi_delay_ms(100); - } - } - } - } else { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - } - - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_sleep(); - furi_delay_ms(100); - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - bool read_success = false; - MfUltralightReader reader = {}; - MfUltralightData data = {}; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - // Try to read supported card - FURI_LOG_I(TAG, "Trying to read a supported card ..."); - for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { - if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareUl) { - if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { - if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { - read_success = true; - nfc_supported_card[i].parse(nfc_worker->dev_data); - break; - } - } else { - furi_hal_nfc_sleep(); - } - } - } - if(read_success) break; - furi_hal_nfc_sleep(); - - // Otherwise, try to read as usual - if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; - if(!mf_ul_read_card(tx_rx, &reader, &data)) break; - // Copy data - nfc_worker->dev_data->mf_ul_data = data; - read_success = true; - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - - return read_success; -} - -static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker->callback); - bool read_success = false; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - // Try to read supported card - FURI_LOG_I(TAG, "Trying to read a supported card ..."); - for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { - if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareClassic) { - if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { - if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { - read_success = true; - nfc_supported_card[i].parse(nfc_worker->dev_data); - break; - } - } else { - furi_hal_nfc_sleep(); - } - } - } - if(read_success) break; - // Try to read card with key cache - FURI_LOG_I(TAG, "Search for key cache ..."); - if(nfc_worker->callback(NfcWorkerEventReadMfClassicLoadKeyCache, nfc_worker->context)) { - FURI_LOG_I(TAG, "Load keys cache success. Start reading"); - uint8_t sectors_read = - mf_classic_update_card(tx_rx, &nfc_worker->dev_data->mf_classic_data); - uint8_t sectors_total = - mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type); - FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total); - read_success = mf_classic_is_card_read(&nfc_worker->dev_data->mf_classic_data); - } - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - return read_success; -} - -static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - bool read_success = false; - MifareDesfireData* data = &nfc_worker->dev_data->mf_df_data; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; - if(!mf_df_read_card(tx_rx, data)) break; - FURI_LOG_I(TAG, "Trying to parse a supported card ..."); - - // The model for parsing DESFire is a little different to other cards; - // we don't have parsers to provide encryption keys, so we can read the - // data normally, and then pass the read data to a parser. - // - // There are fully-protected DESFire cards, but providing keys for them - // is difficult (and unnessesary for many transit cards). - for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { - if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareDesfire) { - if(nfc_supported_card[i].parse(nfc_worker->dev_data)) break; - } - } - read_success = true; - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - - return read_success; -} - -static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - - bool card_read = false; - furi_hal_nfc_sleep(); - if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - FURI_LOG_I(TAG, "Mifare Ultralight / NTAG detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl; - card_read = nfc_worker_read_mf_ultralight(nfc_worker, tx_rx); - } else if(mf_classic_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - FURI_LOG_I(TAG, "Mifare Classic detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic; - nfc_worker->dev_data->mf_classic_data.type = - mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - card_read = nfc_worker_read_mf_classic(nfc_worker, tx_rx); - } else if(mf_df_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - FURI_LOG_I(TAG, "Mifare DESFire detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire; - if(!nfc_worker_read_mf_desfire(nfc_worker, tx_rx)) { - FURI_LOG_I(TAG, "Unknown card. Save UID"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; - } - card_read = true; - } else { - nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; - card_read = true; - } - - return card_read; -} - -void nfc_worker_read(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - nfc_device_data_clear(nfc_worker->dev_data); - NfcDeviceData* dev_data = nfc_worker->dev_data; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - FuriHalNfcTxRxContext tx_rx = {}; - NfcWorkerEvent event = 0; - bool card_not_detected_notified = false; - - while(nfc_worker->state == NfcWorkerStateRead) { - if(furi_hal_nfc_detect(nfc_data, 300)) { - // Process first found device - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_not_detected_notified = false; - if(nfc_data->type == FuriHalNfcTypeA) { - if(nfc_worker_read_nfca(nfc_worker, &tx_rx)) { - if(dev_data->protocol == NfcDeviceProtocolMifareUl) { - event = NfcWorkerEventReadMfUltralight; - break; - } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { - event = NfcWorkerEventReadMfClassicDone; - break; - } else if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) { - event = NfcWorkerEventReadMfDesfire; - break; - } else if(dev_data->protocol == NfcDeviceProtocolUnknown) { - event = NfcWorkerEventReadUidNfcA; - break; - } - } else { - if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { - event = NfcWorkerEventReadMfClassicDictAttackRequired; - break; - } - } - } else if(nfc_data->type == FuriHalNfcTypeB) { - event = NfcWorkerEventReadUidNfcB; - break; - } else if(nfc_data->type == FuriHalNfcTypeF) { - event = NfcWorkerEventReadUidNfcF; - break; - } else if(nfc_data->type == FuriHalNfcTypeV) { - FURI_LOG_I(TAG, "NfcV detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; - if(nfc_worker_read_nfcv(nfc_worker, &tx_rx)) { - FURI_LOG_I(TAG, "nfc_worker_read_nfcv success"); - } - event = NfcWorkerEventReadNfcV; - break; - } - } else { - if(!card_not_detected_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_not_detected_notified = true; - } - } - furi_hal_nfc_sleep(); - furi_delay_ms(100); - } - // Notify caller and exit - if(event > NfcWorkerEventReserved) { - nfc_worker->callback(event, nfc_worker->context); - } -} - -void nfc_worker_read_type(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - NfcReadMode read_mode = nfc_worker->dev_data->read_mode; - nfc_device_data_clear(nfc_worker->dev_data); - NfcDeviceData* dev_data = nfc_worker->dev_data; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - FuriHalNfcTxRxContext tx_rx = {}; - NfcWorkerEvent event = 0; - bool card_not_detected_notified = false; - - while(nfc_worker->state == NfcWorkerStateRead) { - if(furi_hal_nfc_detect(nfc_data, 300)) { - FURI_LOG_D(TAG, "Card detected"); - furi_hal_nfc_sleep(); - // Process first found device - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_not_detected_notified = false; - if(nfc_data->type == FuriHalNfcTypeA) { - if(read_mode == NfcReadModeMfClassic) { - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic; - nfc_worker->dev_data->mf_classic_data.type = mf_classic_get_classic_type( - nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - if(nfc_worker_read_mf_classic(nfc_worker, &tx_rx)) { - FURI_LOG_D(TAG, "Card read"); - dev_data->protocol = NfcDeviceProtocolMifareClassic; - event = NfcWorkerEventReadMfClassicDone; - break; - } else { - FURI_LOG_D(TAG, "Card read failed"); - dev_data->protocol = NfcDeviceProtocolMifareClassic; - event = NfcWorkerEventReadMfClassicDictAttackRequired; - break; - } - } else if(read_mode == NfcReadModeMfUltralight) { - FURI_LOG_I(TAG, "Mifare Ultralight / NTAG"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl; - if(nfc_worker_read_mf_ultralight(nfc_worker, &tx_rx)) { - event = NfcWorkerEventReadMfUltralight; - break; - } - } else if(read_mode == NfcReadModeMfDesfire) { - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire; - if(nfc_worker_read_mf_desfire(nfc_worker, &tx_rx)) { - event = NfcWorkerEventReadMfDesfire; - break; - } - } else if(read_mode == NfcReadModeNFCA) { - nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; - event = NfcWorkerEventReadUidNfcA; - break; - } - } - } else { - if(!card_not_detected_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_not_detected_notified = true; - } - } - furi_hal_nfc_sleep(); - furi_delay_ms(100); - } - // Notify caller and exit - if(event > NfcWorkerEventReserved) { - nfc_worker->callback(event, nfc_worker->context); - } -} - -void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; - NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data; - - // TODO add support for RATS - // Need to save ATS to support ISO-14443A-4 emulation - - while(nfc_worker->state == NfcWorkerStateUidEmulate) { - if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) { - if(furi_hal_nfc_tx_rx(&tx_rx, 100)) { - reader_data->size = tx_rx.rx_bits / 8; - if(reader_data->size > 0) { - memcpy(reader_data->data, tx_rx.rx_data, reader_data->size); - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - } - } - } else { - FURI_LOG_E(TAG, "Failed to get reader commands"); - } - } - } -} - -void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData params = { - .uid = {0xCF, 0x72, 0xd4, 0x40}, - .uid_len = 4, - .atqa = {0x00, 0x04}, - .sak = 0x20, - .type = FuriHalNfcTypeA, - }; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - while(nfc_worker->state == NfcWorkerStateEmulateApdu) { //-V1044 - if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { - FURI_LOG_D(TAG, "POS terminal detected"); - if(emv_card_emulation(&tx_rx)) { - FURI_LOG_D(TAG, "EMV card emulated"); - } - } else { - FURI_LOG_D(TAG, "Can't find reader"); - } - furi_hal_nfc_sleep(); - furi_delay_ms(20); - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -void nfc_worker_mf_ultralight_auth_received_callback(MfUltralightAuth auth, void* context) { - furi_assert(context); - - NfcWorker* nfc_worker = context; - nfc_worker->dev_data->mf_ul_auth = auth; - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventMfUltralightPwdAuth, nfc_worker->context); - } -} - -void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - MfUltralightEmulator emulator = {}; - mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); - - // TODO rework with reader analyzer - emulator.auth_received_callback = nfc_worker_mf_ultralight_auth_received_callback; - emulator.context = nfc_worker; - - rfal_platform_spi_acquire(); - - while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { - mf_ul_reset_emulation(&emulator, true); - furi_hal_nfc_emulate_nfca( - nfc_data->uid, - nfc_data->uid_len, - nfc_data->atqa, - nfc_data->sak, - mf_ul_prepare_emulation_response, - &emulator, - 5000); - // Check if data was modified - if(emulator.data_changed) { - nfc_worker->dev_data->mf_ul_data = emulator.data; - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - } - emulator.data_changed = false; - } - } - - rfal_platform_spi_release(); -} - -static bool nfc_worker_mf_get_b_key_from_sector_trailer( - FuriHalNfcTxRxContext* tx_rx, - uint16_t sector, - uint64_t key, - uint64_t* found_key) { - // Some access conditions allow reading B key via A key - - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - - Crypto1 crypto = {}; - MfClassicBlock block_tmp = {}; - MfClassicAuthContext auth_context = {.sector = sector, .key_a = MF_CLASSIC_NO_KEY, .key_b = 0}; - - furi_hal_nfc_sleep(); - - if(mf_classic_auth_attempt(tx_rx, &crypto, &auth_context, key)) { - if(mf_classic_read_block(tx_rx, &crypto, block, &block_tmp)) { - *found_key = nfc_util_bytes2num(&block_tmp.value[10], sizeof(uint8_t) * 6); - - return *found_key; - } - } - - return false; -} - -static void nfc_worker_mf_classic_key_attack( - NfcWorker* nfc_worker, - uint64_t key, - FuriHalNfcTxRxContext* tx_rx, - uint16_t start_sector) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - bool card_found_notified = true; - bool card_removed_notified = false; - - MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; - NfcMfClassicDictAttackData* dict_attack_data = - &nfc_worker->dev_data->mf_classic_dict_attack_data; - uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); - - furi_assert(start_sector < total_sectors); - - nfc_worker->callback(NfcWorkerEventKeyAttackStart, nfc_worker->context); - - // Check every sector's A and B keys with the given key - for(size_t i = start_sector; i < total_sectors; i++) { - nfc_worker->callback(NfcWorkerEventKeyAttackNextSector, nfc_worker->context); - dict_attack_data->current_sector = i; - furi_hal_nfc_sleep(); - if(furi_hal_nfc_activate_nfca(200, NULL)) { - furi_hal_nfc_sleep(); - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - card_removed_notified = false; - } - uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); - if(mf_classic_is_sector_read(data, i)) continue; - if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { - FURI_LOG_D(TAG, "Trying A key for sector %d, key: %012llX", i, key); - if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { - mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key A found: %012llX", key); - nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); - - uint64_t found_key; - if(nfc_worker_mf_get_b_key_from_sector_trailer(tx_rx, i, key, &found_key)) { - FURI_LOG_D(TAG, "Found B key via reading sector %d", i); - mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); - - if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - } - } - } - furi_hal_nfc_sleep(); - } - if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { - FURI_LOG_D(TAG, "Trying B key for sector %d, key: %012llX", i, key); - if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { - mf_classic_set_key_found(data, i, MfClassicKeyB, key); - FURI_LOG_D(TAG, "Key B found: %012llX", key); - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - } - } - - if(mf_classic_is_sector_read(data, i)) continue; - mf_classic_read_sector(tx_rx, data, i); - } else { - if(!card_removed_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_removed_notified = true; - card_found_notified = false; - } - } - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - } - nfc_worker->callback(NfcWorkerEventKeyAttackStop, nfc_worker->context); -} - -void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; - NfcMfClassicDictAttackData* dict_attack_data = - &nfc_worker->dev_data->mf_classic_dict_attack_data; - uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); - uint64_t key = 0; - uint64_t prev_key = 0; - FuriHalNfcTxRxContext tx_rx = {}; - bool card_found_notified = true; - bool card_removed_notified = false; - - // Load dictionary - MfClassicDict* dict = dict_attack_data->dict; - if(!dict) { - FURI_LOG_E(TAG, "Dictionary not found"); - nfc_worker->callback(NfcWorkerEventNoDictFound, nfc_worker->context); - return; - } - - FURI_LOG_D( - TAG, "Start Dictionary attack, Key Count %lu", mf_classic_dict_get_total_keys(dict)); - for(size_t i = 0; i < total_sectors; i++) { - FURI_LOG_I(TAG, "Sector %d", i); - nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context); - uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); - if(mf_classic_is_sector_read(data, i)) continue; - if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - mf_classic_is_key_found(data, i, MfClassicKeyB)) - continue; - uint16_t key_index = 0; - while(mf_classic_dict_get_next_key(dict, &key)) { - FURI_LOG_T(TAG, "Key %d", key_index); - if(++key_index % NFC_DICT_KEY_BATCH_SIZE == 0) { - nfc_worker->callback(NfcWorkerEventNewDictKeyBatch, nfc_worker->context); - } - furi_hal_nfc_sleep(); - uint32_t cuid; - if(furi_hal_nfc_activate_nfca(200, &cuid)) { - bool deactivated = false; - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - card_removed_notified = false; - nfc_worker_mf_classic_key_attack(nfc_worker, prev_key, &tx_rx, i); - deactivated = true; - } - FURI_LOG_D(TAG, "Try to auth to sector %d with key %012llX", i, key); - if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { - if(mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { - mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key A found: %012llX", key); - nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); - - uint64_t found_key; - if(nfc_worker_mf_get_b_key_from_sector_trailer( - &tx_rx, i, key, &found_key)) { - FURI_LOG_D(TAG, "Found B key via reading sector %d", i); - mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); - - if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - } - - nfc_worker_mf_classic_key_attack(nfc_worker, found_key, &tx_rx, i + 1); - break; - } - nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); - } - furi_hal_nfc_sleep(); - deactivated = true; - } else { - // If the key A is marked as found and matches the searching key, invalidate it - MfClassicSectorTrailer* sec_trailer = - mf_classic_get_sector_trailer_by_sector(data, i); - - uint8_t current_key[6]; - nfc_util_num2bytes(key, 6, current_key); - - if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - memcmp(sec_trailer->key_a, current_key, 6) == 0) { - if(!mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { - mf_classic_set_key_not_found(data, i, MfClassicKeyA); - FURI_LOG_D(TAG, "Key %dA not found in attack", i); - } - } - furi_hal_nfc_sleep(); - deactivated = true; - } - if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { - if(mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 - FURI_LOG_D(TAG, "Key B found: %012llX", key); - mf_classic_set_key_found(data, i, MfClassicKeyB, key); - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); - } - deactivated = true; //-V1048 - } else { - // If the key B is marked as found and matches the searching key, invalidate it - MfClassicSectorTrailer* sec_trailer = - mf_classic_get_sector_trailer_by_sector(data, i); - - uint8_t current_key[6]; - nfc_util_num2bytes(key, 6, current_key); - - if(mf_classic_is_key_found(data, i, MfClassicKeyB) && - memcmp(sec_trailer->key_b, current_key, 6) == 0) { - if(!mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 - mf_classic_set_key_not_found(data, i, MfClassicKeyB); - FURI_LOG_D(TAG, "Key %dB not found in attack", i); - } - furi_hal_nfc_sleep(); - deactivated = true; //-V1048 - } - } - if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - mf_classic_is_key_found(data, i, MfClassicKeyB)) - break; - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - } else { - if(!card_removed_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_removed_notified = true; - card_found_notified = false; - } - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - } - prev_key = key; - } - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - mf_classic_read_sector(&tx_rx, data, i); - mf_classic_dict_rewind(dict); - } - if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - } else { - nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); - } -} - -void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - MfClassicEmulator emulator = { - .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), - .data = nfc_worker->dev_data->mf_classic_data, - .data_changed = false, - }; - NfcaSignal* nfca_signal = nfca_signal_alloc(); - tx_rx.nfca_signal = nfca_signal; - - rfal_platform_spi_acquire(); - - furi_hal_nfc_listen_start(nfc_data); - while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 - if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - if(!mf_classic_emulator(&emulator, &tx_rx, false)) { - furi_hal_nfc_listen_start(nfc_data); - } - } - } - if(emulator.data_changed) { - nfc_worker->dev_data->mf_classic_data = emulator.data; - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - } - emulator.data_changed = false; - } - - nfca_signal_free(nfca_signal); - - rfal_platform_spi_release(); -} - -void nfc_worker_write_mf_classic(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - bool card_found_notified = false; - FuriHalNfcDevData nfc_data = {}; - MfClassicData* src_data = &nfc_worker->dev_data->mf_classic_data; - MfClassicData dest_data = *src_data; - - while(nfc_worker->state == NfcWorkerStateMfClassicWrite) { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - } - furi_hal_nfc_sleep(); - - FURI_LOG_I(TAG, "Check low level nfc data"); - if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != - 0) { - FURI_LOG_E(TAG, "Wrong card"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - FURI_LOG_I(TAG, "Check mf classic type"); - MfClassicType type = - mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); - if(type != nfc_worker->dev_data->mf_classic_data.type) { - FURI_LOG_E(TAG, "Wrong mf classic type"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - // Set blocks not read - mf_classic_set_sector_data_not_read(&dest_data); - FURI_LOG_I(TAG, "Updating card sectors"); - uint8_t total_sectors = mf_classic_get_total_sectors_num(type); - bool write_success = true; - for(uint8_t i = 0; i < total_sectors; i++) { - FURI_LOG_I(TAG, "Reading sector %d", i); - mf_classic_read_sector(&tx_rx, &dest_data, i); - bool old_data_read = mf_classic_is_sector_data_read(src_data, i); - bool new_data_read = mf_classic_is_sector_data_read(&dest_data, i); - if(old_data_read != new_data_read) { - FURI_LOG_E(TAG, "Failed to update sector %d", i); - write_success = false; - break; - } - if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; - if(!mf_classic_write_sector(&tx_rx, &dest_data, src_data, i)) { - FURI_LOG_E(TAG, "Failed to write %d sector", i); - write_success = false; - break; - } - } - if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; - if(write_success) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - break; - } else { - nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); - break; - } - - } else { - if(card_found_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_found_notified = false; - } - } - furi_delay_ms(300); - } -} - -void nfc_worker_update_mf_classic(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - bool card_found_notified = false; - FuriHalNfcDevData nfc_data = {}; - MfClassicData* old_data = &nfc_worker->dev_data->mf_classic_data; - MfClassicData new_data = *old_data; - - while(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - } - furi_hal_nfc_sleep(); - - FURI_LOG_I(TAG, "Check low level nfc data"); - if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != - 0) { - FURI_LOG_E(TAG, "Low level nfc data mismatch"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - FURI_LOG_I(TAG, "Check MF classic type"); - MfClassicType type = - mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); - if(type != nfc_worker->dev_data->mf_classic_data.type) { - FURI_LOG_E(TAG, "MF classic type mismatch"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - // Set blocks not read - mf_classic_set_sector_data_not_read(&new_data); - FURI_LOG_I(TAG, "Updating card sectors"); - uint8_t total_sectors = mf_classic_get_total_sectors_num(type); - bool update_success = true; - for(uint8_t i = 0; i < total_sectors; i++) { - FURI_LOG_I(TAG, "Reading sector %d", i); - mf_classic_read_sector(&tx_rx, &new_data, i); - bool old_data_read = mf_classic_is_sector_data_read(old_data, i); - bool new_data_read = mf_classic_is_sector_data_read(&new_data, i); - if(old_data_read != new_data_read) { - FURI_LOG_E(TAG, "Failed to update sector %d", i); - update_success = false; - break; - } - if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; - } - if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; - - // Check updated data - if(update_success) { - *old_data = new_data; - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - break; - } - } else { - if(card_found_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_found_notified = false; - } - } - furi_delay_ms(300); - } -} - -void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - MfUltralightData* data = &nfc_worker->dev_data->mf_ul_data; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - FuriHalNfcTxRxContext tx_rx = {}; - MfUltralightReader reader = {}; - mf_ul_reset(data); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - uint32_t key = 0; - uint16_t pack = 0; - while(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { - furi_hal_nfc_sleep(); - if(furi_hal_nfc_detect(nfc_data, 300) && nfc_data->type == FuriHalNfcTypeA) { - if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - if(data->auth_method == MfUltralightAuthMethodManual || - data->auth_method == MfUltralightAuthMethodAuto) { - nfc_worker->callback(NfcWorkerEventMfUltralightPassKey, nfc_worker->context); - key = nfc_util_bytes2num(data->auth_key, 4); - } else if(data->auth_method == MfUltralightAuthMethodAmeebo) { - key = mf_ul_pwdgen_amiibo(nfc_data); - } else if(data->auth_method == MfUltralightAuthMethodXiaomi) { - key = mf_ul_pwdgen_xiaomi(nfc_data); - } else { - FURI_LOG_E(TAG, "Incorrect auth method"); - break; - } - - data->auth_success = mf_ultralight_authenticate(&tx_rx, key, &pack); - - if(!data->auth_success) { - // Reset card - furi_hal_nfc_sleep(); - if(!furi_hal_nfc_activate_nfca(300, NULL)) { - nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); - break; - } - } - - mf_ul_read_card(&tx_rx, &reader, data); - if(data->auth_success) { - MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); - if(config_pages != NULL) { - config_pages->auth_data.pwd.value = REVERSE_BYTES_U32(key); - config_pages->auth_data.pack.value = pack; - } - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - break; - } else { - nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); - break; - } - } else { - nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); - furi_delay_ms(10); - } - } else { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - furi_delay_ms(10); - } - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -static void nfc_worker_reader_analyzer_callback(ReaderAnalyzerEvent event, void* context) { - furi_assert(context); - NfcWorker* nfc_worker = context; - - if((nfc_worker->state == NfcWorkerStateAnalyzeReader) && - (event == ReaderAnalyzerEventMfkeyCollected)) { - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventDetectReaderMfkeyCollected, nfc_worker->context); - } - } -} - -void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - FuriHalNfcTxRxContext tx_rx = {}; - - ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; - FuriHalNfcDevData* nfc_data = NULL; - if(nfc_worker->dev_data->protocol == NfcDeviceProtocolMifareClassic) { - nfc_data = &nfc_worker->dev_data->nfc_data; - reader_analyzer_set_nfc_data(reader_analyzer, nfc_data); - } else { - nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); - } - MfClassicEmulator emulator = { - .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), - .data = nfc_worker->dev_data->mf_classic_data, - .data_changed = false, - }; - NfcaSignal* nfca_signal = nfca_signal_alloc(); - tx_rx.nfca_signal = nfca_signal; - reader_analyzer_prepare_tx_rx(reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeMfkey); - reader_analyzer_set_callback(reader_analyzer, nfc_worker_reader_analyzer_callback, nfc_worker); - - rfal_platform_spi_acquire(); - - FURI_LOG_D(TAG, "Start reader analyzer"); - - uint8_t reader_no_data_received_cnt = 0; - bool reader_no_data_notified = true; - - while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - furi_hal_nfc_listen_start(nfc_data); - if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - if(reader_no_data_notified) { - nfc_worker->callback(NfcWorkerEventDetectReaderDetected, nfc_worker->context); - } - reader_no_data_received_cnt = 0; - reader_no_data_notified = false; - NfcProtocol protocol = - reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); - if(protocol == NfcDeviceProtocolMifareClassic) { - if(!mf_classic_emulator(&emulator, &tx_rx, true)) { - furi_hal_nfc_listen_start(nfc_data); - } - } - } else { - reader_no_data_received_cnt++; - if(!reader_no_data_notified && (reader_no_data_received_cnt > 5)) { - nfc_worker->callback(NfcWorkerEventDetectReaderLost, nfc_worker->context); - reader_no_data_received_cnt = 0; - reader_no_data_notified = true; - } - FURI_LOG_D(TAG, "No data from reader"); - continue; - } - furi_delay_ms(1); - } - - rfal_platform_spi_release(); - - reader_analyzer_stop(nfc_worker->reader_analyzer); - - nfca_signal_free(nfca_signal); -} diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h deleted file mode 100644 index f9f5900bbd..0000000000 --- a/lib/nfc/nfc_worker.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include "nfc_device.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct NfcWorker NfcWorker; - -typedef enum { - // Init states - NfcWorkerStateNone, - NfcWorkerStateReady, - // Main worker states - NfcWorkerStateRead, - NfcWorkerStateUidEmulate, - NfcWorkerStateMfUltralightEmulate, - NfcWorkerStateMfClassicEmulate, - NfcWorkerStateMfClassicWrite, - NfcWorkerStateMfClassicUpdate, - NfcWorkerStateReadMfUltralightReadAuth, - NfcWorkerStateMfClassicDictAttack, - NfcWorkerStateAnalyzeReader, - NfcWorkerStateNfcVEmulate, - NfcWorkerStateNfcVUnlock, - NfcWorkerStateNfcVUnlockAndSave, - NfcWorkerStateNfcVSniff, - // Debug - NfcWorkerStateEmulateApdu, - NfcWorkerStateField, - // Transition - NfcWorkerStateStop, -} NfcWorkerState; - -typedef enum { - // Reserve first 50 events for application events - NfcWorkerEventReserved = 50, - - // Nfc read events - NfcWorkerEventReadUidNfcB, - NfcWorkerEventReadUidNfcV, - NfcWorkerEventReadUidNfcF, - NfcWorkerEventReadUidNfcA, - NfcWorkerEventReadMfUltralight, - NfcWorkerEventReadMfDesfire, - NfcWorkerEventReadMfClassicDone, - NfcWorkerEventReadMfClassicLoadKeyCache, - NfcWorkerEventReadMfClassicDictAttackRequired, - NfcWorkerEventReadNfcV, - - // Nfc worker common events - NfcWorkerEventSuccess, - NfcWorkerEventFail, - NfcWorkerEventAborted, - NfcWorkerEventCardDetected, - NfcWorkerEventNoCardDetected, - NfcWorkerEventWrongCardDetected, - - // Read Mifare Classic events - NfcWorkerEventNoDictFound, - NfcWorkerEventNewSector, - NfcWorkerEventNewDictKeyBatch, - NfcWorkerEventFoundKeyA, - NfcWorkerEventFoundKeyB, - NfcWorkerEventKeyAttackStart, - NfcWorkerEventKeyAttackStop, - NfcWorkerEventKeyAttackNextSector, - - // Write Mifare Classic events - NfcWorkerEventWrongCard, - - // Detect Reader events - NfcWorkerEventDetectReaderDetected, - NfcWorkerEventDetectReaderLost, - NfcWorkerEventDetectReaderMfkeyCollected, - - // Mifare Ultralight events - NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key - NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command - NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key - NfcWorkerEventNfcVCommandExecuted, - NfcWorkerEventNfcVContentChanged, -} NfcWorkerEvent; - -typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); - -NfcWorker* nfc_worker_alloc(); - -NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker); - -void nfc_worker_free(NfcWorker* nfc_worker); - -void nfc_worker_start( - NfcWorker* nfc_worker, - NfcWorkerState state, - NfcDeviceData* dev_data, - NfcWorkerCallback callback, - void* context); - -void nfc_worker_stop(NfcWorker* nfc_worker); -void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); -void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); -void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h deleted file mode 100644 index b678573ec0..0000000000 --- a/lib/nfc/nfc_worker_i.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "nfc_worker.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct NfcWorker { - FuriThread* thread; - Storage* storage; - Stream* dict_stream; - - NfcDeviceData* dev_data; - - NfcWorkerCallback callback; - void* context; - - NfcWorkerState state; - - ReaderAnalyzer* reader_analyzer; -}; - -void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state); - -int32_t nfc_worker_task(void* context); - -void nfc_worker_read(NfcWorker* nfc_worker); - -void nfc_worker_read_type(NfcWorker* nfc_worker); - -void nfc_worker_emulate_uid(NfcWorker* nfc_worker); - -void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker); - -void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker); - -void nfc_worker_write_mf_classic(NfcWorker* nfc_worker); - -void nfc_worker_update_mf_classic(NfcWorker* nfc_worker); - -void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker); - -void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker); - -void nfc_worker_mf_ul_auth_attack(NfcWorker* nfc_worker); - -void nfc_worker_emulate_apdu(NfcWorker* nfc_worker); - -void nfc_worker_analyze_reader(NfcWorker* nfc_worker); diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c deleted file mode 100644 index edcc0d0c7c..0000000000 --- a/lib/nfc/parsers/all_in_one.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "nfc_supported_card.h" -#include "all_in_one.h" - -#include -#include - -#include - -#define ALL_IN_ONE_LAYOUT_UNKNOWN 0 -#define ALL_IN_ONE_LAYOUT_A 1 -#define ALL_IN_ONE_LAYOUT_D 2 -#define ALL_IN_ONE_LAYOUT_E2 3 -#define ALL_IN_ONE_LAYOUT_E3 4 -#define ALL_IN_ONE_LAYOUT_E5 5 -#define ALL_IN_ONE_LAYOUT_2 6 - -uint8_t all_in_one_get_layout(NfcDeviceData* dev_data) { - // I absolutely hate what's about to happen here. - - // Switch on the second half of the third byte of page 5 - FURI_LOG_I("all_in_one", "Layout byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 2]); - FURI_LOG_I( - "all_in_one", "Layout half-byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 3] & 0x0F); - switch(dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F) { - // If it is A, the layout type is a type A layout - case 0x0A: - return ALL_IN_ONE_LAYOUT_A; - case 0x0D: - return ALL_IN_ONE_LAYOUT_D; - case 0x02: - return ALL_IN_ONE_LAYOUT_2; - default: - FURI_LOG_I( - "all_in_one", - "Unknown layout type: %d", - dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F); - return ALL_IN_ONE_LAYOUT_UNKNOWN; - } -} - -bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - UNUSED(nfc_worker); - // If this is a all_in_one pass, first 2 bytes of page 4 are 0x45 0xD9 - MfUltralightReader reader = {}; - MfUltralightData data = {}; - - if(!mf_ul_read_card(tx_rx, &reader, &data)) { - return false; - } else { - if(data.data[4 * 4] == 0x45 && data.data[4 * 4 + 1] == 0xD9) { - FURI_LOG_I("all_in_one", "Pass verified"); - return true; - } - } - return false; -} - -bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - MfUltralightReader reader = {}; - MfUltralightData data = {}; - if(!mf_ul_read_card(tx_rx, &reader, &data)) { - return false; - } else { - memcpy(&nfc_worker->dev_data->mf_ul_data, &data, sizeof(data)); - FURI_LOG_I("all_in_one", "Card read"); - return true; - } -} - -bool all_in_one_parser_parse(NfcDeviceData* dev_data) { - if(dev_data->mf_ul_data.data[4 * 4] != 0x45 || dev_data->mf_ul_data.data[4 * 4 + 1] != 0xD9) { - FURI_LOG_I("all_in_one", "Pass not verified"); - return false; - } - - uint8_t ride_count = 0; - uint32_t serial = 0; - if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_A) { - // If the layout is A then the ride count is stored in the first byte of page 8 - ride_count = dev_data->mf_ul_data.data[4 * 8]; - } else if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_D) { - // If the layout is D, the ride count is stored in the second byte of page 9 - ride_count = dev_data->mf_ul_data.data[4 * 9 + 1]; - } else { - FURI_LOG_I("all_in_one", "Unknown layout: %d", all_in_one_get_layout(dev_data)); - ride_count = 137; - } - - // I hate this with a burning passion. - - // The number starts at the second half of the third byte on page 4, and is 32 bits long - // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte - // B8 17 A2 A4 BD becomes 81 7A 2A 4B - serial = - (dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 | - dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | dev_data->mf_ul_data.data[4 * 4 + 4] << 12 | - dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4); - - // Format string for rides count - furi_string_printf( - dev_data->parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count); - return true; -} diff --git a/lib/nfc/parsers/all_in_one.h b/lib/nfc/parsers/all_in_one.h deleted file mode 100644 index 9b646d4dc3..0000000000 --- a/lib/nfc/parsers/all_in_one.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool all_in_one_parser_parse(NfcDeviceData* dev_data); \ No newline at end of file diff --git a/lib/nfc/parsers/nfc_supported_card.c b/lib/nfc/parsers/nfc_supported_card.c deleted file mode 100644 index 153d4d3c51..0000000000 --- a/lib/nfc/parsers/nfc_supported_card.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "nfc_supported_card.h" - -#include "plantain_parser.h" -#include "troika_parser.h" -#include "plantain_4k_parser.h" -#include "troika_4k_parser.h" -#include "two_cities.h" -#include "all_in_one.h" -#include "opal.h" - -NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = { - [NfcSupportedCardTypePlantain] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = plantain_parser_verify, - .read = plantain_parser_read, - .parse = plantain_parser_parse, - }, - [NfcSupportedCardTypeTroika] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = troika_parser_verify, - .read = troika_parser_read, - .parse = troika_parser_parse, - }, - [NfcSupportedCardTypePlantain4K] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = plantain_4k_parser_verify, - .read = plantain_4k_parser_read, - .parse = plantain_4k_parser_parse, - }, - [NfcSupportedCardTypeTroika4K] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = troika_4k_parser_verify, - .read = troika_4k_parser_read, - .parse = troika_4k_parser_parse, - }, - [NfcSupportedCardTypeTwoCities] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = two_cities_parser_verify, - .read = two_cities_parser_read, - .parse = two_cities_parser_parse, - }, - [NfcSupportedCardTypeAllInOne] = - { - .protocol = NfcDeviceProtocolMifareUl, - .verify = all_in_one_parser_verify, - .read = all_in_one_parser_read, - .parse = all_in_one_parser_parse, - }, - [NfcSupportedCardTypeOpal] = - { - .protocol = NfcDeviceProtocolMifareDesfire, - .verify = stub_parser_verify_read, - .read = stub_parser_verify_read, - .parse = opal_parser_parse, - }, - -}; - -bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) { - furi_assert(dev_data); - - bool card_parsed = false; - for(size_t i = 0; i < COUNT_OF(nfc_supported_card); i++) { - if(nfc_supported_card[i].parse(dev_data)) { - card_parsed = true; - break; - } - } - - return card_parsed; -} - -bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - UNUSED(nfc_worker); - UNUSED(tx_rx); - return false; -} diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h deleted file mode 100644 index 2e8c48a87a..0000000000 --- a/lib/nfc/parsers/nfc_supported_card.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include "../nfc_worker.h" -#include "../nfc_device.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - NfcSupportedCardTypePlantain, - NfcSupportedCardTypeTroika, - NfcSupportedCardTypePlantain4K, - NfcSupportedCardTypeTroika4K, - NfcSupportedCardTypeTwoCities, - NfcSupportedCardTypeAllInOne, - NfcSupportedCardTypeOpal, - - NfcSupportedCardTypeEnd, -} NfcSupportedCardType; - -typedef bool (*NfcSupportedCardVerify)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -typedef bool (*NfcSupportedCardRead)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -typedef bool (*NfcSupportedCardParse)(NfcDeviceData* dev_data); - -typedef struct { - NfcProtocol protocol; - NfcSupportedCardVerify verify; - NfcSupportedCardRead read; - NfcSupportedCardParse parse; -} NfcSupportedCard; - -extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd]; - -bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data); - -// stub_parser_verify_read does nothing, and always reports that it does not -// support the card. This is needed for DESFire card parsers which can't -// provide keys, and only use NfcSupportedCard->parse. -bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/parsers/opal.c b/lib/nfc/parsers/opal.c deleted file mode 100644 index 2a6b5d1221..0000000000 --- a/lib/nfc/parsers/opal.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * opal.c - Parser for Opal card (Sydney, Australia). - * - * Copyright 2023 Michael Farrell - * - * This will only read "standard" MIFARE DESFire-based Opal cards. Free travel - * cards (including School Opal cards, veteran, vision-impaired persons and - * TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C - * cards and not supported. - * - * Reference: https://github.com/metrodroid/metrodroid/wiki/Opal - * - * Note: The card values are all little-endian (like Flipper), but the above - * reference was originally written based on Java APIs, which are big-endian. - * This implementation presumes a little-endian system. - * - * 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 . - */ -#include "nfc_supported_card.h" -#include "opal.h" - -#include -#include -#include - -#include - -static const uint8_t opal_aid[3] = {0x31, 0x45, 0x53}; -static const char* opal_modes[5] = - {"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"}; -static const char* opal_usages[14] = { - "New / Unused", - "Tap on: new journey", - "Tap on: transfer from same mode", - "Tap on: transfer from other mode", - "", // Manly Ferry: new journey - "", // Manly Ferry: transfer from ferry - "", // Manly Ferry: transfer from other - "Tap off: distance fare", - "Tap off: flat fare", - "Automated tap off: failed to tap off", - "Tap off: end of trip without start", - "Tap off: reversal", - "Tap on: rejected", - "Unknown usage", -}; - -// Opal file 0x7 structure. Assumes a little-endian CPU. -typedef struct __attribute__((__packed__)) { - uint32_t serial : 32; - uint8_t check_digit : 4; - bool blocked : 1; - uint16_t txn_number : 16; - int32_t balance : 21; - uint16_t days : 15; - uint16_t minutes : 11; - uint8_t mode : 3; - uint16_t usage : 4; - bool auto_topup : 1; - uint8_t weekly_journeys : 4; - uint16_t checksum : 16; -} OpalFile; - -static_assert(sizeof(OpalFile) == 16); - -// Converts an Opal timestamp to FuriHalRtcDateTime. -// -// Opal measures days since 1980-01-01 and minutes since midnight, and presumes -// all days are 1440 minutes. -void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) { - if(!out) return; - uint16_t diy; - out->year = 1980; - out->month = 1; - // 1980-01-01 is a Tuesday - out->weekday = ((days + 1) % 7) + 1; - out->hour = minutes / 60; - out->minute = minutes % 60; - out->second = 0; - - // What year is it? - for(;;) { - diy = furi_hal_rtc_get_days_per_year(out->year); - if(days < diy) break; - days -= diy; - out->year++; - } - - // 1-index the day of the year - days++; - // What month is it? - bool is_leap = furi_hal_rtc_is_leap_year(out->year); - - for(;;) { - uint8_t dim = furi_hal_rtc_get_days_per_month(is_leap, out->month); - if(days <= dim) break; - days -= dim; - out->month++; - } - - out->day = days; -} - -bool opal_parser_parse(NfcDeviceData* dev_data) { - if(dev_data->protocol != NfcDeviceProtocolMifareDesfire) { - return false; - } - - MifareDesfireApplication* app = mf_df_get_application(&dev_data->mf_df_data, &opal_aid); - if(app == NULL) { - return false; - } - MifareDesfireFile* f = mf_df_get_file(app, 0x07); - if(f == NULL || f->type != MifareDesfireFileTypeStandard || f->settings.data.size != 16 || - !f->contents) { - return false; - } - - OpalFile* o = (OpalFile*)f->contents; - - uint8_t serial2 = o->serial / 10000000; - uint16_t serial3 = (o->serial / 1000) % 10000; - uint16_t serial4 = (o->serial % 1000); - - if(o->check_digit > 9) { - return false; - } - - char* sign = ""; - if(o->balance < 0) { - // Negative balance. Make this a positive value again and record the - // sign separately, because then we can handle balances of -99..-1 - // cents, as the "dollars" division below would result in a positive - // zero value. - o->balance = abs(o->balance); //-V1081 - sign = "-"; - } - uint8_t cents = o->balance % 100; - int32_t dollars = o->balance / 100; - - FuriHalRtcDateTime timestamp; - opal_date_time_to_furi(o->days, o->minutes, ×tamp); - - if(o->mode >= 3) { - // 3..7 are "reserved", but we use 4 to indicate the Manly Ferry. - o->mode = 3; - } - - if(o->usage >= 4 && o->usage <= 6) { - // Usages 4..6 associated with the Manly Ferry, which correspond to - // usages 1..3 for other modes. - o->usage -= 3; - o->mode = 4; - } - - const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); //-V547 - const char* usage_str = (o->usage <= 12 ? opal_usages[o->usage] : opal_usages[13]); - - furi_string_printf( - dev_data->parsed_data, - "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", - sign, - dollars, - cents, - serial2, - serial3, - serial4, - o->check_digit, - mode_str, - usage_str); - FuriString* timestamp_str = furi_string_alloc(); - locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); - furi_string_cat(dev_data->parsed_data, timestamp_str); - furi_string_cat_str(dev_data->parsed_data, " at "); - - locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false); - furi_string_cat(dev_data->parsed_data, timestamp_str); - - furi_string_free(timestamp_str); - furi_string_cat_printf( - dev_data->parsed_data, - "\nWeekly journeys: %hhu, Txn #%hu\n", - o->weekly_journeys, - o->txn_number); - - if(o->auto_topup) { - furi_string_cat_str(dev_data->parsed_data, "Auto-topup enabled\n"); - } - if(o->blocked) { - furi_string_cat_str(dev_data->parsed_data, "Card blocked\n"); - } - return true; -} diff --git a/lib/nfc/parsers/opal.h b/lib/nfc/parsers/opal.h deleted file mode 100644 index 42caf9a179..0000000000 --- a/lib/nfc/parsers/opal.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool opal_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c deleted file mode 100644 index 19da0b5ebb..0000000000 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ /dev/null @@ -1,124 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -#include - -static const MfClassicAuthContext plantain_keys_4k[] = { - {.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF}, - {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, - {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, - {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, - {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, - {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, - {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, - {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, - {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, - {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, - {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, - {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, - {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, - {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, - {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, - {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, - {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, - {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, - {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, - {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, - {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, - {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, - {.sector = 30, .key_a = 0x0eb23cc8110b, .key_b = 0x04dc35277635}, - {.sector = 31, .key_a = 0xbc4580b7f20b, .key_b = 0xd0a4131fb290}, - {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, - {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, - {.sector = 34, .key_a = 0xfd8705e721b0, .key_b = 0x296fc317a513}, - {.sector = 35, .key_a = 0x22052b480d11, .key_b = 0xe19504c39461}, - {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, - {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, - {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, - {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, -}; - -bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { - return false; - } - - uint8_t sector = 8; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Plant4K", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { - FURI_LOG_D("Plant4K", "Sector %d verified", sector); - return true; - } - return false; -} - -bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(plantain_keys_4k); i++) { - mf_classic_reader_add_sector( - &reader, - plantain_keys_4k[i].sector, - plantain_keys_4k[i].key_a, - plantain_keys_4k[i].key_b); - FURI_LOG_T("plant4k", "Added sector %d", plantain_keys_4k[i].sector); - } - for(int i = 0; i < 5; i++) { - if(mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40) { - return true; - } - } - return false; -} - -bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != plantain_keys_4k[8].key_a) return false; - - // Point to block 0 of sector 4, value 0 - uint8_t* temp_ptr = &data->block[4 * 4].value[0]; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = &data->block[0 * 4].value[0]; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t - uint64_t card_number = 0; - for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; - } - - furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); - - return true; -} diff --git a/lib/nfc/parsers/plantain_4k_parser.h b/lib/nfc/parsers/plantain_4k_parser.h deleted file mode 100644 index 29998af15e..0000000000 --- a/lib/nfc/parsers/plantain_4k_parser.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c deleted file mode 100644 index 2e4091dda1..0000000000 --- a/lib/nfc/parsers/plantain_parser.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -#include - -static const MfClassicAuthContext plantain_keys[] = { - {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, - {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, - {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, - {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, - {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, - {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, - {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, - {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, -}; - -bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { - return false; - } - - uint8_t sector = 8; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Plant", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { - FURI_LOG_D("Plant", "Sector %d verified", sector); - return true; - } - return false; -} - -bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(plantain_keys); i++) { - mf_classic_reader_add_sector( - &reader, plantain_keys[i].sector, plantain_keys[i].key_a, plantain_keys[i].key_b); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; -} - -uint8_t plantain_calculate_luhn(uint64_t number) { - // No. - UNUSED(number); - return 0; -} - -bool plantain_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != plantain_keys[8].key_a) return false; - - // Point to block 0 of sector 4, value 0 - uint8_t* temp_ptr = &data->block[4 * 4].value[0]; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = &data->block[0 * 4].value[0]; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t - uint64_t card_number = 0; - for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; - } - - furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); - - return true; -} diff --git a/lib/nfc/parsers/plantain_parser.h b/lib/nfc/parsers/plantain_parser.h deleted file mode 100644 index 1af8c50657..0000000000 --- a/lib/nfc/parsers/plantain_parser.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_parser_parse(NfcDeviceData* dev_data); - -uint8_t plantain_calculate_luhn(uint64_t number); diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c deleted file mode 100644 index d248feb17c..0000000000 --- a/lib/nfc/parsers/troika_4k_parser.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -static const MfClassicAuthContext troika_4k_keys[] = { - {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, - {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5}, - {.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698}, - {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb}, - {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, - {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, - {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d}, - {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b}, - {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0}, - {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75}, - {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408}, - {.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df}, - {.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9}, - {.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4}, - {.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216}, - {.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba}, - {.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252}, - {.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01}, - {.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994}, - {.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6}, - {.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32}, - {.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3}, - {.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5}, - {.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a}, - {.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80}, - {.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d}, - {.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2}, - {.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324}, - {.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae}, - {.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda}, - {.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d}, - {.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465}, - {.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4}, - {.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70}, -}; - -bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { - return false; - } - - uint8_t sector = 11; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Troika", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { - FURI_LOG_D("Troika", "Sector %d verified", sector); - return true; - } - return false; -} - -bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(troika_4k_keys); i++) { - mf_classic_reader_add_sector( - &reader, troika_4k_keys[i].sector, troika_4k_keys[i].key_a, troika_4k_keys[i].key_b); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; -} - -bool troika_4k_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != troika_4k_keys[4].key_a) return false; - - // Verify card type - if(data->type != MfClassicType4k) return false; - - uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; - uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[2]; - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; - } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); - - return true; -} diff --git a/lib/nfc/parsers/troika_4k_parser.h b/lib/nfc/parsers/troika_4k_parser.h deleted file mode 100644 index c1d6f01d3a..0000000000 --- a/lib/nfc/parsers/troika_4k_parser.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c deleted file mode 100644 index 6d8ae98f3c..0000000000 --- a/lib/nfc/parsers/troika_parser.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -static const MfClassicAuthContext troika_keys[] = { - {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, - {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5}, - {.sector = 5, .key_a = 0xfbc2793d540b, .key_b = 0xd3a297dc2698}, - {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dba}, - {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, - {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, - {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d}, - {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b}, - {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0}, - {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75}, - {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, -}; - -bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { - return false; - } - - uint8_t sector = 11; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Troika", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { - FURI_LOG_D("Troika", "Sector %d verified", sector); - return true; - } - return false; -} - -bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - - for(size_t i = 0; i < COUNT_OF(troika_keys); i++) { - mf_classic_reader_add_sector( - &reader, troika_keys[i].sector, troika_keys[i].key_a, troika_keys[i].key_b); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; -} - -bool troika_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - bool troika_parsed = false; - - do { - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != troika_keys[8].key_a) break; - - // Verify card type - if(data->type != MfClassicType1k) break; - - // Parse data - uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; - uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[2]; - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; - } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); - troika_parsed = true; - } while(false); - - return troika_parsed; -} diff --git a/lib/nfc/parsers/troika_parser.h b/lib/nfc/parsers/troika_parser.h deleted file mode 100644 index 2aae48d29a..0000000000 --- a/lib/nfc/parsers/troika_parser.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c deleted file mode 100644 index d6d4279ddd..0000000000 --- a/lib/nfc/parsers/two_cities.c +++ /dev/null @@ -1,146 +0,0 @@ -#include "nfc_supported_card.h" -#include "plantain_parser.h" // For plantain-specific stuff - -#include -#include - -#include - -static const MfClassicAuthContext two_cities_keys_4k[] = { - {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, - {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, - {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, - {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, - {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, - {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, - {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, - {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, - {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, - {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, - {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, - {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, - {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, - {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, - {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, - {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, - {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, - {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, - {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, - {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, - {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, - {.sector = 30, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, - {.sector = 31, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, - {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, - {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, - {.sector = 34, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 35, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, - {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, - {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, - {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, -}; - -bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { - return false; - } - - uint8_t sector = 4; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("2cities", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0xe56ac127dd45, MfClassicKeyA)) { - FURI_LOG_D("2cities", "Sector %d verified", sector); - return true; - } - return false; -} - -bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(two_cities_keys_4k); i++) { - mf_classic_reader_add_sector( - &reader, - two_cities_keys_4k[i].sector, - two_cities_keys_4k[i].key_a, - two_cities_keys_4k[i].key_b); - FURI_LOG_T("2cities", "Added sector %d", two_cities_keys_4k[i].sector); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; -} - -bool two_cities_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != two_cities_keys_4k[4].key_a) return false; - - // ===== - // PLANTAIN - // ===== - - // Point to block 0 of sector 4, value 0 - uint8_t* temp_ptr = &data->block[4 * 4].value[0]; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = &data->block[0 * 4].value[0]; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t - uint64_t card_number = 0; - for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; - } - - // ===== - // --PLANTAIN-- - // ===== - // TROIKA - // ===== - - uint8_t* troika_temp_ptr = &data->block[8 * 4 + 1].value[5]; - uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25; - troika_temp_ptr = &data->block[8 * 4].value[3]; - uint32_t troika_number = 0; - for(size_t i = 0; i < 4; i++) { - troika_number <<= 8; - troika_number |= troika_temp_ptr[i]; - } - troika_number >>= 4; - - furi_string_printf( - dev_data->parsed_data, - "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", - card_number, - balance, - troika_number, - troika_balance); - - return true; -} diff --git a/lib/nfc/parsers/two_cities.h b/lib/nfc/parsers/two_cities.h deleted file mode 100644 index e735bea8e1..0000000000 --- a/lib/nfc/parsers/two_cities.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool two_cities_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/protocols/crypto1.c b/lib/nfc/protocols/crypto1.c deleted file mode 100644 index f59651cf45..0000000000 --- a/lib/nfc/protocols/crypto1.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "crypto1.h" -#include "nfc_util.h" -#include - -// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git - -#define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -#define LF_POLY_ODD (0x29CE5C) -#define LF_POLY_EVEN (0x870804) - -#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) - -void crypto1_reset(Crypto1* crypto1) { - furi_assert(crypto1); - crypto1->even = 0; - crypto1->odd = 0; -} - -void crypto1_init(Crypto1* crypto1, uint64_t key) { - furi_assert(crypto1); - crypto1->even = 0; - crypto1->odd = 0; - for(int8_t i = 47; i > 0; i -= 2) { - crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); - crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); - } -} - -uint32_t crypto1_filter(uint32_t in) { - uint32_t out = 0; - out = 0xf22c0 >> (in & 0xf) & 16; - out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; - out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; - out |= 0x1e458 >> (in >> 12 & 0xf) & 2; - out |= 0x0d938 >> (in >> 16 & 0xf) & 1; - return FURI_BIT(0xEC57E80A, out); -} - -uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { - furi_assert(crypto1); - uint8_t out = crypto1_filter(crypto1->odd); - uint32_t feed = out & (!!is_encrypted); - feed ^= !!in; - feed ^= LF_POLY_ODD & crypto1->odd; - feed ^= LF_POLY_EVEN & crypto1->even; - crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); - - FURI_SWAP(crypto1->odd, crypto1->even); - return out; -} - -uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { - furi_assert(crypto1); - uint8_t out = 0; - for(uint8_t i = 0; i < 8; i++) { - out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; - } - return out; -} - -uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { - furi_assert(crypto1); - uint32_t out = 0; - for(uint8_t i = 0; i < 32; i++) { - out |= crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); - } - return out; -} - -uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - - return SWAPENDIAN(x); -} - -void crypto1_decrypt( - Crypto1* crypto, - uint8_t* encrypted_data, - uint16_t encrypted_data_bits, - uint8_t* decrypted_data) { - furi_assert(crypto); - furi_assert(encrypted_data); - furi_assert(decrypted_data); - - if(encrypted_data_bits < 8) { - uint8_t decrypted_byte = 0; - decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0; - decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1; - decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2; - decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3; - decrypted_data[0] = decrypted_byte; - } else { - for(size_t i = 0; i < encrypted_data_bits / 8; i++) { - decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; - } - } -} - -void crypto1_encrypt( - Crypto1* crypto, - uint8_t* keystream, - uint8_t* plain_data, - uint16_t plain_data_bits, - uint8_t* encrypted_data, - uint8_t* encrypted_parity) { - furi_assert(crypto); - furi_assert(plain_data); - furi_assert(encrypted_data); - furi_assert(encrypted_parity); - - if(plain_data_bits < 8) { - encrypted_data[0] = 0; - for(size_t i = 0; i < plain_data_bits; i++) { - encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; - } - } else { - memset(encrypted_parity, 0, plain_data_bits / 8 + 1); - for(uint8_t i = 0; i < plain_data_bits / 8; i++) { - encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ - plain_data[i]; - encrypted_parity[i / 8] |= - (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01) - << (7 - (i & 0x0007))); - } - } -} diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c deleted file mode 100644 index 4c4ac856b1..0000000000 --- a/lib/nfc/protocols/emv.c +++ /dev/null @@ -1,444 +0,0 @@ -#include "emv.h" - -#include - -#define TAG "Emv" - -const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information -const PDOLValue pdol_term_type = {0x9F5A, {0x00}}; // Terminal transaction type -const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator -const PDOLValue pdol_term_trans_qualifies = { - 0x9F66, - {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers -const PDOLValue pdol_addtnl_term_qualifies = { - 0x9F40, - {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers -const PDOLValue pdol_amount_authorise = { - 0x9F02, - {0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised -const PDOLValue pdol_amount = {0x9F03, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Amount -const PDOLValue pdol_country_code = {0x9F1A, {0x01, 0x24}}; // Terminal country code -const PDOLValue pdol_currency_code = {0x5F2A, {0x01, 0x24}}; // Transaction currency code -const PDOLValue pdol_term_verification = { - 0x95, - {0x00, 0x00, 0x00, 0x00, 0x00}}; // Terminal verification results -const PDOLValue pdol_transaction_date = {0x9A, {0x19, 0x01, 0x01}}; // Transaction date -const PDOLValue pdol_transaction_type = {0x9C, {0x00}}; // Transaction type -const PDOLValue pdol_transaction_cert = {0x98, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; // Transaction cert -const PDOLValue pdol_unpredict_number = {0x9F37, {0x82, 0x3D, 0xDE, 0x7A}}; // Unpredictable number - -const PDOLValue* const pdol_values[] = { - &pdol_term_info, - &pdol_term_type, - &pdol_merchant_type, - &pdol_term_trans_qualifies, - &pdol_addtnl_term_qualifies, - &pdol_amount_authorise, - &pdol_amount, - &pdol_country_code, - &pdol_currency_code, - &pdol_term_verification, - &pdol_transaction_date, - &pdol_transaction_type, - &pdol_transaction_cert, - &pdol_unpredict_number, -}; - -static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E, - 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, - 0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07, - 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x50, 0x04, - 0x56, 0x49, 0x53, 0x41, 0x87, 0x01, 0x01, 0x90, 0x00}; -static const uint8_t select_app_ans[] = {0x6F, 0x20, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, - 0x10, 0x10, 0xA5, 0x15, 0x50, 0x04, 0x56, 0x49, 0x53, - 0x41, 0x9F, 0x38, 0x0C, 0x9F, 0x66, 0x04, 0x9F, 0x02, - 0x06, 0x9F, 0x37, 0x04, 0x5F, 0x2A, 0x02, 0x90, 0x00}; -static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x13, 0x55, 0x70, - 0x73, 0x83, 0x85, 0x87, 0x73, 0x31, 0xD1, 0x80, 0x22, 0x01, - 0x38, 0x84, 0x77, 0x94, 0x00, 0x00, 0x1F, 0x5F, 0x34, 0x01, - 0x00, 0x9F, 0x10, 0x07, 0x06, 0x01, 0x11, 0x03, 0x80, 0x00, - 0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96, - 0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06, - 0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00}; - -static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { - if(furi_log_get_level() == FuriLogLevelTrace) { - FURI_LOG_T(TAG, "%s", message); - printf("TX: "); - for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) { - printf("%02X ", tx_rx->tx_data[i]); - } - printf("\r\nRX: "); - for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) { - printf("%02X ", tx_rx->rx_data[i]); - } - printf("\r\n"); - } -} - -static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app) { - uint16_t i = 0; - uint16_t tag = 0, first_byte = 0; - uint16_t tlen = 0; - bool success = false; - - while(i < len) { - first_byte = buff[i]; - if((first_byte & 31) == 31) { // 2-byte tag - tag = buff[i] << 8 | buff[i + 1]; - i++; - FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag); - } else { - tag = buff[i]; - FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag); - } - i++; - tlen = buff[i]; - if((tlen & 128) == 128) { // long length value - i++; - tlen = buff[i]; - FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen); - } else { - FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen); - } - i++; - if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse - FURI_LOG_T(TAG, "Constructed TLV %x", tag); - if(!emv_decode_response(&buff[i], tlen, app)) { - FURI_LOG_T(TAG, "Failed to decode response for %x", tag); - // return false; - } else { - success = true; - } - } else { - switch(tag) { - case EMV_TAG_AID: - app->aid_len = tlen; - memcpy(app->aid, &buff[i], tlen); - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_AID %x", tag); - break; - case EMV_TAG_PRIORITY: - memcpy(&app->priority, &buff[i], tlen); - success = true; - break; - case EMV_TAG_CARD_NAME: - memcpy(app->name, &buff[i], tlen); - app->name[tlen] = '\0'; - app->name_found = true; - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name); - break; - case EMV_TAG_PDOL: - memcpy(app->pdol.data, &buff[i], tlen); - app->pdol.size = tlen; - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen); - break; - case EMV_TAG_AFL: - memcpy(app->afl.data, &buff[i], tlen); - app->afl.size = tlen; - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen); - break; - case EMV_TAG_TRACK_1_EQUIV: { - char track_1_equiv[80]; - memcpy(track_1_equiv, &buff[i], tlen); - track_1_equiv[tlen] = '\0'; - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv); - break; - } - case EMV_TAG_TRACK_2_EQUIV: { - // 0xD0 delimits PAN from expiry (YYMM) - for(int x = 1; x < tlen; x++) { - if(buff[i + x + 1] > 0xD0) { - memcpy(app->card_number, &buff[i], x + 1); - app->card_number_len = x + 1; - app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4); - app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4); - break; - } - } - - // Convert 4-bit to ASCII representation - char track_2_equiv[41]; - uint8_t track_2_equiv_len = 0; - for(int x = 0; x < tlen; x++) { - char top = (buff[i + x] >> 4) + '0'; - char bottom = (buff[i + x] & 0x0F) + '0'; - track_2_equiv[x * 2] = top; - track_2_equiv_len++; - if(top == '?') break; - track_2_equiv[x * 2 + 1] = bottom; - track_2_equiv_len++; - if(bottom == '?') break; - } - track_2_equiv[track_2_equiv_len] = '\0'; - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv); - break; - } - case EMV_TAG_PAN: - memcpy(app->card_number, &buff[i], tlen); - app->card_number_len = tlen; - success = true; - break; - case EMV_TAG_EXP_DATE: - app->exp_year = buff[i]; - app->exp_month = buff[i + 1]; - success = true; - break; - case EMV_TAG_CURRENCY_CODE: - app->currency_code = (buff[i] << 8 | buff[i + 1]); - success = true; - break; - case EMV_TAG_COUNTRY_CODE: - app->country_code = (buff[i] << 8 | buff[i + 1]); - success = true; - break; - } - } - i += tlen; - } - return success; -} - -static bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - bool app_aid_found = false; - const uint8_t emv_select_ppse_cmd[] = { - 0x00, 0xA4, // SELECT ppse - 0x04, 0x00, // P1:By name, P2: empty - 0x0e, // Lc: Data length - 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string: - 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE) - 0x00 // Le - }; - - memcpy(tx_rx->tx_data, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd)); - tx_rx->tx_bits = sizeof(emv_select_ppse_cmd) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - FURI_LOG_D(TAG, "Send select PPSE"); - if(furi_hal_nfc_tx_rx(tx_rx, 300)) { - emv_trace(tx_rx, "Select PPSE answer:"); - if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - app_aid_found = true; - } else { - FURI_LOG_E(TAG, "Failed to parse application"); - } - } else { - FURI_LOG_E(TAG, "Failed select PPSE"); - } - - return app_aid_found; -} - -static bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - app->app_started = false; - const uint8_t emv_select_header[] = { - 0x00, - 0xA4, // SELECT application - 0x04, - 0x00 // P1:By name, P2:First or only occurence - }; - uint16_t size = sizeof(emv_select_header); - - // Copy header - memcpy(tx_rx->tx_data, emv_select_header, size); - // Copy AID - tx_rx->tx_data[size++] = app->aid_len; - memcpy(&tx_rx->tx_data[size], app->aid, app->aid_len); - size += app->aid_len; - tx_rx->tx_data[size++] = 0x00; - tx_rx->tx_bits = size * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - FURI_LOG_D(TAG, "Start application"); - if(furi_hal_nfc_tx_rx(tx_rx, 300)) { - emv_trace(tx_rx, "Start application answer:"); - if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - app->app_started = true; - } else { - FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); - } - } else { - FURI_LOG_E(TAG, "Failed to start application"); - } - - return app->app_started; -} - -static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { - bool tag_found; - for(uint16_t i = 0; i < src->size; i++) { - tag_found = false; - for(uint8_t j = 0; j < sizeof(pdol_values) / sizeof(PDOLValue*); j++) { - if(src->data[i] == pdol_values[j]->tag) { - // Found tag with 1 byte length - uint8_t len = src->data[++i]; - memcpy(dest->data + dest->size, pdol_values[j]->data, len); - dest->size += len; - tag_found = true; - break; - } else if(((src->data[i] << 8) | src->data[i + 1]) == pdol_values[j]->tag) { - // Found tag with 2 byte length - i += 2; - uint8_t len = src->data[i]; - memcpy(dest->data + dest->size, pdol_values[j]->data, len); - dest->size += len; - tag_found = true; - break; - } - } - if(!tag_found) { - // Unknown tag, fill zeros - i += 2; - uint8_t len = src->data[i]; - memset(dest->data + dest->size, 0, len); - dest->size += len; - } - } - return dest->size; -} - -static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - bool card_num_read = false; - const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00}; - uint16_t size = sizeof(emv_gpo_header); - - // Copy header - memcpy(tx_rx->tx_data, emv_gpo_header, size); - APDU pdol_data = {0, {0}}; - // Prepare and copy pdol parameters - emv_prepare_pdol(&pdol_data, &app->pdol); - tx_rx->tx_data[size++] = 0x02 + pdol_data.size; - tx_rx->tx_data[size++] = 0x83; - tx_rx->tx_data[size++] = pdol_data.size; - memcpy(tx_rx->tx_data + size, pdol_data.data, pdol_data.size); - size += pdol_data.size; - tx_rx->tx_data[size++] = 0; - tx_rx->tx_bits = size * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - FURI_LOG_D(TAG, "Get proccessing options"); - if(furi_hal_nfc_tx_rx(tx_rx, 300)) { - emv_trace(tx_rx, "Get processing options answer:"); - if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - if(app->card_number_len > 0) { - card_num_read = true; - } - } - } else { - FURI_LOG_E(TAG, "Failed to get processing options"); - } - - return card_num_read; -} - -static bool emv_read_sfi_record( - FuriHalNfcTxRxContext* tx_rx, - EmvApplication* app, - uint8_t sfi, - uint8_t record_num) { - bool card_num_read = false; - uint8_t sfi_param = (sfi << 3) | (1 << 2); - uint8_t emv_sfi_header[] = { - 0x00, - 0xB2, // READ RECORD - record_num, // P1:record_number - sfi_param, // P2:SFI - 0x00 // Le - }; - - memcpy(tx_rx->tx_data, emv_sfi_header, sizeof(emv_sfi_header)); - tx_rx->tx_bits = sizeof(emv_sfi_header) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - if(furi_hal_nfc_tx_rx(tx_rx, 300)) { - emv_trace(tx_rx, "SFI record:"); - if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - card_num_read = true; - } - } else { - FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num); - } - - return card_num_read; -} - -static bool emv_read_files(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - bool card_num_read = false; - - if(app->afl.size == 0) { - return false; - } - - FURI_LOG_D(TAG, "Search PAN in SFI"); - // Iterate through all files - for(size_t i = 0; i < app->afl.size; i += 4) { - uint8_t sfi = app->afl.data[i] >> 3; - uint8_t record_start = app->afl.data[i + 1]; - uint8_t record_end = app->afl.data[i + 2]; - // Iterate through all records in file - for(uint8_t record = record_start; record <= record_end; ++record) { - card_num_read |= emv_read_sfi_record(tx_rx, app, sfi, record); - } - } - - return card_num_read; -} - -bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { - furi_assert(tx_rx); - furi_assert(emv_app); - bool card_num_read = false; - memset(emv_app, 0, sizeof(EmvApplication)); - - do { - if(!emv_select_ppse(tx_rx, emv_app)) break; - if(!emv_select_app(tx_rx, emv_app)) break; - if(emv_get_processing_options(tx_rx, emv_app)) { - card_num_read = true; - } else { - card_num_read = emv_read_files(tx_rx, emv_app); - } - } while(false); - - return card_num_read; -} - -bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { - furi_assert(tx_rx); - bool emulation_complete = false; - tx_rx->tx_bits = 0; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - do { - FURI_LOG_D(TAG, "Read select PPSE command"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - memcpy(tx_rx->tx_data, select_ppse_ans, sizeof(select_ppse_ans)); - tx_rx->tx_bits = sizeof(select_ppse_ans) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - FURI_LOG_D(TAG, "Send select PPSE answer and read select App command"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - memcpy(tx_rx->tx_data, select_app_ans, sizeof(select_app_ans)); - tx_rx->tx_bits = sizeof(select_app_ans) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - FURI_LOG_D(TAG, "Send select App answer and read get PDOL command"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - memcpy(tx_rx->tx_data, pdol_ans, sizeof(pdol_ans)); - tx_rx->tx_bits = sizeof(pdol_ans) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - FURI_LOG_D(TAG, "Send get PDOL answer"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - emulation_complete = true; - } while(false); - - return emulation_complete; -} diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h deleted file mode 100644 index c5b089fdff..0000000000 --- a/lib/nfc/protocols/emv.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#define MAX_APDU_LEN 255 - -#define EMV_TAG_APP_TEMPLATE 0x61 -#define EMV_TAG_AID 0x4F -#define EMV_TAG_PRIORITY 0x87 -#define EMV_TAG_PDOL 0x9F38 -#define EMV_TAG_CARD_NAME 0x50 -#define EMV_TAG_FCI 0xBF0C -#define EMV_TAG_LOG_CTRL 0x9F4D -#define EMV_TAG_TRACK_1_EQUIV 0x56 -#define EMV_TAG_TRACK_2_EQUIV 0x57 -#define EMV_TAG_PAN 0x5A -#define EMV_TAG_AFL 0x94 -#define EMV_TAG_EXP_DATE 0x5F24 -#define EMV_TAG_COUNTRY_CODE 0x5F28 -#define EMV_TAG_CURRENCY_CODE 0x9F42 -#define EMV_TAG_CARDHOLDER_NAME 0x5F20 - -typedef struct { - char name[32]; - uint8_t aid[16]; - uint16_t aid_len; - uint8_t number[10]; - uint8_t number_len; - uint8_t exp_mon; - uint8_t exp_year; - uint16_t country_code; - uint16_t currency_code; -} EmvData; - -typedef struct { - uint16_t tag; - uint8_t data[]; -} PDOLValue; - -typedef struct { - uint8_t size; - uint8_t data[MAX_APDU_LEN]; -} APDU; - -typedef struct { - uint8_t priority; - uint8_t aid[16]; - uint8_t aid_len; - bool app_started; - char name[32]; - bool name_found; - uint8_t card_number[10]; - uint8_t card_number_len; - uint8_t exp_month; - uint8_t exp_year; - uint16_t country_code; - uint16_t currency_code; - APDU pdol; - APDU afl; -} EmvApplication; - -/** Read bank card data - * @note Search EMV Application, start it, try to read AID, PAN, card name, - * expiration date, currency and country codes - * - * @param tx_rx FuriHalNfcTxRxContext instance - * @param emv_app EmvApplication instance - * - * @return true on success - */ -bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app); - -/** Emulate bank card - * @note Answer to application selection and PDOL - * - * @param tx_rx FuriHalNfcTxRxContext instance - * - * @return true on success - */ -bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c new file mode 100644 index 0000000000..4b7c91fb64 --- /dev/null +++ b/lib/nfc/protocols/felica/felica.c @@ -0,0 +1,147 @@ +#include "felica.h" + +#include + +#include + +#define FELICA_PROTOCOL_NAME "FeliCa" +#define FELICA_DEVICE_NAME "FeliCa" + +#define FELICA_DATA_FORMAT_VERSION "Data format version" +#define FELICA_MANUFACTURE_ID "Manufacture id" +#define FELICA_MANUFACTURE_PARAMETER "Manufacture parameter" + +static const uint32_t felica_data_format_version = 1; + +const NfcDeviceBase nfc_device_felica = { + .protocol_name = FELICA_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)felica_alloc, + .free = (NfcDeviceFree)felica_free, + .reset = (NfcDeviceReset)felica_reset, + .copy = (NfcDeviceCopy)felica_copy, + .verify = (NfcDeviceVerify)felica_verify, + .load = (NfcDeviceLoad)felica_load, + .save = (NfcDeviceSave)felica_save, + .is_equal = (NfcDeviceEqual)felica_is_equal, + .get_name = (NfcDeviceGetName)felica_get_device_name, + .get_uid = (NfcDeviceGetUid)felica_get_uid, + .set_uid = (NfcDeviceSetUid)felica_set_uid, + .get_base_data = (NfcDeviceGetBaseData)felica_get_base_data, +}; + +FelicaData* felica_alloc() { + FelicaData* data = malloc(sizeof(FelicaData)); + return data; +} + +void felica_free(FelicaData* data) { + furi_assert(data); + + free(data); +} + +void felica_reset(FelicaData* data) { + memset(data, 0, sizeof(FelicaData)); +} + +void felica_copy(FelicaData* data, const FelicaData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool felica_verify(FelicaData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + if(version < NFC_UNIFIED_FORMAT_VERSION) break; + + uint32_t data_format_version = 0; + if(!flipper_format_read_uint32(ff, FELICA_DATA_FORMAT_VERSION, &data_format_version, 1)) + break; + if(data_format_version != felica_data_format_version) break; + if(!flipper_format_read_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE)) + break; + if(!flipper_format_read_hex( + ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) + break; + + parsed = true; + } while(false); + + return parsed; +} + +bool felica_save(const FelicaData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, FELICA_PROTOCOL_NAME " specific data")) break; + if(!flipper_format_write_uint32( + ff, FELICA_DATA_FORMAT_VERSION, &felica_data_format_version, 1)) + break; + if(!flipper_format_write_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE)) + break; + if(!flipper_format_write_hex( + ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) + break; + + saved = true; + } while(false); + + return saved; +} + +bool felica_is_equal(const FelicaData* data, const FelicaData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(FelicaData)) == 0; +} + +const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + + return FELICA_DEVICE_NAME; +} + +const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { + furi_assert(data); + + // Consider Manufacturer ID as UID + if(uid_len) { + *uid_len = FELICA_IDM_SIZE; + } + + return data->idm.data; +} + +bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + // Consider Manufacturer ID as UID + const bool uid_valid = uid_len == FELICA_IDM_SIZE; + if(uid_valid) { + memcpy(data->idm.data, uid, uid_len); + } + + return uid_valid; +} + +FelicaData* felica_get_base_data(const FelicaData* data) { + UNUSED(data); + furi_crash("No base data"); +} diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h new file mode 100644 index 0000000000..da9d2294ee --- /dev/null +++ b/lib/nfc/protocols/felica/felica.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FELICA_IDM_SIZE (8U) +#define FELICA_PMM_SIZE (8U) + +#define FELICA_GUARD_TIME_US (20000U) +#define FELICA_FDT_POLL_FC (10000U) +#define FELICA_POLL_POLL_MIN_US (1280U) + +#define FELICA_SYSTEM_CODE_CODE (0xFFFFU) +#define FELICA_TIME_SLOT_1 (0x00U) +#define FELICA_TIME_SLOT_2 (0x01U) +#define FELICA_TIME_SLOT_4 (0x03U) +#define FELICA_TIME_SLOT_8 (0x07U) +#define FELICA_TIME_SLOT_16 (0x0FU) + +typedef enum { + FelicaErrorNone, + FelicaErrorNotPresent, + FelicaErrorColResFailed, + FelicaErrorBufferOverflow, + FelicaErrorCommunication, + FelicaErrorFieldOff, + FelicaErrorWrongCrc, + FelicaErrorProtocol, + FelicaErrorTimeout, +} FelicaError; + +typedef struct { + uint8_t data[FELICA_IDM_SIZE]; +} FelicaIDm; + +typedef struct { + uint8_t data[FELICA_PMM_SIZE]; +} FelicaPMm; + +typedef struct { + FelicaIDm idm; + FelicaPMm pmm; +} FelicaData; + +extern const NfcDeviceBase nfc_device_felica; + +FelicaData* felica_alloc(); + +void felica_free(FelicaData* data); + +void felica_reset(FelicaData* data); + +void felica_copy(FelicaData* data, const FelicaData* other); + +bool felica_verify(FelicaData* data, const FuriString* device_type); + +bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version); + +bool felica_save(const FelicaData* data, FlipperFormat* ff); + +bool felica_is_equal(const FelicaData* data, const FelicaData* other); + +const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type); + +const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len); + +bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len); + +FelicaData* felica_get_base_data(const FelicaData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c new file mode 100644 index 0000000000..3fc2affedc --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -0,0 +1,117 @@ +#include "felica_poller_i.h" + +#include + +#include + +const FelicaData* felica_poller_get_data(FelicaPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static FelicaPoller* felica_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + FelicaPoller* instance = malloc(sizeof(FelicaPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(FELICA_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(FELICA_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechFelica); + nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US); + instance->data = felica_alloc(); + + instance->felica_event.data = &instance->felica_event_data; + instance->general_event.protocol = NfcProtocolFelica; + instance->general_event.event_data = &instance->felica_event; + instance->general_event.instance = instance; + + return instance; +} + +static void felica_poller_free(FelicaPoller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + felica_free(instance->data); + free(instance); +} + +static void + felica_poller_set_callback(FelicaPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + FelicaPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != FelicaPollerStateActivated) { + FelicaError error = felica_poller_async_activate(instance, instance->data); + if(error == FelicaErrorNone) { + instance->felica_event.type = FelicaPollerEventTypeReady; + instance->felica_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->felica_event.type = FelicaPollerEventTypeReady; + instance->felica_event_data.error = FelicaErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool felica_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + FelicaPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == FelicaPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + FelicaError error = felica_poller_async_activate(instance, instance->data); + protocol_detected = (error == FelicaErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_felica = { + .alloc = (NfcPollerAlloc)felica_poller_alloc, + .free = (NfcPollerFree)felica_poller_free, + .set_callback = (NfcPollerSetCallback)felica_poller_set_callback, + .run = (NfcPollerRun)felica_poller_run, + .detect = (NfcPollerDetect)felica_poller_detect, + .get_data = (NfcPollerGetData)felica_poller_get_data, +}; diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h new file mode 100644 index 0000000000..7d0c9525e7 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -0,0 +1,30 @@ +#pragma once + +#include "felica.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FelicaPoller FelicaPoller; + +typedef enum { + FelicaPollerEventTypeError, + FelicaPollerEventTypeReady, +} FelicaPollerEventType; + +typedef struct { + FelicaError error; +} FelicaPollerEventData; + +typedef struct { + FelicaPollerEventType type; + FelicaPollerEventData* data; +} FelicaPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_poller_defs.h b/lib/nfc/protocols/felica/felica_poller_defs.h new file mode 100644 index 0000000000..fc99dc7521 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase nfc_poller_felica; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c new file mode 100644 index 0000000000..d8015fdfad --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -0,0 +1,128 @@ +#include "felica_poller_i.h" + +#include + +#define TAG "FelicaPoller" + +static FelicaError felica_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return FelicaErrorNone; + case NfcErrorTimeout: + return FelicaErrorTimeout; + default: + return FelicaErrorNotPresent; + } +} + +static FelicaError felica_poller_frame_exchange( + FelicaPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - FELICA_CRC_SIZE); + + felica_crc_append(instance->tx_buffer); + + FelicaError ret = FelicaErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = felica_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!felica_crc_check(instance->rx_buffer)) { + ret = FelicaErrorWrongCrc; + break; + } + + felica_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +FelicaError felica_poller_async_polling( + FelicaPoller* instance, + const FelicaPollerPollingCommand* cmd, + FelicaPollerPollingResponse* resp) { + furi_assert(instance); + furi_assert(cmd); + furi_assert(resp); + + FelicaError error = FelicaErrorNone; + + do { + bit_buffer_set_size_bytes(instance->tx_buffer, 2); + // Set frame len + bit_buffer_set_byte( + instance->tx_buffer, 0, sizeof(FelicaPollerPollingCommand) + FELICA_CRC_SIZE); + // Set command code + bit_buffer_set_byte(instance->tx_buffer, 1, FELICA_POLLER_CMD_POLLING_REQ_CODE); + // Set other data + bit_buffer_append_bytes( + instance->tx_buffer, (uint8_t*)cmd, sizeof(FelicaPollerPollingCommand)); + + error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + + if(error != FelicaErrorNone) break; + if(bit_buffer_get_byte(instance->rx_buffer, 1) != FELICA_POLLER_CMD_POLLING_RESP_CODE) { + error = FelicaErrorProtocol; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) < + sizeof(FelicaIDm) + sizeof(FelicaPMm) + 1) { + error = FelicaErrorProtocol; + break; + } + + bit_buffer_write_bytes_mid(instance->rx_buffer, resp->idm.data, 2, sizeof(FelicaIDm)); + bit_buffer_write_bytes_mid( + instance->rx_buffer, resp->pmm.data, sizeof(FelicaIDm) + 2, sizeof(FelicaPMm)); + + } while(false); + + return error; +} + +FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data) { + furi_assert(instance); + + felica_reset(data); + + FelicaError ret; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Polling command + const FelicaPollerPollingCommand polling_cmd = { + .system_code = FELICA_SYSTEM_CODE_CODE, + .request_code = 0, + .time_slot = FELICA_TIME_SLOT_1, + }; + FelicaPollerPollingResponse polling_resp = {}; + + ret = felica_poller_async_polling(instance, &polling_cmd, &polling_resp); + + if(ret != FelicaErrorNone) { + FURI_LOG_T(TAG, "Activation failed error: %d", ret); + break; + } + + data->idm = polling_resp.idm; + data->pmm = polling_resp.pmm; + instance->state = FelicaPollerStateActivated; + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h new file mode 100644 index 0000000000..e12f014726 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -0,0 +1,60 @@ +#pragma once + +#include "felica_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FELICA_POLLER_MAX_BUFFER_SIZE (256U) + +#define FELICA_POLLER_POLLING_FWT (200000U) + +#define FELICA_POLLER_CMD_POLLING_REQ_CODE (0x00U) +#define FELICA_POLLER_CMD_POLLING_RESP_CODE (0x01U) + +typedef enum { + FelicaPollerStateIdle, + FelicaPollerStateActivated, +} FelicaPollerState; + +struct FelicaPoller { + Nfc* nfc; + FelicaPollerState state; + FelicaData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + FelicaPollerEvent felica_event; + FelicaPollerEventData felica_event_data; + NfcGenericCallback callback; + void* context; +}; + +typedef struct { + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; +} FelicaPollerPollingCommand; + +typedef struct { + FelicaIDm idm; + FelicaPMm pmm; + uint8_t request_data[2]; +} FelicaPollerPollingResponse; + +const FelicaData* felica_poller_get_data(FelicaPoller* instance); + +FelicaError felica_poller_async_polling( + FelicaPoller* instance, + const FelicaPollerPollingCommand* cmd, + FelicaPollerPollingResponse* resp); + +FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a.c new file mode 100644 index 0000000000..96d0b39537 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a.c @@ -0,0 +1,186 @@ +#include "iso14443_3a.h" + +#include +#include + +#define ISO14443A_ATS_BIT (1U << 5) + +#define ISO14443_3A_PROTOCOL_NAME_LEGACY "UID" +#define ISO14443_3A_PROTOCOL_NAME "ISO14443-3A" +#define ISO14443_3A_DEVICE_NAME "ISO14443-3A (Unknown)" + +#define ISO14443_3A_ATQA_KEY "ATQA" +#define ISO14443_3A_SAK_KEY "SAK" + +const NfcDeviceBase nfc_device_iso14443_3a = { + .protocol_name = ISO14443_3A_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_3a_alloc, + .free = (NfcDeviceFree)iso14443_3a_free, + .reset = (NfcDeviceReset)iso14443_3a_reset, + .copy = (NfcDeviceCopy)iso14443_3a_copy, + .verify = (NfcDeviceVerify)iso14443_3a_verify, + .load = (NfcDeviceLoad)iso14443_3a_load, + .save = (NfcDeviceSave)iso14443_3a_save, + .is_equal = (NfcDeviceEqual)iso14443_3a_is_equal, + .get_name = (NfcDeviceGetName)iso14443_3a_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_3a_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_3a_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_3a_get_base_data, +}; + +Iso14443_3aData* iso14443_3a_alloc() { + Iso14443_3aData* data = malloc(sizeof(Iso14443_3aData)); + return data; +} + +void iso14443_3a_free(Iso14443_3aData* data) { + furi_assert(data); + + free(data); +} + +void iso14443_3a_reset(Iso14443_3aData* data) { + furi_assert(data); + memset(data, 0, sizeof(Iso14443_3aData)); +} + +void iso14443_3a_copy(Iso14443_3aData* data, const Iso14443_3aData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool iso14443_3a_verify(Iso14443_3aData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal(device_type, ISO14443_3A_PROTOCOL_NAME_LEGACY); +} + +bool iso14443_3a_load(Iso14443_3aData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + // Common to all format versions + if(!flipper_format_read_hex(ff, ISO14443_3A_ATQA_KEY, data->atqa, 2)) break; + if(!flipper_format_read_hex(ff, ISO14443_3A_SAK_KEY, &data->sak, 1)) break; + + if(version > NFC_LSB_ATQA_FORMAT_VERSION) { + // Swap ATQA bytes for newer versions + FURI_SWAP(data->atqa[0], data->atqa[1]); + } + + parsed = true; + } while(false); + + return parsed; +} + +bool iso14443_3a_save(const Iso14443_3aData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + // Save ATQA in MSB order for correct companion apps display + const uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; + if(!flipper_format_write_comment_cstr(ff, ISO14443_3A_PROTOCOL_NAME " specific data")) + break; + + // Write ATQA and SAK + if(!flipper_format_write_hex(ff, ISO14443_3A_ATQA_KEY, atqa, 2)) break; + if(!flipper_format_write_hex(ff, ISO14443_3A_SAK_KEY, &data->sak, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool iso14443_3a_is_equal(const Iso14443_3aData* data, const Iso14443_3aData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(Iso14443_3aData)) == 0; +} + +const char* iso14443_3a_get_device_name(const Iso14443_3aData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return ISO14443_3A_DEVICE_NAME; +} + +const uint8_t* iso14443_3a_get_uid(const Iso14443_3aData* data, size_t* uid_len) { + furi_assert(data); + + if(uid_len) { + *uid_len = data->uid_len; + } + + return data->uid; +} + +bool iso14443_3a_set_uid(Iso14443_3aData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + const bool uid_valid = uid_len == ISO14443_3A_UID_4_BYTES || + uid_len == ISO14443_3A_UID_7_BYTES || + uid_len == ISO14443_3A_UID_10_BYTES; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + data->uid_len = uid_len; + } + + return uid_valid; +} + +Iso14443_3aData* iso14443_3a_get_base_data(const Iso14443_3aData* data) { + UNUSED(data); + furi_crash("No base data"); +} + +uint32_t iso14443_3a_get_cuid(const Iso14443_3aData* data) { + furi_assert(data); + + uint32_t cuid = 0; + const uint8_t* cuid_start = data->uid; + if(data->uid_len == ISO14443_3A_UID_7_BYTES) { + cuid_start = &data->uid[3]; + } + cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | (cuid_start[3]); + + return cuid; +} + +bool iso14443_3a_supports_iso14443_4(const Iso14443_3aData* data) { + furi_assert(data); + + return data->sak & ISO14443A_ATS_BIT; +} + +uint8_t iso14443_3a_get_sak(const Iso14443_3aData* data) { + furi_assert(data); + + return data->sak; +} + +void iso14443_3a_get_atqa(const Iso14443_3aData* data, uint8_t atqa[2]) { + furi_assert(data); + furi_assert(atqa); + + memcpy(atqa, data->atqa, sizeof(data->atqa)); +} + +void iso14443_3a_set_sak(Iso14443_3aData* data, uint8_t sak) { + furi_assert(data); + + data->sak = sak; +} + +void iso14443_3a_set_atqa(Iso14443_3aData* data, const uint8_t atqa[2]) { + furi_assert(data); + furi_assert(atqa); + + memcpy(data->atqa, atqa, sizeof(data->atqa)); +} diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a.h new file mode 100644 index 0000000000..005626e628 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_3A_UID_4_BYTES (4U) +#define ISO14443_3A_UID_7_BYTES (7U) +#define ISO14443_3A_UID_10_BYTES (10U) +#define ISO14443_3A_MAX_UID_SIZE ISO14443_3A_UID_10_BYTES + +#define ISO14443_3A_GUARD_TIME_US (5000) +#define ISO14443_3A_FDT_POLL_FC (1620) +#define ISO14443_3A_FDT_LISTEN_FC (1172) +#define ISO14443_3A_POLLER_MASK_RX_FS ((ISO14443_3A_FDT_LISTEN_FC) / 2) +#define ISO14443_3A_POLL_POLL_MIN_US (1100) + +typedef enum { + Iso14443_3aErrorNone, + Iso14443_3aErrorNotPresent, + Iso14443_3aErrorColResFailed, + Iso14443_3aErrorBufferOverflow, + Iso14443_3aErrorCommunication, + Iso14443_3aErrorFieldOff, + Iso14443_3aErrorWrongCrc, + Iso14443_3aErrorTimeout, +} Iso14443_3aError; + +typedef struct { + uint8_t sens_resp[2]; +} Iso14443_3aSensResp; + +typedef struct { + uint8_t sel_cmd; + uint8_t sel_par; + uint8_t data[4]; // max data bit is 32 +} Iso14443_3aSddReq; + +typedef struct { + uint8_t nfcid[4]; + uint8_t bss; +} Iso14443_3aSddResp; + +typedef struct { + uint8_t sel_cmd; + uint8_t sel_par; + uint8_t nfcid[4]; + uint8_t bcc; +} Iso14443_3aSelReq; + +typedef struct { + uint8_t sak; +} Iso14443_3aSelResp; + +typedef struct { + uint8_t uid[ISO14443_3A_MAX_UID_SIZE]; + uint8_t uid_len; + uint8_t atqa[2]; + uint8_t sak; +} Iso14443_3aData; + +Iso14443_3aData* iso14443_3a_alloc(); + +void iso14443_3a_free(Iso14443_3aData* data); + +void iso14443_3a_reset(Iso14443_3aData* data); + +void iso14443_3a_copy(Iso14443_3aData* data, const Iso14443_3aData* other); + +bool iso14443_3a_verify(Iso14443_3aData* data, const FuriString* device_type); + +bool iso14443_3a_load(Iso14443_3aData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_3a_save(const Iso14443_3aData* data, FlipperFormat* ff); + +bool iso14443_3a_is_equal(const Iso14443_3aData* data, const Iso14443_3aData* other); + +const char* iso14443_3a_get_device_name(const Iso14443_3aData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_3a_get_uid(const Iso14443_3aData* data, size_t* uid_len); + +bool iso14443_3a_set_uid(Iso14443_3aData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* iso14443_3a_get_base_data(const Iso14443_3aData* data); + +uint32_t iso14443_3a_get_cuid(const Iso14443_3aData* data); + +// Getters and tests + +bool iso14443_3a_supports_iso14443_4(const Iso14443_3aData* data); + +uint8_t iso14443_3a_get_sak(const Iso14443_3aData* data); + +void iso14443_3a_get_atqa(const Iso14443_3aData* data, uint8_t atqa[2]); + +// Setters + +void iso14443_3a_set_sak(Iso14443_3aData* data, uint8_t sak); + +void iso14443_3a_set_atqa(Iso14443_3aData* data, const uint8_t atqa[2]); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h new file mode 100644 index 0000000000..8a736dfa7f --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_3a; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c new file mode 100644 index 0000000000..89d96f6fc5 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c @@ -0,0 +1,127 @@ +#include "iso14443_3a_listener_i.h" + +#include "nfc/protocols/nfc_listener_base.h" +#include "nfc/helpers/iso14443_crc.h" + +#include +#include + +#define TAG "Iso14443_3aListener" + +#define ISO14443_3A_LISTENER_MAX_BUFFER_SIZE (256) + +static bool iso14443_3a_listener_halt_received(BitBuffer* buf) { + bool halt_cmd_received = false; + + do { + if(bit_buffer_get_size_bytes(buf) != 4) break; + if(!iso14443_crc_check(Iso14443CrcTypeA, buf)) break; + if(bit_buffer_get_byte(buf, 0) != 0x50) break; + if(bit_buffer_get_byte(buf, 1) != 0x00) break; + halt_cmd_received = true; + } while(false); + + return halt_cmd_received; +} + +Iso14443_3aListener* iso14443_3a_listener_alloc(Nfc* nfc, Iso14443_3aData* data) { + furi_assert(nfc); + + Iso14443_3aListener* instance = malloc(sizeof(Iso14443_3aListener)); + instance->nfc = nfc; + instance->data = data; + instance->tx_buffer = bit_buffer_alloc(ISO14443_3A_LISTENER_MAX_BUFFER_SIZE); + + instance->iso14443_3a_event.data = &instance->iso14443_3a_event_data; + instance->generic_event.protocol = NfcProtocolIso14443_3a; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->iso14443_3a_event; + + nfc_set_fdt_listen_fc(instance->nfc, ISO14443_3A_FDT_LISTEN_FC); + nfc_config(instance->nfc, NfcModeListener, NfcTechIso14443a); + nfc_iso14443a_listener_set_col_res_data( + instance->nfc, + instance->data->uid, + instance->data->uid_len, + instance->data->atqa, + instance->data->sak); + + return instance; +} + +void iso14443_3a_listener_free(Iso14443_3aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +void iso14443_3a_listener_set_callback( + Iso14443_3aListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +const Iso14443_3aData* iso14443_3a_listener_get_data(Iso14443_3aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +NfcCommand iso14443_3a_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso14443_3aListener* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypeListenerActivated) { + instance->state = Iso14443_3aListenerStateActive; + } else if(nfc_event->type == NfcEventTypeFieldOff) { + instance->state = Iso14443_3aListenerStateIdle; + if(instance->callback) { + instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeFieldOff; + instance->callback(instance->generic_event, instance->context); + } + command = NfcCommandSleep; + } else if(nfc_event->type == NfcEventTypeRxEnd) { + if(iso14443_3a_listener_halt_received(nfc_event->data.buffer)) { + if(instance->callback) { + instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeHalted; + instance->callback(instance->generic_event, instance->context); + } + command = NfcCommandSleep; + } else { + if(iso14443_crc_check(Iso14443CrcTypeA, nfc_event->data.buffer)) { + instance->iso14443_3a_event.type = + Iso14443_3aListenerEventTypeReceivedStandardFrame; + iso14443_crc_trim(nfc_event->data.buffer); + } else { + instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeReceivedData; + } + instance->iso14443_3a_event_data.buffer = nfc_event->data.buffer; + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } + } + } + + return command; +} + +const NfcListenerBase nfc_listener_iso14443_3a = { + .alloc = (NfcListenerAlloc)iso14443_3a_listener_alloc, + .free = (NfcListenerFree)iso14443_3a_listener_free, + .set_callback = (NfcListenerSetCallback)iso14443_3a_listener_set_callback, + .get_data = (NfcListenerGetData)iso14443_3a_listener_get_data, + .run = (NfcListenerRun)iso14443_3a_listener_run, +}; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h new file mode 100644 index 0000000000..8a550ca0a8 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h @@ -0,0 +1,31 @@ +#pragma once + +#include "iso14443_3a.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3aListener Iso14443_3aListener; + +typedef enum { + Iso14443_3aListenerEventTypeFieldOff, + Iso14443_3aListenerEventTypeHalted, + + Iso14443_3aListenerEventTypeReceivedStandardFrame, + Iso14443_3aListenerEventTypeReceivedData, +} Iso14443_3aListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Iso14443_3aListenerEventData; + +typedef struct { + Iso14443_3aListenerEventType type; + Iso14443_3aListenerEventData* data; +} Iso14443_3aListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h new file mode 100644 index 0000000000..b4fccec74c --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_iso14443_3a; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c new file mode 100644 index 0000000000..46501503ce --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c @@ -0,0 +1,73 @@ +#include "iso14443_3a_listener_i.h" + +#include "nfc/helpers/iso14443_crc.h" + +#define TAG "Iso14443_3aListener" + +static Iso14443_3aError iso14443_3a_listener_process_nfc_error(NfcError error) { + Iso14443_3aError ret = Iso14443_3aErrorNone; + + if(error == NfcErrorNone) { + ret = Iso14443_3aErrorNone; + } else if(error == NfcErrorTimeout) { + ret = Iso14443_3aErrorTimeout; + } else { + ret = Iso14443_3aErrorFieldOff; + } + + return ret; +} + +Iso14443_3aError + iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = nfc_listener_tx(instance->nfc, tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_W(TAG, "Tx error: %d", error); + ret = iso14443_3a_listener_process_nfc_error(error); + } + + return ret; +} + +Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = nfc_iso14443a_listener_tx_custom_parity(instance->nfc, tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_W(TAG, "Tx error: %d", error); + ret = iso14443_3a_listener_process_nfc_error(error); + } + + return ret; +}; + +Iso14443_3aError iso14443_3a_listener_send_standard_frame( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(instance->tx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + do { + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer); + + NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_W(TAG, "Tx error: %d", error); + ret = iso14443_3a_listener_process_nfc_error(error); + break; + } + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h new file mode 100644 index 0000000000..0113a1cb89 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h @@ -0,0 +1,42 @@ +#pragma once + +#include "iso14443_3a_listener.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_3aListenerStateIdle, + Iso14443_3aListenerStateActive, +} Iso14443_3aListenerState; + +struct Iso14443_3aListener { + Nfc* nfc; + Iso14443_3aData* data; + Iso14443_3aListenerState state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Iso14443_3aListenerEvent iso14443_3a_event; + Iso14443_3aListenerEventData iso14443_3a_event_data; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_3aError + iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); + +Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + +Iso14443_3aError iso14443_3a_listener_send_standard_frame( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c new file mode 100644 index 0000000000..880092c333 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c @@ -0,0 +1,128 @@ +#include "iso14443_3a_poller_i.h" + +#include + +#include + +#define TAG "ISO14443_3A" + +const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static Iso14443_3aPoller* iso14443_3a_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + Iso14443_3aPoller* instance = malloc(sizeof(Iso14443_3aPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ISO14443_3A_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_3A_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443a); + nfc_set_guard_time_us(instance->nfc, ISO14443_3A_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ISO14443_3A_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ISO14443_3A_POLL_POLL_MIN_US); + instance->data = iso14443_3a_alloc(); + + instance->iso14443_3a_event.data = &instance->iso14443_3a_event_data; + instance->general_event.protocol = NfcProtocolIso14443_3a; + instance->general_event.event_data = &instance->iso14443_3a_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_3a_poller_free_new(Iso14443_3aPoller* iso14443_3a_poller) { + furi_assert(iso14443_3a_poller); + + Iso14443_3aPoller* instance = iso14443_3a_poller; + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + iso14443_3a_free(instance->data); + free(instance); +} + +static void iso14443_3a_poller_set_callback( + Iso14443_3aPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_3a_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso14443_3aPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != Iso14443_3aPollerStateActivated) { + Iso14443_3aData data = {}; + Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, &data); + if(error == Iso14443_3aErrorNone) { + instance->state = Iso14443_3aPollerStateActivated; + instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady; + instance->iso14443_3a_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeError; + instance->iso14443_3a_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady; + instance->iso14443_3a_event_data.error = Iso14443_3aErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + if(command == NfcCommandReset) { + instance->state = Iso14443_3aPollerStateIdle; + } + + return command; +} + +static bool iso14443_3a_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + Iso14443_3aPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == Iso14443_3aPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, NULL); + protocol_detected = (error == Iso14443_3aErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_3a = { + .alloc = (NfcPollerAlloc)iso14443_3a_poller_alloc, + .free = (NfcPollerFree)iso14443_3a_poller_free_new, + .set_callback = (NfcPollerSetCallback)iso14443_3a_poller_set_callback, + .run = (NfcPollerRun)iso14443_3a_poller_run, + .detect = (NfcPollerDetect)iso14443_3a_poller_detect, + .get_data = (NfcPollerGetData)iso14443_3a_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h new file mode 100644 index 0000000000..385cd52256 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h @@ -0,0 +1,42 @@ +#pragma once + +#include "iso14443_3a.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3aPoller Iso14443_3aPoller; + +typedef enum { + Iso14443_3aPollerEventTypeError, + Iso14443_3aPollerEventTypeReady, +} Iso14443_3aPollerEventType; + +typedef struct { + Iso14443_3aError error; +} Iso14443_3aPollerEventData; + +typedef struct { + Iso14443_3aPollerEventType type; + Iso14443_3aPollerEventData* data; +} Iso14443_3aPollerEvent; + +Iso14443_3aError iso14443_3a_poller_txrx( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +Iso14443_3aError iso14443_3a_poller_send_standard_frame( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h new file mode 100644 index 0000000000..1bcc684b40 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_3a; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c new file mode 100644 index 0000000000..3434dc8e35 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c @@ -0,0 +1,293 @@ +#include "iso14443_3a_poller_i.h" + +#include + +#include "nfc/helpers/iso14443_crc.h" + +#define TAG "ISO14443_3A" + +static Iso14443_3aError iso14443_3a_poller_process_error(NfcError error) { + Iso14443_3aError ret = Iso14443_3aErrorNone; + if(error == NfcErrorNone) { + ret = Iso14443_3aErrorNone; + } else if(error == NfcErrorTimeout) { + ret = Iso14443_3aErrorTimeout; + } else { + ret = Iso14443_3aErrorNotPresent; + } + return ret; +} + +static Iso14443_3aError iso14443_3a_poller_standard_frame_exchange( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + uint16_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - 2); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer); + Iso14443_3aError ret = Iso14443_3aErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_buffer)) { + ret = Iso14443_3aErrorWrongCrc; + break; + } + + iso14443_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance) { + furi_assert(instance); + furi_assert(instance->nfc); + + NfcError error = NfcErrorNone; + Iso14443_3aError ret = Iso14443_3aErrorNone; + do { + error = nfc_iso14443a_poller_trx_short_frame( + instance->nfc, + NfcIso14443aShortFrameSensReq, + instance->rx_buffer, + ISO14443_3A_FDT_LISTEN_FC); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(instance->col_res.sens_resp)) { + ret = Iso14443_3aErrorCommunication; + break; + } + } while(false); + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(instance->tx_buffer); + + uint8_t halt_cmd[2] = {0x50, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, halt_cmd, sizeof(halt_cmd)); + + iso14443_3a_poller_standard_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3A_FDT_LISTEN_FC); + + instance->state = Iso14443_3aPollerStateIdle; + return Iso14443_3aErrorNone; +} + +Iso14443_3aError iso14443_3a_poller_async_activate( + Iso14443_3aPoller* instance, + Iso14443_3aData* iso14443_3a_data) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + + // Reset Iso14443_3a poller state + memset(&instance->col_res, 0, sizeof(instance->col_res)); + memset(instance->data, 0, sizeof(Iso14443_3aData)); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Halt if necessary + if(instance->state != Iso14443_3aPollerStateIdle) { + iso14443_3a_poller_halt(instance); + instance->state = Iso14443_3aPollerStateIdle; + } + + NfcError error = NfcErrorNone; + Iso14443_3aError ret = Iso14443_3aErrorNone; + + bool activated = false; + do { + error = nfc_iso14443a_poller_trx_short_frame( + instance->nfc, + NfcIso14443aShortFrameSensReq, + instance->rx_buffer, + ISO14443_3A_FDT_LISTEN_FC); + if(error != NfcErrorNone) { + ret = Iso14443_3aErrorNotPresent; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(instance->col_res.sens_resp)) { + FURI_LOG_W(TAG, "Wrong sens response size"); + ret = Iso14443_3aErrorCommunication; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, + &instance->col_res.sens_resp, + sizeof(instance->col_res.sens_resp)); + memcpy( + instance->data->atqa, + &instance->col_res.sens_resp, + sizeof(instance->col_res.sens_resp)); + + instance->state = Iso14443_3aPollerStateColResInProgress; + instance->col_res.cascade_level = 0; + instance->col_res.state = Iso14443_3aPollerColResStateStateNewCascade; + + while(instance->state == Iso14443_3aPollerStateColResInProgress) { + if(instance->col_res.state == Iso14443_3aPollerColResStateStateNewCascade) { + bit_buffer_set_size_bytes(instance->tx_buffer, 2); + bit_buffer_set_byte( + instance->tx_buffer, + 0, + ISO14443_3A_POLLER_SEL_CMD(instance->col_res.cascade_level)); + bit_buffer_set_byte(instance->tx_buffer, 1, ISO14443_3A_POLLER_SEL_PAR(2, 0)); + error = nfc_iso14443a_poller_trx_sdd_frame( + instance->nfc, + instance->tx_buffer, + instance->rx_buffer, + ISO14443_3A_FDT_LISTEN_FC); + if(error != NfcErrorNone) { + FURI_LOG_E(TAG, "Sdd request failed: %d", error); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 5) { + FURI_LOG_E(TAG, "Sdd response wrong length"); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, &instance->col_res.sdd_resp, sizeof(Iso14443_3aSddResp)); + instance->col_res.state = Iso14443_3aPollerColResStateStateSelectCascade; + } else if(instance->col_res.state == Iso14443_3aPollerColResStateStateSelectCascade) { + instance->col_res.sel_req.sel_cmd = + ISO14443_3A_POLLER_SEL_CMD(instance->col_res.cascade_level); + instance->col_res.sel_req.sel_par = ISO14443_3A_POLLER_SEL_PAR(7, 0); + memcpy( + instance->col_res.sel_req.nfcid, + instance->col_res.sdd_resp.nfcid, + sizeof(instance->col_res.sdd_resp.nfcid)); + instance->col_res.sel_req.bcc = instance->col_res.sdd_resp.bss; + bit_buffer_copy_bytes( + instance->tx_buffer, + (uint8_t*)&instance->col_res.sel_req, + sizeof(instance->col_res.sel_req)); + ret = iso14443_3a_poller_send_standard_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3A_FDT_LISTEN_FC); + if(ret != Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "Sel request failed: %d", ret); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != + sizeof(instance->col_res.sel_resp)) { + FURI_LOG_E(TAG, "Sel response wrong length"); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, + &instance->col_res.sel_resp, + sizeof(instance->col_res.sel_resp)); + FURI_LOG_T(TAG, "Sel resp: %02X", instance->col_res.sel_resp.sak); + if(instance->col_res.sel_req.nfcid[0] == ISO14443_3A_POLLER_SDD_CL) { + // Copy part of UID + memcpy( + &instance->data->uid[instance->data->uid_len], + &instance->col_res.sel_req.nfcid[1], + 3); + instance->data->uid_len += 3; + instance->col_res.cascade_level++; + instance->col_res.state = Iso14443_3aPollerColResStateStateNewCascade; + } else { + FURI_LOG_T(TAG, "Col resolution complete"); + instance->data->sak = instance->col_res.sel_resp.sak; + memcpy( + &instance->data->uid[instance->data->uid_len], + &instance->col_res.sel_req.nfcid[0], + 4); + instance->data->uid_len += 4; + instance->col_res.state = Iso14443_3aPollerColResStateStateSuccess; + instance->state = Iso14443_3aPollerStateActivated; + } + } + } + + activated = (instance->state == Iso14443_3aPollerStateActivated); + } while(false); + + if(activated && iso14443_3a_data) { + *iso14443_3a_data = *instance->data; + } + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = + nfc_iso14443a_poller_trx_custom_parity(instance->nfc, tx_buffer, rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + } + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_txrx( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + } + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_send_standard_frame( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + Iso14443_3aError ret = + iso14443_3a_poller_standard_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + + return ret; +} diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h new file mode 100644 index 0000000000..063ef15566 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h @@ -0,0 +1,81 @@ +#pragma once + +#include "iso14443_3a_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_3A_POLLER_MAX_BUFFER_SIZE (512U) + +#define ISO14443_3A_POLLER_SEL_CMD(cascade_lvl) (0x93 + 2 * (cascade_lvl)) +#define ISO14443_3A_POLLER_SEL_PAR(bytes, bits) (((bytes) << 4 & 0xf0U) | ((bits)&0x0fU)) +#define ISO14443_3A_POLLER_SDD_CL (0x88U) + +typedef enum { + Iso14443_3aPollerColResStateStateIdle, + Iso14443_3aPollerColResStateStateNewCascade, + Iso14443_3aPollerColResStateStateSelectCascade, + Iso14443_3aPollerColResStateStateSuccess, + Iso14443_3aPollerColResStateStateFail, +} Iso14443_3aPollerColResState; + +typedef struct { + Iso14443_3aPollerColResState state; + Iso14443_3aSensResp sens_resp; + Iso14443_3aSddReq sdd_req; + Iso14443_3aSddResp sdd_resp; + Iso14443_3aSelReq sel_req; + Iso14443_3aSelResp sel_resp; + uint8_t cascade_level; +} Iso14443_3aPollerColRes; + +typedef enum { + Iso14443_3aPollerStateIdle, + Iso14443_3aPollerStateColResInProgress, + Iso14443_3aPollerStateColResFailed, + Iso14443_3aPollerStateActivated, +} Iso14443_3aPollerState; + +typedef enum { + Iso14443_3aPollerConfigStateIdle, + Iso14443_3aPollerConfigStateDone, +} Iso14443_3aPollerConfigState; + +struct Iso14443_3aPoller { + Nfc* nfc; + Iso14443_3aPollerState state; + Iso14443_3aPollerConfigState config_state; + Iso14443_3aPollerColRes col_res; + Iso14443_3aData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + Iso14443_3aPollerEvent iso14443_3a_event; + Iso14443_3aPollerEventData iso14443_3a_event_data; + NfcGenericCallback callback; + void* context; +}; + +const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance); + +Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance); + +Iso14443_3aError iso14443_3a_poller_async_activate( + Iso14443_3aPoller* instance, + Iso14443_3aData* iso14443_3a_data); + +Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance); + +Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c new file mode 100644 index 0000000000..2bab1a881a --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c @@ -0,0 +1,58 @@ +#include "iso14443_3a_poller_sync_api.h" + +#include "iso14443_3a_poller_i.h" +#include + +#include + +#define ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0) + +typedef struct { + Iso14443_3aPoller* instance; + FuriThreadId thread_id; + Iso14443_3aError error; + Iso14443_3aData data; +} Iso14443_3aPollerContext; + +NfcCommand iso14443_3a_poller_read_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + Iso14443_3aPollerContext* poller_context = context; + Iso14443_3aPoller* iso14443_3a_poller = event.instance; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + iso14443_3a_copy(&poller_context->data, iso14443_3a_poller->data); + } + poller_context->error = iso14443_3a_event->data->error; + + furi_thread_flags_set(poller_context->thread_id, ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE); + + return NfcCommandStop; +} + +Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data) { + furi_assert(nfc); + furi_assert(iso14443_3a_data); + + Iso14443_3aPollerContext poller_context = {}; + poller_context.thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + nfc_poller_start(poller, iso14443_3a_poller_read_callback, &poller_context); + furi_thread_flags_wait( + ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + if(poller_context.error == Iso14443_3aErrorNone) { + *iso14443_3a_data = poller_context.data; + } + + return poller_context.error; +} \ No newline at end of file diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h new file mode 100644 index 0000000000..ed17ff4324 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h @@ -0,0 +1,14 @@ +#pragma once + +#include "iso14443_3a.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b.c new file mode 100644 index 0000000000..fd81e390da --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b.c @@ -0,0 +1,223 @@ +#include "iso14443_3b_i.h" + +#include +#include + +#include +#include + +#define ISO14443_3B_PROTOCOL_NAME "ISO14443-3B" +#define ISO14443_3B_DEVICE_NAME "ISO14443-3B (Unknown)" + +#define ISO14443_3B_APP_DATA_KEY "Application data" +#define ISO14443_3B_PROTOCOL_INFO_KEY "Protocol info" + +#define ISO14443_3B_FDT_POLL_DEFAULT_FC (ISO14443_3B_FDT_POLL_FC) + +const NfcDeviceBase nfc_device_iso14443_3b = { + .protocol_name = ISO14443_3B_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_3b_alloc, + .free = (NfcDeviceFree)iso14443_3b_free, + .reset = (NfcDeviceReset)iso14443_3b_reset, + .copy = (NfcDeviceCopy)iso14443_3b_copy, + .verify = (NfcDeviceVerify)iso14443_3b_verify, + .load = (NfcDeviceLoad)iso14443_3b_load, + .save = (NfcDeviceSave)iso14443_3b_save, + .is_equal = (NfcDeviceEqual)iso14443_3b_is_equal, + .get_name = (NfcDeviceGetName)iso14443_3b_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_3b_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_3b_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_3b_get_base_data, +}; + +Iso14443_3bData* iso14443_3b_alloc() { + Iso14443_3bData* data = malloc(sizeof(Iso14443_3bData)); + return data; +} + +void iso14443_3b_free(Iso14443_3bData* data) { + furi_assert(data); + + free(data); +} + +void iso14443_3b_reset(Iso14443_3bData* data) { + memset(data, 0, sizeof(Iso14443_3bData)); +} + +void iso14443_3b_copy(Iso14443_3bData* data, const Iso14443_3bData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool iso14443_3b_verify(Iso14443_3bData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + // No support for old ISO14443-3B + return false; +} + +bool iso14443_3b_load(Iso14443_3bData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + if(version < NFC_UNIFIED_FORMAT_VERSION) break; + + if(!flipper_format_read_hex( + ff, ISO14443_3B_APP_DATA_KEY, data->app_data, ISO14443_3B_APP_DATA_SIZE)) + break; + if(!flipper_format_read_hex( + ff, + ISO14443_3B_PROTOCOL_INFO_KEY, + (uint8_t*)&data->protocol_info, + sizeof(Iso14443_3bProtocolInfo))) + break; + + parsed = true; + } while(false); + + return parsed; +} + +bool iso14443_3b_save(const Iso14443_3bData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, ISO14443_3B_PROTOCOL_NAME " specific data")) + break; + if(!flipper_format_write_hex( + ff, ISO14443_3B_APP_DATA_KEY, data->app_data, ISO14443_3B_APP_DATA_SIZE)) + break; + if(!flipper_format_write_hex( + ff, + ISO14443_3B_PROTOCOL_INFO_KEY, + (uint8_t*)&data->protocol_info, + sizeof(Iso14443_3bProtocolInfo))) + break; + saved = true; + } while(false); + + return saved; +} + +bool iso14443_3b_is_equal(const Iso14443_3bData* data, const Iso14443_3bData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(Iso14443_3bData)) == 0; +} + +const char* iso14443_3b_get_device_name(const Iso14443_3bData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + + return ISO14443_3B_DEVICE_NAME; +} + +const uint8_t* iso14443_3b_get_uid(const Iso14443_3bData* data, size_t* uid_len) { + furi_assert(data); + furi_assert(uid_len); + + *uid_len = ISO14443_3B_UID_SIZE; + return data->uid; +} + +bool iso14443_3b_set_uid(Iso14443_3bData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + const bool uid_valid = uid_len == ISO14443_3B_UID_SIZE; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + } + + return uid_valid; +} + +Iso14443_3bData* iso14443_3b_get_base_data(const Iso14443_3bData* data) { + UNUSED(data); + furi_crash("No base data"); +} + +bool iso14443_3b_supports_iso14443_4(const Iso14443_3bData* data) { + furi_assert(data); + + return data->protocol_info.protocol_type == 0x01; +} + +bool iso14443_3b_supports_bit_rate(const Iso14443_3bData* data, Iso14443_3bBitRate bit_rate) { + furi_assert(data); + + const uint8_t capability = data->protocol_info.bit_rate_capability; + + switch(bit_rate) { + case Iso14443_3bBitRateBoth106Kbit: + return capability == ISO14443_3B_BIT_RATE_BOTH_106KBIT; + case Iso14443_3bBitRatePiccToPcd212Kbit: + return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_212KBIT; + case Iso14443_3bBitRatePiccToPcd424Kbit: + return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_424KBIT; + case Iso14443_3bBitRatePiccToPcd848Kbit: + return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_848KBIT; + case Iso14443_3bBitRatePcdToPicc212Kbit: + return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_212KBIT; + case Iso14443_3bBitRatePcdToPicc424Kbit: + return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_424KBIT; + case Iso14443_3bBitRatePcdToPicc848Kbit: + return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_848KBIT; + default: + return false; + } +} + +bool iso14443_3b_supports_frame_option(const Iso14443_3bData* data, Iso14443_3bFrameOption option) { + furi_assert(data); + + switch(option) { + case Iso14443_3bFrameOptionNad: + return data->protocol_info.fo & ISO14443_3B_FRAME_OPTION_NAD; + case Iso14443_3bFrameOptionCid: + return data->protocol_info.fo & ISO14443_3B_FRAME_OPTION_CID; + default: + return false; + } +} + +const uint8_t* iso14443_3b_get_application_data(const Iso14443_3bData* data, size_t* data_size) { + furi_assert(data); + furi_assert(data_size); + + *data_size = ISO14443_3B_APP_DATA_SIZE; + return data->app_data; +} + +uint16_t iso14443_3b_get_frame_size_max(const Iso14443_3bData* data) { + furi_assert(data); + + const uint8_t fs_bits = data->protocol_info.max_frame_size; + + if(fs_bits < 5) { + return fs_bits * 8 + 16; + } else if(fs_bits == 5) { + return 64; + } else if(fs_bits == 6) { + return 96; + } else if(fs_bits < 13) { + return 128U << (fs_bits - 7); + } else { + return 0; + } +} + +uint32_t iso14443_3b_get_fwt_fc_max(const Iso14443_3bData* data) { + furi_assert(data); + + const uint8_t fwi = data->protocol_info.fwi; + return fwi < 0x0F ? 4096UL << fwi : ISO14443_3B_FDT_POLL_DEFAULT_FC; +} diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b.h new file mode 100644 index 0000000000..848e610c3e --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_3bErrorNone, + Iso14443_3bErrorNotPresent, + Iso14443_3bErrorColResFailed, + Iso14443_3bErrorBufferOverflow, + Iso14443_3bErrorCommunication, + Iso14443_3bErrorFieldOff, + Iso14443_3bErrorWrongCrc, + Iso14443_3bErrorTimeout, +} Iso14443_3bError; + +typedef enum { + Iso14443_3bBitRateBoth106Kbit, + Iso14443_3bBitRatePiccToPcd212Kbit, + Iso14443_3bBitRatePiccToPcd424Kbit, + Iso14443_3bBitRatePiccToPcd848Kbit, + Iso14443_3bBitRatePcdToPicc212Kbit, + Iso14443_3bBitRatePcdToPicc424Kbit, + Iso14443_3bBitRatePcdToPicc848Kbit, +} Iso14443_3bBitRate; + +typedef enum { + Iso14443_3bFrameOptionNad, + Iso14443_3bFrameOptionCid, +} Iso14443_3bFrameOption; + +typedef struct Iso14443_3bData Iso14443_3bData; + +// Virtual methods + +Iso14443_3bData* iso14443_3b_alloc(); + +void iso14443_3b_free(Iso14443_3bData* data); + +void iso14443_3b_reset(Iso14443_3bData* data); + +void iso14443_3b_copy(Iso14443_3bData* data, const Iso14443_3bData* other); + +bool iso14443_3b_verify(Iso14443_3bData* data, const FuriString* device_type); + +bool iso14443_3b_load(Iso14443_3bData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_3b_save(const Iso14443_3bData* data, FlipperFormat* ff); + +bool iso14443_3b_is_equal(const Iso14443_3bData* data, const Iso14443_3bData* other); + +const char* iso14443_3b_get_device_name(const Iso14443_3bData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_3b_get_uid(const Iso14443_3bData* data, size_t* uid_len); + +bool iso14443_3b_set_uid(Iso14443_3bData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3bData* iso14443_3b_get_base_data(const Iso14443_3bData* data); + +// Getters and tests + +bool iso14443_3b_supports_iso14443_4(const Iso14443_3bData* data); + +bool iso14443_3b_supports_bit_rate(const Iso14443_3bData* data, Iso14443_3bBitRate bit_rate); + +bool iso14443_3b_supports_frame_option(const Iso14443_3bData* data, Iso14443_3bFrameOption option); + +const uint8_t* iso14443_3b_get_application_data(const Iso14443_3bData* data, size_t* data_size); + +uint16_t iso14443_3b_get_frame_size_max(const Iso14443_3bData* data); + +uint32_t iso14443_3b_get_fwt_fc_max(const Iso14443_3bData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h new file mode 100644 index 0000000000..6c33900da2 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_3b; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c new file mode 100644 index 0000000000..cac7ed8266 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c @@ -0,0 +1 @@ +#include "iso14443_3b_i.h" diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h new file mode 100644 index 0000000000..f4ff36bd7e --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h @@ -0,0 +1,37 @@ +#pragma once + +#include "iso14443_3b.h" + +#define ISO14443_3B_UID_SIZE (4U) +#define ISO14443_3B_APP_DATA_SIZE (4U) + +#define ISO14443_3B_GUARD_TIME_US (5000U) +#define ISO14443_3B_FDT_POLL_FC (9000U) +#define ISO14443_3B_POLL_POLL_MIN_US (1280U) + +#define ISO14443_3B_BIT_RATE_BOTH_106KBIT (0U << 0) +#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_212KBIT (1U << 0) +#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_424KBIT (1U << 1) +#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_848KBIT (1U << 2) +#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_212KBIT (1U << 4) +#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_424KBIT (1U << 5) +#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_848KBIT (1U << 6) +#define ISO14443_3B_BIT_RATE_BOTH_SAME_COMPULSORY (1U << 7) + +#define ISO14443_3B_FRAME_OPTION_NAD (1U << 1) +#define ISO14443_3B_FRAME_OPTION_CID (1U << 0) + +typedef struct { + uint8_t bit_rate_capability; + uint8_t protocol_type : 4; + uint8_t max_frame_size : 4; + uint8_t fo : 2; + uint8_t adc : 2; + uint8_t fwi : 4; +} Iso14443_3bProtocolInfo; + +struct Iso14443_3bData { + uint8_t uid[ISO14443_3B_UID_SIZE]; + uint8_t app_data[ISO14443_3B_APP_DATA_SIZE]; + Iso14443_3bProtocolInfo protocol_info; +}; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c new file mode 100644 index 0000000000..9507f28c41 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c @@ -0,0 +1,121 @@ +#include "iso14443_3b_poller_i.h" + +#include + +#include + +#define TAG "ISO14443_3bPoller" + +const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static Iso14443_3bPoller* iso14443_3b_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + Iso14443_3bPoller* instance = malloc(sizeof(Iso14443_3bPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ISO14443_3B_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_3B_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443b); + nfc_set_guard_time_us(instance->nfc, ISO14443_3B_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ISO14443_3B_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ISO14443_3B_POLL_POLL_MIN_US); + instance->data = iso14443_3b_alloc(); + + instance->iso14443_3b_event.data = &instance->iso14443_3b_event_data; + instance->general_event.protocol = NfcProtocolIso14443_3b; + instance->general_event.event_data = &instance->iso14443_3b_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_3b_poller_free(Iso14443_3bPoller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + iso14443_3b_free(instance->data); + free(instance); +} + +static void iso14443_3b_poller_set_callback( + Iso14443_3bPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_3b_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso14443_3bPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != Iso14443_3bPollerStateActivated) { + Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + if(error == Iso14443_3bErrorNone) { + instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady; + instance->iso14443_3b_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeError; + instance->iso14443_3b_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady; + instance->iso14443_3b_event_data.error = Iso14443_3bErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool iso14443_3b_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + Iso14443_3bPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == Iso14443_3bPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + protocol_detected = (error == Iso14443_3bErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_3b = { + .alloc = (NfcPollerAlloc)iso14443_3b_poller_alloc, + .free = (NfcPollerFree)iso14443_3b_poller_free, + .set_callback = (NfcPollerSetCallback)iso14443_3b_poller_set_callback, + .run = (NfcPollerRun)iso14443_3b_poller_run, + .detect = (NfcPollerDetect)iso14443_3b_poller_detect, + .get_data = (NfcPollerGetData)iso14443_3b_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h new file mode 100644 index 0000000000..d25d9dbe9f --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h @@ -0,0 +1,30 @@ +#pragma once + +#include "iso14443_3b.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3bPoller Iso14443_3bPoller; + +typedef enum { + Iso14443_3bPollerEventTypeError, + Iso14443_3bPollerEventTypeReady, +} Iso14443_3bPollerEventType; + +typedef struct { + Iso14443_3bError error; +} Iso14443_3bPollerEventData; + +typedef struct { + Iso14443_3bPollerEventType type; + Iso14443_3bPollerEventData* data; +} Iso14443_3bPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h new file mode 100644 index 0000000000..60225310ab --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_3b; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c new file mode 100644 index 0000000000..95668ccf22 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c @@ -0,0 +1,194 @@ +#include "iso14443_3b_poller_i.h" + +#include + +#define TAG "Iso14443_3bPoller" + +#define ISO14443_3B_ATTRIB_FRAME_SIZE_256 (0x08) + +static Iso14443_3bError iso14443_3b_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return Iso14443_3bErrorNone; + case NfcErrorTimeout: + return Iso14443_3bErrorTimeout; + default: + return Iso14443_3bErrorNotPresent; + } +} + +static Iso14443_3bError iso14443_3b_poller_prepare_trx(Iso14443_3bPoller* instance) { + furi_assert(instance); + + if(instance->state == Iso14443_3bPollerStateIdle) { + return iso14443_3b_poller_async_activate(instance, NULL); + } + + return Iso14443_3bErrorNone; +} + +static Iso14443_3bError iso14443_3b_poller_frame_exchange( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert( + tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO14443_CRC_SIZE); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeB, instance->tx_buffer); + + Iso14443_3bError ret = Iso14443_3bErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3b_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeB, instance->rx_buffer)) { + ret = Iso14443_3bErrorWrongCrc; + break; + } + + iso14443_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +Iso14443_3bError + iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + iso14443_3b_reset(data); + + Iso14443_3bError ret; + + do { + instance->state = Iso14443_3bPollerStateColResInProgress; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send REQB + bit_buffer_append_byte(instance->tx_buffer, 0x05); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + bit_buffer_append_byte(instance->tx_buffer, 0x08); + + ret = iso14443_3b_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3B_FDT_POLL_FC); + if(ret != Iso14443_3bErrorNone) { + instance->state = Iso14443_3bPollerStateColResFailed; + break; + } + + typedef struct { + uint8_t flag; + uint8_t uid[ISO14443_3B_UID_SIZE]; + uint8_t app_data[ISO14443_3B_APP_DATA_SIZE]; + Iso14443_3bProtocolInfo protocol_info; + } Iso14443_3bAtqBLayout; + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(Iso14443_3bAtqBLayout)) { + FURI_LOG_D(TAG, "Unexpected REQB response"); + instance->state = Iso14443_3bPollerStateColResFailed; + ret = Iso14443_3bErrorCommunication; + break; + } + + instance->state = Iso14443_3bPollerStateActivationInProgress; + + const Iso14443_3bAtqBLayout* atqb = + (const Iso14443_3bAtqBLayout*)bit_buffer_get_data(instance->rx_buffer); + + memcpy(data->uid, atqb->uid, ISO14443_3B_UID_SIZE); + memcpy(data->app_data, atqb->app_data, ISO14443_3B_APP_DATA_SIZE); + + data->protocol_info = atqb->protocol_info; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send ATTRIB + bit_buffer_append_byte(instance->tx_buffer, 0x1d); + bit_buffer_append_bytes(instance->tx_buffer, data->uid, ISO14443_3B_UID_SIZE); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + bit_buffer_append_byte(instance->tx_buffer, ISO14443_3B_ATTRIB_FRAME_SIZE_256); + bit_buffer_append_byte(instance->tx_buffer, 0x01); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + ret = iso14443_3b_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, iso14443_3b_get_fwt_fc_max(data)); + if(ret != Iso14443_3bErrorNone) { + instance->state = Iso14443_3bPollerStateActivationFailed; + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1 || + bit_buffer_get_byte(instance->rx_buffer, 0) != 0) { + FURI_LOG_D(TAG, "Unexpected ATTRIB response"); + instance->state = Iso14443_3bPollerStateActivationFailed; + ret = Iso14443_3bErrorCommunication; + break; + } + + instance->state = Iso14443_3bPollerStateActivated; + } while(false); + + return ret; +} + +Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte(instance->tx_buffer, 0x50); + bit_buffer_append_bytes(instance->tx_buffer, instance->data->uid, ISO14443_3B_UID_SIZE); + + Iso14443_3bError ret; + + do { + ret = iso14443_3b_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3B_FDT_POLL_FC); + if(ret != Iso14443_3bErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(uint8_t) || + bit_buffer_get_byte(instance->rx_buffer, 0) != 0) { + ret = Iso14443_3bErrorCommunication; + break; + } + + instance->state = Iso14443_3bPollerStateIdle; + } while(false); + + return ret; +} + +Iso14443_3bError iso14443_3b_poller_send_frame( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + Iso14443_3bError ret; + + do { + ret = iso14443_3b_poller_prepare_trx(instance); + if(ret != Iso14443_3bErrorNone) break; + + ret = iso14443_3b_poller_frame_exchange( + instance, tx_buffer, rx_buffer, iso14443_3b_get_fwt_fc_max(instance->data)); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h new file mode 100644 index 0000000000..5821d6373f --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h @@ -0,0 +1,49 @@ +#pragma once + +#include "iso14443_3b_poller.h" +#include "iso14443_3b_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_3B_POLLER_MAX_BUFFER_SIZE (256U) + +typedef enum { + Iso14443_3bPollerStateIdle, + Iso14443_3bPollerStateColResInProgress, + Iso14443_3bPollerStateColResFailed, + Iso14443_3bPollerStateActivationInProgress, + Iso14443_3bPollerStateActivationFailed, + Iso14443_3bPollerStateActivated, +} Iso14443_3bPollerState; + +struct Iso14443_3bPoller { + Nfc* nfc; + Iso14443_3bPollerState state; + Iso14443_3bData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + Iso14443_3bPollerEvent iso14443_3b_event; + Iso14443_3bPollerEventData iso14443_3b_event_data; + NfcGenericCallback callback; + void* context; +}; + +const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance); + +Iso14443_3bError + iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data); + +Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance); + +Iso14443_3bError iso14443_3b_poller_send_frame( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a.c new file mode 100644 index 0000000000..9c2a530d53 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.c @@ -0,0 +1,300 @@ +#include "iso14443_4a_i.h" + +#include + +#define ISO14443_4A_PROTOCOL_NAME "ISO14443-4A" +#define ISO14443_4A_DEVICE_NAME "ISO14443-4A (Unknown)" + +#define ISO14443_4A_T0_KEY "T0" +#define ISO14443_4A_TA1_KEY "TA(1)" +#define ISO14443_4A_TB1_KEY "TB(1)" +#define ISO14443_4A_TC1_KEY "TC(1)" +#define ISO14443_4A_T1_TK_KEY "T1...Tk" + +#define ISO14443_4A_FDT_DEFAULT_FC ISO14443_3A_FDT_POLL_FC + +typedef enum { + Iso14443_4aInterfaceByteTA1, + Iso14443_4aInterfaceByteTB1, + Iso14443_4aInterfaceByteTC1, +} Iso14443_4aInterfaceByte; + +const NfcDeviceBase nfc_device_iso14443_4a = { + .protocol_name = ISO14443_4A_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_4a_alloc, + .free = (NfcDeviceFree)iso14443_4a_free, + .reset = (NfcDeviceReset)iso14443_4a_reset, + .copy = (NfcDeviceCopy)iso14443_4a_copy, + .verify = (NfcDeviceVerify)iso14443_4a_verify, + .load = (NfcDeviceLoad)iso14443_4a_load, + .save = (NfcDeviceSave)iso14443_4a_save, + .is_equal = (NfcDeviceEqual)iso14443_4a_is_equal, + .get_name = (NfcDeviceGetName)iso14443_4a_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_4a_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_4a_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_4a_get_base_data, +}; + +Iso14443_4aData* iso14443_4a_alloc() { + Iso14443_4aData* data = malloc(sizeof(Iso14443_4aData)); + + data->iso14443_3a_data = iso14443_3a_alloc(); + data->ats_data.t1_tk = simple_array_alloc(&simple_array_config_uint8_t); + + return data; +} + +void iso14443_4a_free(Iso14443_4aData* data) { + furi_assert(data); + + simple_array_free(data->ats_data.t1_tk); + iso14443_3a_free(data->iso14443_3a_data); + + free(data); +} + +void iso14443_4a_reset(Iso14443_4aData* data) { + furi_assert(data); + + iso14443_3a_reset(data->iso14443_3a_data); + + data->ats_data.tl = 1; + data->ats_data.t0 = 0; + data->ats_data.ta_1 = 0; + data->ats_data.tb_1 = 0; + data->ats_data.tc_1 = 0; + + simple_array_reset(data->ats_data.t1_tk); +} + +void iso14443_4a_copy(Iso14443_4aData* data, const Iso14443_4aData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data); + + data->ats_data.tl = other->ats_data.tl; + data->ats_data.t0 = other->ats_data.t0; + data->ats_data.ta_1 = other->ats_data.ta_1; + data->ats_data.tb_1 = other->ats_data.tb_1; + data->ats_data.tc_1 = other->ats_data.tc_1; + + simple_array_copy(data->ats_data.t1_tk, other->ats_data.t1_tk); +} + +bool iso14443_4a_verify(Iso14443_4aData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + // Empty, unified file format only + return false; +} + +bool iso14443_4a_load(Iso14443_4aData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break; + + Iso14443_4aAtsData* ats_data = &data->ats_data; + + ats_data->tl = 1; + + if(flipper_format_key_exist(ff, ISO14443_4A_T0_KEY)) { + if(!flipper_format_read_hex(ff, ISO14443_4A_T0_KEY, &ats_data->t0, 1)) break; + ++ats_data->tl; + } + + if(ats_data->t0 & ISO14443_4A_ATS_T0_TA1) { + if(!flipper_format_key_exist(ff, ISO14443_4A_TA1_KEY)) break; + if(!flipper_format_read_hex(ff, ISO14443_4A_TA1_KEY, &ats_data->ta_1, 1)) break; + ++ats_data->tl; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TB1) { + if(!flipper_format_key_exist(ff, ISO14443_4A_TB1_KEY)) break; + if(!flipper_format_read_hex(ff, ISO14443_4A_TB1_KEY, &ats_data->tb_1, 1)) break; + ++ats_data->tl; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TC1) { + if(!flipper_format_key_exist(ff, ISO14443_4A_TC1_KEY)) break; + if(!flipper_format_read_hex(ff, ISO14443_4A_TC1_KEY, &ats_data->tc_1, 1)) break; + ++ats_data->tl; + } + + if(flipper_format_key_exist(ff, ISO14443_4A_T1_TK_KEY)) { + uint32_t t1_tk_size; + if(!flipper_format_get_value_count(ff, ISO14443_4A_T1_TK_KEY, &t1_tk_size)) break; + + if(t1_tk_size > 0) { + simple_array_init(ats_data->t1_tk, t1_tk_size); + if(!flipper_format_read_hex( + ff, + ISO14443_4A_T1_TK_KEY, + simple_array_get_data(ats_data->t1_tk), + t1_tk_size)) + break; + ats_data->tl += t1_tk_size; + } + } + parsed = true; + } while(false); + + return parsed; +} + +bool iso14443_4a_save(const Iso14443_4aData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break; + if(!flipper_format_write_comment_cstr(ff, ISO14443_4A_PROTOCOL_NAME " specific data")) + break; + + const Iso14443_4aAtsData* ats_data = &data->ats_data; + + if(ats_data->tl > 1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_T0_KEY, &ats_data->t0, 1)) break; + + if(ats_data->t0 & ISO14443_4A_ATS_T0_TA1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_TA1_KEY, &ats_data->ta_1, 1)) break; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TB1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_TB1_KEY, &ats_data->tb_1, 1)) break; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TC1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_TC1_KEY, &ats_data->tc_1, 1)) break; + } + + const uint32_t t1_tk_size = simple_array_get_count(ats_data->t1_tk); + if(t1_tk_size > 0) { + if(!flipper_format_write_hex( + ff, + ISO14443_4A_T1_TK_KEY, + simple_array_cget_data(ats_data->t1_tk), + t1_tk_size)) + break; + } + } + saved = true; + } while(false); + + return saved; +} + +bool iso14443_4a_is_equal(const Iso14443_4aData* data, const Iso14443_4aData* other) { + return iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data); +} + +const char* iso14443_4a_get_device_name(const Iso14443_4aData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return ISO14443_4A_DEVICE_NAME; +} + +const uint8_t* iso14443_4a_get_uid(const Iso14443_4aData* data, size_t* uid_len) { + return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len); +} + +bool iso14443_4a_set_uid(Iso14443_4aData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); +} + +Iso14443_3aData* iso14443_4a_get_base_data(const Iso14443_4aData* data) { + furi_assert(data); + + return data->iso14443_3a_data; +} + +uint16_t iso14443_4a_get_frame_size_max(const Iso14443_4aData* data) { + furi_assert(data); + + const uint8_t fsci = data->ats_data.t0 & 0x0F; + + if(fsci < 5) { + return fsci * 8 + 16; + } else if(fsci == 5) { + return 64; + } else if(fsci == 6) { + return 96; + } else if(fsci < 13) { + return 128U << (fsci - 7); + } else { + return 0; + } +} + +uint32_t iso14443_4a_get_fwt_fc_max(const Iso14443_4aData* data) { + furi_assert(data); + + uint32_t fwt_fc_max = ISO14443_4A_FDT_DEFAULT_FC; + + do { + if(!(data->ats_data.tl > 1)) break; + if(!(data->ats_data.t0 & ISO14443_4A_ATS_T0_TB1)) break; + + const uint8_t fwi = data->ats_data.tb_1 >> 4; + if(fwi == 0x0F) break; + + fwt_fc_max = 4096UL << fwi; + } while(false); + + return fwt_fc_max; +} + +const uint8_t* iso14443_4a_get_historical_bytes(const Iso14443_4aData* data, uint32_t* count) { + furi_assert(data); + furi_assert(count); + + *count = simple_array_get_count(data->ats_data.t1_tk); + return simple_array_cget_data(data->ats_data.t1_tk); +} + +bool iso14443_4a_supports_bit_rate(const Iso14443_4aData* data, Iso14443_4aBitRate bit_rate) { + furi_assert(data); + + if(!(data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1)) + return bit_rate == Iso14443_4aBitRateBoth106Kbit; + + const uint8_t ta_1 = data->ats_data.ta_1; + + switch(bit_rate) { + case Iso14443_4aBitRateBoth106Kbit: + return ta_1 == ISO14443_4A_ATS_TA1_BOTH_SAME_COMPULSORY; + case Iso14443_4aBitRatePiccToPcd212Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_212KBIT; + case Iso14443_4aBitRatePiccToPcd424Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_424KBIT; + case Iso14443_4aBitRatePiccToPcd848Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_848KBIT; + case Iso14443_4aBitRatePcdToPicc212Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_212KBIT; + case Iso14443_4aBitRatePcdToPicc424Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_424KBIT; + case Iso14443_4aBitRatePcdToPicc848Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_848KBIT; + default: + return false; + } +} + +bool iso14443_4a_supports_frame_option(const Iso14443_4aData* data, Iso14443_4aFrameOption option) { + furi_assert(data); + + const Iso14443_4aAtsData* ats_data = &data->ats_data; + if(!(ats_data->t0 & ISO14443_4A_ATS_T0_TC1)) return false; + + switch(option) { + case Iso14443_4aFrameOptionNad: + return ats_data->tc_1 & ISO14443_4A_ATS_TC1_NAD; + case Iso14443_4aFrameOptionCid: + return ats_data->tc_1 & ISO14443_4A_ATS_TC1_CID; + default: + return false; + } +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h new file mode 100644 index 0000000000..9580c14040 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4aErrorNone, + Iso14443_4aErrorNotPresent, + Iso14443_4aErrorProtocol, + Iso14443_4aErrorTimeout, +} Iso14443_4aError; + +typedef enum { + Iso14443_4aBitRateBoth106Kbit, + Iso14443_4aBitRatePiccToPcd212Kbit, + Iso14443_4aBitRatePiccToPcd424Kbit, + Iso14443_4aBitRatePiccToPcd848Kbit, + Iso14443_4aBitRatePcdToPicc212Kbit, + Iso14443_4aBitRatePcdToPicc424Kbit, + Iso14443_4aBitRatePcdToPicc848Kbit, +} Iso14443_4aBitRate; + +typedef enum { + Iso14443_4aFrameOptionNad, + Iso14443_4aFrameOptionCid, +} Iso14443_4aFrameOption; + +typedef struct Iso14443_4aData Iso14443_4aData; + +// Virtual methods + +Iso14443_4aData* iso14443_4a_alloc(); + +void iso14443_4a_free(Iso14443_4aData* data); + +void iso14443_4a_reset(Iso14443_4aData* data); + +void iso14443_4a_copy(Iso14443_4aData* data, const Iso14443_4aData* other); + +bool iso14443_4a_verify(Iso14443_4aData* data, const FuriString* device_type); + +bool iso14443_4a_load(Iso14443_4aData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_4a_save(const Iso14443_4aData* data, FlipperFormat* ff); + +bool iso14443_4a_is_equal(const Iso14443_4aData* data, const Iso14443_4aData* other); + +const char* iso14443_4a_get_device_name(const Iso14443_4aData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_4a_get_uid(const Iso14443_4aData* data, size_t* uid_len); + +bool iso14443_4a_set_uid(Iso14443_4aData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* iso14443_4a_get_base_data(const Iso14443_4aData* data); + +// Getters & Tests + +uint16_t iso14443_4a_get_frame_size_max(const Iso14443_4aData* data); + +uint32_t iso14443_4a_get_fwt_fc_max(const Iso14443_4aData* data); + +const uint8_t* iso14443_4a_get_historical_bytes(const Iso14443_4aData* data, uint32_t* count); + +bool iso14443_4a_supports_bit_rate(const Iso14443_4aData* data, Iso14443_4aBitRate bit_rate); + +bool iso14443_4a_supports_frame_option(const Iso14443_4aData* data, Iso14443_4aFrameOption option); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h new file mode 100644 index 0000000000..db372f8103 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_4a; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c new file mode 100644 index 0000000000..f6e3acc5c1 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c @@ -0,0 +1,71 @@ +#include "iso14443_4a_i.h" + +bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf) { + bool can_parse = false; + + do { + const size_t buf_size = bit_buffer_get_size_bytes(buf); + if(buf_size == 0) break; + + size_t current_index = 0; + + const uint8_t tl = bit_buffer_get_byte(buf, current_index++); + if(tl != buf_size) break; + + data->tl = tl; + + if(tl > 1) { + const uint8_t t0 = bit_buffer_get_byte(buf, current_index++); + + const bool has_ta_1 = t0 & ISO14443_4A_ATS_T0_TA1; + const bool has_tb_1 = t0 & ISO14443_4A_ATS_T0_TB1; + const bool has_tc_1 = t0 & ISO14443_4A_ATS_T0_TC1; + + const uint8_t buf_size_min = + 2 + (has_ta_1 ? 1 : 0) + (has_tb_1 ? 1 : 0) + (has_tc_1 ? 1 : 0); + + if(buf_size < buf_size_min) break; + + data->t0 = t0; + + if(has_ta_1) { + data->ta_1 = bit_buffer_get_byte(buf, current_index++); + } + if(has_tb_1) { + data->tb_1 = bit_buffer_get_byte(buf, current_index++); + } + if(has_tc_1) { + data->tc_1 = bit_buffer_get_byte(buf, current_index++); + } + + const uint8_t t1_tk_size = buf_size - buf_size_min; + + if(t1_tk_size > 0) { + simple_array_init(data->t1_tk, t1_tk_size); + bit_buffer_write_bytes_mid( + buf, simple_array_get_data(data->t1_tk), current_index, t1_tk_size); + } + } + + can_parse = true; + } while(false); + + return can_parse; +} + +Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error) { + switch(error) { + case Iso14443_3aErrorNone: + return Iso14443_4aErrorNone; + case Iso14443_3aErrorNotPresent: + return Iso14443_4aErrorNotPresent; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + return Iso14443_4aErrorProtocol; + case Iso14443_3aErrorTimeout: + return Iso14443_4aErrorTimeout; + default: + return Iso14443_4aErrorProtocol; + } +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h new file mode 100644 index 0000000000..e45fb90cca --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h @@ -0,0 +1,42 @@ +#pragma once + +#include "iso14443_4a.h" + +#include + +#define ISO14443_4A_CMD_READ_ATS (0xE0) + +// ATS bit definitions +#define ISO14443_4A_ATS_T0_TA1 (1U << 4) +#define ISO14443_4A_ATS_T0_TB1 (1U << 5) +#define ISO14443_4A_ATS_T0_TC1 (1U << 6) + +#define ISO14443_4A_ATS_TA1_BOTH_106KBIT (0U << 0) +#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_212KBIT (1U << 0) +#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_424KBIT (1U << 1) +#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_848KBIT (1U << 2) +#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_212KBIT (1U << 4) +#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_424KBIT (1U << 5) +#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_848KBIT (1U << 6) +#define ISO14443_4A_ATS_TA1_BOTH_SAME_COMPULSORY (1U << 7) + +#define ISO14443_4A_ATS_TC1_NAD (1U << 0) +#define ISO14443_4A_ATS_TC1_CID (1U << 1) + +typedef struct { + uint8_t tl; + uint8_t t0; + uint8_t ta_1; + uint8_t tb_1; + uint8_t tc_1; + SimpleArray* t1_tk; +} Iso14443_4aAtsData; + +struct Iso14443_4aData { + Iso14443_3aData* iso14443_3a_data; + Iso14443_4aAtsData ats_data; +}; + +bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf); + +Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c new file mode 100644 index 0000000000..95612bf54d --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -0,0 +1,99 @@ +#include "iso14443_4a_listener_i.h" + +#include +#include + +#define TAG "Iso14443_4aListener" + +#define ISO14443_4A_LISTENER_BUF_SIZE (256U) + +static Iso14443_4aListener* + iso14443_4a_listener_alloc(Iso14443_3aListener* iso14443_3a_listener, Iso14443_4aData* data) { + furi_assert(iso14443_3a_listener); + + Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener)); + instance->iso14443_3a_listener = iso14443_3a_listener; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); + + instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data; + instance->generic_event.protocol = NfcProtocolIso14443_4a; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->iso14443_4a_event; + + return instance; +} + +static void iso14443_4a_listener_free(Iso14443_4aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +static void iso14443_4a_listener_set_callback( + Iso14443_4aListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +static const Iso14443_4aData* iso14443_4a_listener_get_data(Iso14443_4aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + + Iso14443_4aListener* instance = context; + Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; + BitBuffer* rx_buffer = iso14443_3a_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { + if(instance->state == Iso14443_4aListenerStateIdle) { + if(bit_buffer_get_size_bytes(rx_buffer) == 2 && + bit_buffer_get_byte(rx_buffer, 0) == ISO14443_4A_CMD_READ_ATS) { + if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) != + Iso14443_4aErrorNone) { + command = NfcCommandContinue; + } else { + instance->state = Iso14443_4aListenerStateActive; + } + } + } else { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; + instance->iso14443_4a_event.data->buffer = rx_buffer; + + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } + } + } else if( + iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || + iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { + instance->state = Iso14443_4aListenerStateIdle; + command = NfcCommandContinue; + } + + return command; +} + +const NfcListenerBase nfc_listener_iso14443_4a = { + .alloc = (NfcListenerAlloc)iso14443_4a_listener_alloc, + .free = (NfcListenerFree)iso14443_4a_listener_free, + .set_callback = (NfcListenerSetCallback)iso14443_4a_listener_set_callback, + .get_data = (NfcListenerGetData)iso14443_4a_listener_get_data, + .run = (NfcListenerRun)iso14443_4a_listener_run, +}; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h new file mode 100644 index 0000000000..ba649847b2 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "iso14443_4a.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4aListener Iso14443_4aListener; + +typedef enum { + Iso14443_4aListenerEventTypeHalted, + Iso14443_4aListenerEventTypeReceivedData, +} Iso14443_4aListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Iso14443_4aListenerEventData; + +typedef struct { + Iso14443_4aListenerEventType type; + Iso14443_4aListenerEventData* data; +} Iso14443_4aListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h new file mode 100644 index 0000000000..ef70a5e141 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_iso14443_4a; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c new file mode 100644 index 0000000000..8590c22ade --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c @@ -0,0 +1,32 @@ +#include "iso14443_4a_listener_i.h" + +#include + +Iso14443_4aError + iso14443_4a_listener_send_ats(Iso14443_4aListener* instance, const Iso14443_4aAtsData* data) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, data->tl); + + if(data->tl > 1) { + bit_buffer_append_byte(instance->tx_buffer, data->t0); + if(data->t0 & ISO14443_4A_ATS_T0_TA1) { + bit_buffer_append_byte(instance->tx_buffer, data->ta_1); + } + if(data->t0 & ISO14443_4A_ATS_T0_TB1) { + bit_buffer_append_byte(instance->tx_buffer, data->tb_1); + } + if(data->t0 & ISO14443_4A_ATS_T0_TC1) { + bit_buffer_append_byte(instance->tx_buffer, data->tc_1); + } + + const uint32_t t1_tk_size = simple_array_get_count(data->t1_tk); + if(t1_tk_size != 0) { + bit_buffer_append_bytes( + instance->tx_buffer, simple_array_cget_data(data->t1_tk), t1_tk_size); + } + } + + const Iso14443_3aError error = iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + return iso14443_4a_process_error(error); +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h new file mode 100644 index 0000000000..d4e884f6f0 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "iso14443_4a_listener.h" +#include "iso14443_4a_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4aListenerStateIdle, + Iso14443_4aListenerStateActive, +} Iso14443_4aListenerState; + +struct Iso14443_4aListener { + Iso14443_3aListener* iso14443_3a_listener; + Iso14443_4aData* data; + Iso14443_4aListenerState state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Iso14443_4aListenerEvent iso14443_4a_event; + Iso14443_4aListenerEventData iso14443_4a_event_data; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_4aError + iso14443_4a_listener_send_ats(Iso14443_4aListener* instance, const Iso14443_4aAtsData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c new file mode 100644 index 0000000000..c07cc6b7f0 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c @@ -0,0 +1,154 @@ +#include "iso14443_4a_poller_i.h" + +#include + +#include + +#define TAG "Iso14443_4aPoller" + +#define ISO14443_4A_POLLER_BUF_SIZE (256U) + +typedef NfcCommand (*Iso14443_4aPollerStateHandler)(Iso14443_4aPoller* instance); + +const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Iso14443_4aPoller* iso14443_4a_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { + Iso14443_4aPoller* instance = malloc(sizeof(Iso14443_4aPoller)); + instance->iso14443_3a_poller = iso14443_3a_poller; + instance->data = iso14443_4a_alloc(); + instance->iso14443_4_layer = iso14443_4_layer_alloc(); + instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + + instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data; + + instance->general_event.protocol = NfcProtocolIso14443_4a; + instance->general_event.event_data = &instance->iso14443_4a_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_4a_poller_free(Iso14443_4aPoller* instance) { + furi_assert(instance); + + iso14443_4a_free(instance->data); + iso14443_4_layer_free(instance->iso14443_4_layer); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand iso14443_4a_poller_handler_idle(Iso14443_4aPoller* instance) { + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + + iso14443_4_layer_reset(instance->iso14443_4_layer); + + instance->poller_state = Iso14443_4aPollerStateReadAts; + return NfcCommandContinue; +} + +static NfcCommand iso14443_4a_poller_handler_read_ats(Iso14443_4aPoller* instance) { + Iso14443_4aError error = + iso14443_4a_poller_async_read_ats(instance, &instance->data->ats_data); + if(error == Iso14443_4aErrorNone) { + FURI_LOG_D(TAG, "Read ATS success"); + instance->poller_state = Iso14443_4aPollerStateReady; + } else { + FURI_LOG_D(TAG, "Failed to read ATS"); + instance->poller_state = Iso14443_4aPollerStateError; + } + + return NfcCommandContinue; +} + +static NfcCommand iso14443_4a_poller_handler_error(Iso14443_4aPoller* instance) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->iso14443_4a_event_data.error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->poller_state = Iso14443_4aPollerStateIdle; + return command; +} + +static NfcCommand iso14443_4a_poller_handler_ready(Iso14443_4aPoller* instance) { + instance->iso14443_4a_event.type = Iso14443_4aPollerEventTypeReady; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Iso14443_4aPollerStateHandler + iso14443_4a_poller_state_handler[Iso14443_4aPollerStateNum] = { + [Iso14443_4aPollerStateIdle] = iso14443_4a_poller_handler_idle, + [Iso14443_4aPollerStateReadAts] = iso14443_4a_poller_handler_read_ats, + [Iso14443_4aPollerStateError] = iso14443_4a_poller_handler_error, + [Iso14443_4aPollerStateReady] = iso14443_4a_poller_handler_ready, +}; + +static void iso14443_4a_poller_set_callback( + Iso14443_4aPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_4a_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + Iso14443_4aPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + furi_assert(iso14443_3a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + command = iso14443_4a_poller_state_handler[instance->poller_state](instance); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + instance->iso14443_4a_event.type = Iso14443_4aPollerEventTypeError; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool iso14443_4a_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + const Iso14443_4aPoller* instance = context; + furi_assert(instance); + + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + furi_assert(iso14443_3a_event); + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + + bool protocol_detected = false; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + protocol_detected = iso14443_3a_supports_iso14443_4(instance->data->iso14443_3a_data); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_4a = { + .alloc = (NfcPollerAlloc)iso14443_4a_poller_alloc, + .free = (NfcPollerFree)iso14443_4a_poller_free, + .set_callback = (NfcPollerSetCallback)iso14443_4a_poller_set_callback, + .run = (NfcPollerRun)iso14443_4a_poller_run, + .detect = (NfcPollerDetect)iso14443_4a_poller_detect, + .get_data = (NfcPollerGetData)iso14443_4a_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h new file mode 100644 index 0000000000..b224299e0a --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "iso14443_4a.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4aPoller Iso14443_4aPoller; + +typedef enum { + Iso14443_4aPollerEventTypeError, + Iso14443_4aPollerEventTypeReady, +} Iso14443_4aPollerEventType; + +typedef struct { + Iso14443_4aError error; +} Iso14443_4aPollerEventData; + +typedef struct { + Iso14443_4aPollerEventType type; + Iso14443_4aPollerEventData* data; +} Iso14443_4aPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h new file mode 100644 index 0000000000..aa62166742 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_4a; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c new file mode 100644 index 0000000000..7221e2aa94 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -0,0 +1,83 @@ +#include "iso14443_4a_poller_i.h" + +#include + +#include "iso14443_4a_i.h" + +#define TAG "Iso14443_4aPoller" + +#define ISO14443_4A_FSDI_256 (0x8U) + +Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) { + furi_assert(instance); + + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->poller_state = Iso14443_4aPollerStateIdle; + + return Iso14443_4aErrorNone; +} + +Iso14443_4aError + iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO14443_4A_CMD_READ_ATS); + bit_buffer_append_byte(instance->tx_buffer, ISO14443_4A_FSDI_256 << 4); + + Iso14443_4aError error = Iso14443_4aErrorNone; + + do { + const Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + ISO14443_4A_POLLER_ATS_FWT_FC); + + if(iso14443_3a_error != Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "ATS request failed"); + error = iso14443_4a_process_error(iso14443_3a_error); + break; + + } else if(!iso14443_4a_ats_parse(data, instance->rx_buffer)) { + FURI_LOG_E(TAG, "Failed to parse ATS response"); + error = Iso14443_4aErrorProtocol; + break; + } + + } while(false); + + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + + Iso14443_4aError error = Iso14443_4aErrorNone; + + do { + Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + iso14443_4a_get_fwt_fc_max(instance->data)); + + if(iso14443_3a_error != Iso14443_3aErrorNone) { + error = iso14443_4a_process_error(iso14443_3a_error); + break; + + } else if(!iso14443_4_layer_decode_block( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { + error = Iso14443_4aErrorProtocol; + break; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h new file mode 100644 index 0000000000..1113d381c2 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include "iso14443_4a_poller.h" +#include "iso14443_4a_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_4A_POLLER_ATS_FWT_FC (40000) + +typedef enum { + Iso14443_4aPollerStateIdle, + Iso14443_4aPollerStateReadAts, + Iso14443_4aPollerStateError, + Iso14443_4aPollerStateReady, + + Iso14443_4aPollerStateNum, +} Iso14443_4aPollerState; + +typedef enum { + Iso14443_4aPollerSessionStateIdle, + Iso14443_4aPollerSessionStateActive, + Iso14443_4aPollerSessionStateStopRequest, +} Iso14443_4aPollerSessionState; + +struct Iso14443_4aPoller { + Iso14443_3aPoller* iso14443_3a_poller; + Iso14443_4aPollerState poller_state; + Iso14443_4aPollerSessionState session_state; + Iso14443_4aError error; + Iso14443_4aData* data; + Iso14443_4Layer* iso14443_4_layer; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Iso14443_4aPollerEventData iso14443_4a_event_data; + Iso14443_4aPollerEvent iso14443_4a_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); + +const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance); + +Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); + +Iso14443_4aError + iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data); + +Iso14443_4aError iso14443_4a_poller_send_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b.c new file mode 100644 index 0000000000..19f2939da0 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b.c @@ -0,0 +1,94 @@ +#include "iso14443_4b_i.h" + +#include +#include + +#define ISO14443_4B_PROTOCOL_NAME "ISO14443-4B" +#define ISO14443_4B_DEVICE_NAME "ISO14443-4B (Unknown)" + +const NfcDeviceBase nfc_device_iso14443_4b = { + .protocol_name = ISO14443_4B_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_4b_alloc, + .free = (NfcDeviceFree)iso14443_4b_free, + .reset = (NfcDeviceReset)iso14443_4b_reset, + .copy = (NfcDeviceCopy)iso14443_4b_copy, + .verify = (NfcDeviceVerify)iso14443_4b_verify, + .load = (NfcDeviceLoad)iso14443_4b_load, + .save = (NfcDeviceSave)iso14443_4b_save, + .is_equal = (NfcDeviceEqual)iso14443_4b_is_equal, + .get_name = (NfcDeviceGetName)iso14443_4b_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_4b_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_4b_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_4b_get_base_data, +}; + +Iso14443_4bData* iso14443_4b_alloc() { + Iso14443_4bData* data = malloc(sizeof(Iso14443_4bData)); + + data->iso14443_3b_data = iso14443_3b_alloc(); + return data; +} + +void iso14443_4b_free(Iso14443_4bData* data) { + furi_assert(data); + + iso14443_3b_free(data->iso14443_3b_data); + free(data); +} + +void iso14443_4b_reset(Iso14443_4bData* data) { + furi_assert(data); + + iso14443_3b_reset(data->iso14443_3b_data); +} + +void iso14443_4b_copy(Iso14443_4bData* data, const Iso14443_4bData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3b_copy(data->iso14443_3b_data, other->iso14443_3b_data); +} + +bool iso14443_4b_verify(Iso14443_4bData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + // Empty, unified file format only + return false; +} + +bool iso14443_4b_load(Iso14443_4bData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + return iso14443_3b_load(data->iso14443_3b_data, ff, version); +} + +bool iso14443_4b_save(const Iso14443_4bData* data, FlipperFormat* ff) { + furi_assert(data); + return iso14443_3b_save(data->iso14443_3b_data, ff); +} + +bool iso14443_4b_is_equal(const Iso14443_4bData* data, const Iso14443_4bData* other) { + return iso14443_3b_is_equal(data->iso14443_3b_data, other->iso14443_3b_data); +} + +const char* iso14443_4b_get_device_name(const Iso14443_4bData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return ISO14443_4B_DEVICE_NAME; +} + +const uint8_t* iso14443_4b_get_uid(const Iso14443_4bData* data, size_t* uid_len) { + return iso14443_3b_get_uid(data->iso14443_3b_data, uid_len); +} + +bool iso14443_4b_set_uid(Iso14443_4bData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3b_set_uid(data->iso14443_3b_data, uid, uid_len); +} + +Iso14443_3bData* iso14443_4b_get_base_data(const Iso14443_4bData* data) { + furi_assert(data); + + return data->iso14443_3b_data; +} diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b.h new file mode 100644 index 0000000000..1f269ed914 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4bErrorNone, + Iso14443_4bErrorNotPresent, + Iso14443_4bErrorProtocol, + Iso14443_4bErrorTimeout, +} Iso14443_4bError; + +typedef struct Iso14443_4bData Iso14443_4bData; + +// Virtual methods + +Iso14443_4bData* iso14443_4b_alloc(); + +void iso14443_4b_free(Iso14443_4bData* data); + +void iso14443_4b_reset(Iso14443_4bData* data); + +void iso14443_4b_copy(Iso14443_4bData* data, const Iso14443_4bData* other); + +bool iso14443_4b_verify(Iso14443_4bData* data, const FuriString* device_type); + +bool iso14443_4b_load(Iso14443_4bData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_4b_save(const Iso14443_4bData* data, FlipperFormat* ff); + +bool iso14443_4b_is_equal(const Iso14443_4bData* data, const Iso14443_4bData* other); + +const char* iso14443_4b_get_device_name(const Iso14443_4bData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_4b_get_uid(const Iso14443_4bData* data, size_t* uid_len); + +bool iso14443_4b_set_uid(Iso14443_4bData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3bData* iso14443_4b_get_base_data(const Iso14443_4bData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h new file mode 100644 index 0000000000..8185c9aeeb --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_4b; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c new file mode 100644 index 0000000000..1df8806408 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c @@ -0,0 +1,18 @@ +#include "iso14443_4b_i.h" + +Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error) { + switch(error) { + case Iso14443_3bErrorNone: + return Iso14443_4bErrorNone; + case Iso14443_3bErrorNotPresent: + return Iso14443_4bErrorNotPresent; + case Iso14443_3bErrorColResFailed: + case Iso14443_3bErrorCommunication: + case Iso14443_3bErrorWrongCrc: + return Iso14443_4bErrorProtocol; + case Iso14443_3bErrorTimeout: + return Iso14443_4bErrorTimeout; + default: + return Iso14443_4bErrorProtocol; + } +} diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h new file mode 100644 index 0000000000..6090c03936 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h @@ -0,0 +1,9 @@ +#pragma once + +#include "iso14443_4b.h" + +struct Iso14443_4bData { + Iso14443_3bData* iso14443_3b_data; +}; + +Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error); diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c new file mode 100644 index 0000000000..1030ebfb6f --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c @@ -0,0 +1,138 @@ +#include "iso14443_4b_poller_i.h" + +#include + +#include + +#define TAG "Iso14443_4bPoller" + +#define ISO14443_4A_POLLER_BUF_SIZE (256U) + +typedef NfcCommand (*Iso14443_4bPollerStateHandler)(Iso14443_4bPoller* instance); + +const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Iso14443_4bPoller* iso14443_4b_poller_alloc(Iso14443_3bPoller* iso14443_3b_poller) { + Iso14443_4bPoller* instance = malloc(sizeof(Iso14443_4bPoller)); + instance->iso14443_3b_poller = iso14443_3b_poller; + instance->data = iso14443_4b_alloc(); + instance->iso14443_4_layer = iso14443_4_layer_alloc(); + instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + + instance->iso14443_4b_event.data = &instance->iso14443_4b_event_data; + + instance->general_event.protocol = NfcProtocolIso14443_4b; + instance->general_event.event_data = &instance->iso14443_4b_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_4b_poller_free(Iso14443_4bPoller* instance) { + furi_assert(instance); + + iso14443_4b_free(instance->data); + iso14443_4_layer_free(instance->iso14443_4_layer); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand iso14443_4b_poller_handler_idle(Iso14443_4bPoller* instance) { + iso14443_3b_copy( + instance->data->iso14443_3b_data, + iso14443_3b_poller_get_data(instance->iso14443_3b_poller)); + + iso14443_4_layer_reset(instance->iso14443_4_layer); + instance->poller_state = Iso14443_4bPollerStateReady; + return NfcCommandContinue; +} + +static NfcCommand iso14443_4b_poller_handler_error(Iso14443_4bPoller* instance) { + iso14443_3b_poller_halt(instance->iso14443_3b_poller); + instance->iso14443_4b_event_data.error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->poller_state = Iso14443_4bPollerStateIdle; + return command; +} + +static NfcCommand iso14443_4b_poller_handler_ready(Iso14443_4bPoller* instance) { + instance->iso14443_4b_event.type = Iso14443_4bPollerEventTypeReady; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Iso14443_4bPollerStateHandler + iso14443_4b_poller_state_handler[Iso14443_4bPollerStateNum] = { + [Iso14443_4bPollerStateIdle] = iso14443_4b_poller_handler_idle, + [Iso14443_4bPollerStateError] = iso14443_4b_poller_handler_error, + [Iso14443_4bPollerStateReady] = iso14443_4b_poller_handler_ready, +}; + +static void iso14443_4b_poller_set_callback( + Iso14443_4bPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_4b_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3b); + + Iso14443_4bPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data; + furi_assert(iso14443_3b_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) { + command = iso14443_4b_poller_state_handler[instance->poller_state](instance); + } else if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeError) { + instance->iso14443_4b_event.type = Iso14443_4bPollerEventTypeError; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool iso14443_4b_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3b); + + const Iso14443_4bPoller* instance = context; + furi_assert(instance); + + const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data; + furi_assert(iso14443_3b_event); + iso14443_3b_copy( + instance->data->iso14443_3b_data, + iso14443_3b_poller_get_data(instance->iso14443_3b_poller)); + + bool protocol_detected = false; + + if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) { + protocol_detected = iso14443_3b_supports_iso14443_4(instance->data->iso14443_3b_data); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_4b = { + .alloc = (NfcPollerAlloc)iso14443_4b_poller_alloc, + .free = (NfcPollerFree)iso14443_4b_poller_free, + .set_callback = (NfcPollerSetCallback)iso14443_4b_poller_set_callback, + .run = (NfcPollerRun)iso14443_4b_poller_run, + .detect = (NfcPollerDetect)iso14443_4b_poller_detect, + .get_data = (NfcPollerGetData)iso14443_4b_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h new file mode 100644 index 0000000000..e60090c04e --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "iso14443_4b.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4bPoller Iso14443_4bPoller; + +typedef enum { + Iso14443_4bPollerEventTypeError, + Iso14443_4bPollerEventTypeReady, +} Iso14443_4bPollerEventType; + +typedef struct { + Iso14443_4bError error; +} Iso14443_4bPollerEventData; + +typedef struct { + Iso14443_4bPollerEventType type; + Iso14443_4bPollerEventData* data; +} Iso14443_4bPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h new file mode 100644 index 0000000000..4388f2cf8f --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_4b; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c new file mode 100644 index 0000000000..82de1d538a --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c @@ -0,0 +1,45 @@ +#include "iso14443_4b_poller_i.h" + +#include + +#include "iso14443_4b_i.h" + +#define TAG "Iso14443_4bPoller" + +Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance) { + furi_assert(instance); + + iso14443_3b_poller_halt(instance->iso14443_3b_poller); + instance->poller_state = Iso14443_4bPollerStateIdle; + + return Iso14443_4bErrorNone; +} + +Iso14443_4bError iso14443_4b_poller_send_block( + Iso14443_4bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + + Iso14443_4bError error = Iso14443_4bErrorNone; + + do { + Iso14443_3bError iso14443_3b_error = iso14443_3b_poller_send_frame( + instance->iso14443_3b_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_3b_error != Iso14443_3bErrorNone) { + error = iso14443_4b_process_error(iso14443_3b_error); + break; + + } else if(!iso14443_4_layer_decode_block( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { + error = Iso14443_4bErrorProtocol; + break; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h new file mode 100644 index 0000000000..4df00adcf1 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "iso14443_4b_poller.h" +#include "iso14443_4b_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4bPollerStateIdle, + Iso14443_4bPollerStateError, + Iso14443_4bPollerStateReady, + + Iso14443_4bPollerStateNum, +} Iso14443_4bPollerState; + +typedef enum { + Iso14443_4bPollerSessionStateIdle, + Iso14443_4bPollerSessionStateActive, + Iso14443_4bPollerSessionStateStopRequest, +} Iso14443_4bPollerSessionState; + +struct Iso14443_4bPoller { + Iso14443_3bPoller* iso14443_3b_poller; + Iso14443_4bPollerState poller_state; + Iso14443_4bPollerSessionState session_state; + Iso14443_4bError error; + Iso14443_4bData* data; + Iso14443_4Layer* iso14443_4_layer; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Iso14443_4bPollerEventData iso14443_4b_event_data; + Iso14443_4bPollerEvent iso14443_4b_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error); + +const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance); + +Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); + +Iso14443_4bError iso14443_4b_poller_send_block( + Iso14443_4bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.c b/lib/nfc/protocols/iso15693_3/iso15693_3.c new file mode 100644 index 0000000000..3203cbad00 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.c @@ -0,0 +1,358 @@ +#include "iso15693_3.h" +#include "iso15693_3_device_defs.h" + +#include + +#define ISO15693_3_PROTOCOL_NAME "ISO15693-3" +#define ISO15693_3_PROTOCOL_NAME_LEGACY "ISO15693" +#define ISO15693_3_DEVICE_NAME "ISO15693-3 (Unknown)" + +#define ISO15693_3_LOCK_DSFID_LEGACY (1U << 0) +#define ISO15693_3_LOCK_AFI_LEGACY (1U << 1) + +#define ISO15693_3_DSFID_KEY "DSFID" +#define ISO15693_3_AFI_KEY "AFI" +#define ISO15693_3_IC_REF_KEY "IC Reference" +#define ISO15693_3_BLOCK_COUNT_KEY "Block Count" +#define ISO15693_3_BLOCK_SIZE_KEY "Block Size" +#define ISO15693_3_DATA_CONTENT_KEY "Data Content" +#define ISO15693_3_LOCK_DSFID_KEY "Lock DSFID" +#define ISO15693_3_LOCK_AFI_KEY "Lock AFI" +#define ISO15693_3_SECURITY_STATUS_KEY "Security Status" + +const NfcDeviceBase nfc_device_iso15693_3 = { + .protocol_name = ISO15693_3_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso15693_3_alloc, + .free = (NfcDeviceFree)iso15693_3_free, + .reset = (NfcDeviceReset)iso15693_3_reset, + .copy = (NfcDeviceCopy)iso15693_3_copy, + .verify = (NfcDeviceVerify)iso15693_3_verify, + .load = (NfcDeviceLoad)iso15693_3_load, + .save = (NfcDeviceSave)iso15693_3_save, + .is_equal = (NfcDeviceEqual)iso15693_3_is_equal, + .get_name = (NfcDeviceGetName)iso15693_3_get_device_name, + .get_uid = (NfcDeviceGetUid)iso15693_3_get_uid, + .set_uid = (NfcDeviceSetUid)iso15693_3_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso15693_3_get_base_data, +}; + +Iso15693_3Data* iso15693_3_alloc() { + Iso15693_3Data* data = malloc(sizeof(Iso15693_3Data)); + + data->block_data = simple_array_alloc(&simple_array_config_uint8_t); + data->block_security = simple_array_alloc(&simple_array_config_uint8_t); + + return data; +} + +void iso15693_3_free(Iso15693_3Data* data) { + furi_assert(data); + + simple_array_free(data->block_data); + simple_array_free(data->block_security); + free(data); +} + +void iso15693_3_reset(Iso15693_3Data* data) { + furi_assert(data); + + memset(data->uid, 0, ISO15693_3_UID_SIZE); + memset(&data->system_info, 0, sizeof(Iso15693_3SystemInfo)); + memset(&data->settings, 0, sizeof(Iso15693_3Settings)); + + simple_array_reset(data->block_data); + simple_array_reset(data->block_security); +} + +void iso15693_3_copy(Iso15693_3Data* data, const Iso15693_3Data* other) { + furi_assert(data); + furi_assert(other); + + memcpy(data->uid, other->uid, ISO15693_3_UID_SIZE); + + data->system_info = other->system_info; + data->settings = other->settings; + + simple_array_copy(data->block_data, other->block_data); + simple_array_copy(data->block_security, other->block_security); +} + +bool iso15693_3_verify(Iso15693_3Data* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal(device_type, ISO15693_3_PROTOCOL_NAME_LEGACY); +} + +static inline bool iso15693_3_load_security_legacy(Iso15693_3Data* data, FlipperFormat* ff) { + bool loaded = false; + uint8_t* legacy_data = NULL; + + do { + uint32_t value_count; + if(!flipper_format_get_value_count(ff, ISO15693_3_SECURITY_STATUS_KEY, &value_count)) + break; + if(simple_array_get_count(data->block_security) + 1 != value_count) break; + + legacy_data = malloc(value_count); + if(!flipper_format_read_hex(ff, ISO15693_3_SECURITY_STATUS_KEY, legacy_data, value_count)) + break; + + // First legacy data byte is lock bits + data->settings.lock_bits.dsfid = legacy_data[0] & ISO15693_3_LOCK_DSFID_LEGACY; + data->settings.lock_bits.afi = legacy_data[0] & ISO15693_3_LOCK_AFI_LEGACY; + + // The rest are block security + memcpy( + &legacy_data[1], + simple_array_get_data(data->block_security), + simple_array_get_count(data->block_security)); + + loaded = true; + } while(false); + + if(legacy_data) free(legacy_data); + + return loaded; +} + +static inline bool iso15693_3_load_security(Iso15693_3Data* data, FlipperFormat* ff) { + bool loaded = false; + + do { + uint32_t value_count; + if(!flipper_format_get_value_count(ff, ISO15693_3_SECURITY_STATUS_KEY, &value_count)) + break; + if(simple_array_get_count(data->block_security) != value_count) break; + if(!flipper_format_read_hex( + ff, + ISO15693_3_SECURITY_STATUS_KEY, + simple_array_get_data(data->block_security), + simple_array_get_count(data->block_security))) + break; + + loaded = true; + } while(false); + + return loaded; +} + +bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + UNUSED(version); + + bool loaded = false; + + do { + if(flipper_format_key_exist(ff, ISO15693_3_DSFID_KEY)) { + if(!flipper_format_read_hex(ff, ISO15693_3_DSFID_KEY, &data->system_info.dsfid, 1)) + break; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_DSFID; + } + + if(flipper_format_key_exist(ff, ISO15693_3_AFI_KEY)) { + if(!flipper_format_read_hex(ff, ISO15693_3_AFI_KEY, &data->system_info.afi, 1)) break; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_AFI; + } + + if(flipper_format_key_exist(ff, ISO15693_3_IC_REF_KEY)) { + if(!flipper_format_read_hex(ff, ISO15693_3_IC_REF_KEY, &data->system_info.ic_ref, 1)) + break; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_IC_REF; + } + + const bool has_lock_bits = flipper_format_key_exist(ff, ISO15693_3_LOCK_DSFID_KEY) && + flipper_format_key_exist(ff, ISO15693_3_LOCK_AFI_KEY); + if(has_lock_bits) { + Iso15693_3LockBits* lock_bits = &data->settings.lock_bits; + if(!flipper_format_read_bool(ff, ISO15693_3_LOCK_DSFID_KEY, &lock_bits->dsfid, 1)) + break; + if(!flipper_format_read_bool(ff, ISO15693_3_LOCK_AFI_KEY, &lock_bits->afi, 1)) break; + } + + if(flipper_format_key_exist(ff, ISO15693_3_BLOCK_COUNT_KEY) && + flipper_format_key_exist(ff, ISO15693_3_BLOCK_SIZE_KEY)) { + uint32_t block_count; + if(!flipper_format_read_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) break; + + data->system_info.block_count = block_count; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY; + + if(!flipper_format_read_hex( + ff, ISO15693_3_BLOCK_SIZE_KEY, &(data->system_info.block_size), 1)) + break; + + simple_array_init( + data->block_data, data->system_info.block_size * data->system_info.block_count); + + if(!flipper_format_read_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_get_data(data->block_data), + simple_array_get_count(data->block_data))) + break; + + if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) { + simple_array_init(data->block_security, data->system_info.block_count); + + const bool security_loaded = has_lock_bits ? + iso15693_3_load_security(data, ff) : + iso15693_3_load_security_legacy(data, ff); + if(!security_loaded) break; + } + } + + loaded = true; + } while(false); + + return loaded; +} + +bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, ISO15693_3_PROTOCOL_NAME " specific data")) + break; + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + if(!flipper_format_write_comment_cstr(ff, "Data Storage Format Identifier")) break; + if(!flipper_format_write_hex(ff, ISO15693_3_DSFID_KEY, &data->system_info.dsfid, 1)) + break; + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + if(!flipper_format_write_comment_cstr(ff, "Application Family Identifier")) break; + if(!flipper_format_write_hex(ff, ISO15693_3_AFI_KEY, &data->system_info.afi, 1)) break; + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + if(!flipper_format_write_comment_cstr(ff, "IC Reference - Vendor specific meaning")) + break; + if(!flipper_format_write_hex(ff, ISO15693_3_IC_REF_KEY, &data->system_info.ic_ref, 1)) + break; + } + + if(!flipper_format_write_comment_cstr(ff, "Lock Bits")) break; + if(!flipper_format_write_bool( + ff, ISO15693_3_LOCK_DSFID_KEY, &data->settings.lock_bits.dsfid, 1)) + break; + if(!flipper_format_write_bool( + ff, ISO15693_3_LOCK_AFI_KEY, &data->settings.lock_bits.afi, 1)) + break; + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + const uint32_t block_count = data->system_info.block_count; + if(!flipper_format_write_comment_cstr( + ff, "Number of memory blocks, valid range = 1..256")) + break; + if(!flipper_format_write_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) + break; + + if(!flipper_format_write_comment_cstr( + ff, "Size of a single memory block, valid range = 01...20 (hex)")) + break; + if(!flipper_format_write_hex( + ff, ISO15693_3_BLOCK_SIZE_KEY, &data->system_info.block_size, 1)) + break; + + if(!flipper_format_write_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_cget_data(data->block_data), + simple_array_get_count(data->block_data))) + break; + + if(!flipper_format_write_comment_cstr( + ff, "Block Security Status: 01 = locked, 00 = not locked")) + break; + if(!flipper_format_write_hex( + ff, + ISO15693_3_SECURITY_STATUS_KEY, + simple_array_cget_data(data->block_security), + simple_array_get_count(data->block_security))) + break; + } + saved = true; + } while(false); + + return saved; +} + +bool iso15693_3_is_equal(const Iso15693_3Data* data, const Iso15693_3Data* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data->uid, other->uid, ISO15693_3_UID_SIZE) == 0 && + memcmp(&data->settings, &other->settings, sizeof(Iso15693_3Settings)) == 0 && + memcmp(&data->system_info, &other->system_info, sizeof(Iso15693_3SystemInfo)) == 0 && + simple_array_is_equal(data->block_data, other->block_data) && + simple_array_is_equal(data->block_security, other->block_security); +} + +const char* iso15693_3_get_device_name(const Iso15693_3Data* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + + return ISO15693_3_DEVICE_NAME; +} + +const uint8_t* iso15693_3_get_uid(const Iso15693_3Data* data, size_t* uid_len) { + furi_assert(data); + + if(uid_len) *uid_len = ISO15693_3_UID_SIZE; + return data->uid; +} + +bool iso15693_3_set_uid(Iso15693_3Data* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + furi_assert(uid); + + bool uid_valid = uid_len == ISO15693_3_UID_SIZE; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + // All ISO15693-3 cards must have this as first UID byte + data->uid[0] = 0xe0; + } + + return uid_valid; +} + +Iso15693_3Data* iso15693_3_get_base_data(const Iso15693_3Data* data) { + UNUSED(data); + furi_crash("No base data"); +} + +bool iso15693_3_is_block_locked(const Iso15693_3Data* data, uint8_t block_index) { + furi_assert(data); + furi_assert(block_index < data->system_info.block_count); + + return *(const uint8_t*)simple_array_cget(data->block_security, block_index); +} + +uint8_t iso15693_3_get_manufacturer_id(const Iso15693_3Data* data) { + furi_assert(data); + + return data->uid[1]; +} + +uint16_t iso15693_3_get_block_count(const Iso15693_3Data* data) { + furi_assert(data); + + return data->system_info.block_count; +} + +uint8_t iso15693_3_get_block_size(const Iso15693_3Data* data) { + furi_assert(data); + + return data->system_info.block_size; +} + +const uint8_t* iso15693_3_get_block_data(const Iso15693_3Data* data, uint8_t block_index) { + furi_assert(data); + furi_assert(data->system_info.block_count > block_index); + + return (const uint8_t*)simple_array_cget( + data->block_data, block_index * data->system_info.block_size); +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.h b/lib/nfc/protocols/iso15693_3/iso15693_3.h new file mode 100644 index 0000000000..5d4158ab9e --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO15693_3_UID_SIZE (8U) + +#define ISO15693_3_GUARD_TIME_US (5000U) +#define ISO15693_3_FDT_POLL_FC (4202U) +#define ISO15693_3_FDT_LISTEN_FC (4320U) +#define ISO15693_3_POLL_POLL_MIN_US (1500U) + +#define ISO15693_3_REQ_FLAG_SUBCARRIER_1 (0U << 0) +#define ISO15693_3_REQ_FLAG_SUBCARRIER_2 (1U << 0) +#define ISO15693_3_REQ_FLAG_DATA_RATE_LO (0U << 1) +#define ISO15693_3_REQ_FLAG_DATA_RATE_HI (1U << 1) +#define ISO15693_3_REQ_FLAG_INVENTORY_T4 (0U << 2) +#define ISO15693_3_REQ_FLAG_INVENTORY_T5 (1U << 2) +#define ISO15693_3_REQ_FLAG_EXTENSION (1U << 3) + +#define ISO15693_3_REQ_FLAG_T4_SELECTED (1U << 4) +#define ISO15693_3_REQ_FLAG_T4_ADDRESSED (1U << 5) +#define ISO15693_3_REQ_FLAG_T4_OPTION (1U << 6) + +#define ISO15693_3_REQ_FLAG_T5_AFI_PRESENT (1U << 4) +#define ISO15693_3_REQ_FLAG_T5_N_SLOTS_16 (0U << 5) +#define ISO15693_3_REQ_FLAG_T5_N_SLOTS_1 (1U << 5) +#define ISO15693_3_REQ_FLAG_T5_OPTION (1U << 6) + +#define ISO15693_3_RESP_FLAG_NONE (0U) +#define ISO15693_3_RESP_FLAG_ERROR (1U << 0) +#define ISO15693_3_RESP_FLAG_EXTENSION (1U << 3) + +#define ISO15693_3_RESP_ERROR_NOT_SUPPORTED (0x01U) +#define ISO15693_3_RESP_ERROR_FORMAT (0x02U) +#define ISO15693_3_RESP_ERROR_OPTION (0x03U) +#define ISO15693_3_RESP_ERROR_UNKNOWN (0x0FU) +#define ISO15693_3_RESP_ERROR_BLOCK_UNAVAILABLE (0x10U) +#define ISO15693_3_RESP_ERROR_BLOCK_ALREADY_LOCKED (0x11U) +#define ISO15693_3_RESP_ERROR_BLOCK_LOCKED (0x12U) +#define ISO15693_3_RESP_ERROR_BLOCK_WRITE (0x13U) +#define ISO15693_3_RESP_ERROR_BLOCK_LOCK (0x14U) +#define ISO15693_3_RESP_ERROR_CUSTOM_START (0xA0U) +#define ISO15693_3_RESP_ERROR_CUSTOM_END (0xDFU) + +#define ISO15693_3_CMD_MANDATORY_START (0x01U) +#define ISO15693_3_CMD_INVENTORY (0x01U) +#define ISO15693_3_CMD_STAY_QUIET (0x02U) +#define ISO15693_3_CMD_MANDATORY_RFU (0x03U) +#define ISO15693_3_CMD_OPTIONAL_START (0x20U) +#define ISO15693_3_CMD_READ_BLOCK (0x20U) +#define ISO15693_3_CMD_WRITE_BLOCK (0x21U) +#define ISO15693_3_CMD_LOCK_BLOCK (0x22U) +#define ISO15693_3_CMD_READ_MULTI_BLOCKS (0x23U) +#define ISO15693_3_CMD_WRITE_MULTI_BLOCKS (0x24U) +#define ISO15693_3_CMD_SELECT (0x25U) +#define ISO15693_3_CMD_RESET_TO_READY (0x26U) +#define ISO15693_3_CMD_WRITE_AFI (0x27U) +#define ISO15693_3_CMD_LOCK_AFI (0x28U) +#define ISO15693_3_CMD_WRITE_DSFID (0x29U) +#define ISO15693_3_CMD_LOCK_DSFID (0x2AU) +#define ISO15693_3_CMD_GET_SYS_INFO (0x2BU) +#define ISO15693_3_CMD_GET_BLOCKS_SECURITY (0x2CU) +#define ISO15693_3_CMD_OPTIONAL_RFU (0x2DU) +#define ISO15693_3_CMD_CUSTOM_START (0xA0U) + +#define ISO15693_3_MANDATORY_COUNT (ISO15693_3_CMD_MANDATORY_RFU - ISO15693_3_CMD_MANDATORY_START) +#define ISO15693_3_OPTIONAL_COUNT (ISO15693_3_CMD_OPTIONAL_RFU - ISO15693_3_CMD_OPTIONAL_START) + +#define ISO15693_3_SYSINFO_FLAG_DSFID (1U << 0) +#define ISO15693_3_SYSINFO_FLAG_AFI (1U << 1) +#define ISO15693_3_SYSINFO_FLAG_MEMORY (1U << 2) +#define ISO15693_3_SYSINFO_FLAG_IC_REF (1U << 3) + +typedef enum { + Iso15693_3ErrorNone, + Iso15693_3ErrorNotPresent, + Iso15693_3ErrorBufferEmpty, + Iso15693_3ErrorBufferOverflow, + Iso15693_3ErrorFieldOff, + Iso15693_3ErrorWrongCrc, + Iso15693_3ErrorTimeout, + Iso15693_3ErrorFormat, + Iso15693_3ErrorIgnore, + Iso15693_3ErrorNotSupported, + Iso15693_3ErrorUidMismatch, + Iso15693_3ErrorFullyHandled, + Iso15693_3ErrorUnexpectedResponse, + Iso15693_3ErrorInternal, + Iso15693_3ErrorCustom, + Iso15693_3ErrorUnknown, +} Iso15693_3Error; + +typedef struct { + uint8_t flags; + uint8_t dsfid; + uint8_t afi; + uint8_t ic_ref; + uint16_t block_count; + uint8_t block_size; +} Iso15693_3SystemInfo; + +typedef struct { + bool dsfid; + bool afi; +} Iso15693_3LockBits; + +typedef struct { + Iso15693_3LockBits lock_bits; +} Iso15693_3Settings; + +typedef struct { + uint8_t uid[ISO15693_3_UID_SIZE]; + Iso15693_3SystemInfo system_info; + Iso15693_3Settings settings; + SimpleArray* block_data; + SimpleArray* block_security; +} Iso15693_3Data; + +Iso15693_3Data* iso15693_3_alloc(); + +void iso15693_3_free(Iso15693_3Data* data); + +void iso15693_3_reset(Iso15693_3Data* data); + +void iso15693_3_copy(Iso15693_3Data* data, const Iso15693_3Data* other); + +bool iso15693_3_verify(Iso15693_3Data* data, const FuriString* device_type); + +bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version); + +bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff); + +bool iso15693_3_is_equal(const Iso15693_3Data* data, const Iso15693_3Data* other); + +const char* iso15693_3_get_device_name(const Iso15693_3Data* data, NfcDeviceNameType name_type); + +const uint8_t* iso15693_3_get_uid(const Iso15693_3Data* data, size_t* uid_len); + +bool iso15693_3_set_uid(Iso15693_3Data* data, const uint8_t* uid, size_t uid_len); + +Iso15693_3Data* iso15693_3_get_base_data(const Iso15693_3Data* data); + +// Getters and tests + +bool iso15693_3_is_block_locked(const Iso15693_3Data* data, uint8_t block_index); + +uint8_t iso15693_3_get_manufacturer_id(const Iso15693_3Data* data); + +uint16_t iso15693_3_get_block_count(const Iso15693_3Data* data); + +uint8_t iso15693_3_get_block_size(const Iso15693_3Data* data); + +const uint8_t* iso15693_3_get_block_data(const Iso15693_3Data* data, uint8_t block_index); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h b/lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h new file mode 100644 index 0000000000..69a73476e6 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso15693_3; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c new file mode 100644 index 0000000000..3d8d95c3a1 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c @@ -0,0 +1,263 @@ +#include "iso15693_3_i.h" + +bool iso15693_3_error_response_parse(Iso15693_3Error* error, const BitBuffer* buf) { + furi_assert(error); + + if(bit_buffer_get_size_bytes(buf) == 0) { + // YEET! + *error = Iso15693_3ErrorBufferEmpty; + return true; + } + + typedef struct { + uint8_t flags; + uint8_t error; + } ErrorResponseLayout; + + const ErrorResponseLayout* resp = (const ErrorResponseLayout*)bit_buffer_get_data(buf); + + if((resp->flags & ISO15693_3_RESP_FLAG_ERROR) == 0) { + // No error flag is set, the data does not contain an error frame + return false; + } else if(bit_buffer_get_size_bytes(buf) < sizeof(ErrorResponseLayout)) { + // Error bit is set, but not enough data to determine the error + *error = Iso15693_3ErrorUnexpectedResponse; + return true; + } else if( + resp->error >= ISO15693_3_RESP_ERROR_CUSTOM_START && + resp->error <= ISO15693_3_RESP_ERROR_CUSTOM_END) { + // Custom vendor-specific error, must be checked in the respective protocol implementation + *error = Iso15693_3ErrorCustom; + return true; + } + + switch(resp->error) { + case ISO15693_3_RESP_ERROR_NOT_SUPPORTED: + case ISO15693_3_RESP_ERROR_OPTION: + *error = Iso15693_3ErrorNotSupported; + break; + case ISO15693_3_RESP_ERROR_FORMAT: + *error = Iso15693_3ErrorFormat; + break; + case ISO15693_3_RESP_ERROR_BLOCK_UNAVAILABLE: + case ISO15693_3_RESP_ERROR_BLOCK_ALREADY_LOCKED: + case ISO15693_3_RESP_ERROR_BLOCK_LOCKED: + case ISO15693_3_RESP_ERROR_BLOCK_WRITE: + case ISO15693_3_RESP_ERROR_BLOCK_LOCK: + *error = Iso15693_3ErrorInternal; + break; + case ISO15693_3_RESP_ERROR_UNKNOWN: + default: + *error = Iso15693_3ErrorUnknown; + } + + return true; +} + +Iso15693_3Error iso15693_3_inventory_response_parse(uint8_t* data, const BitBuffer* buf) { + furi_assert(data); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t dsfid; + uint8_t uid[ISO15693_3_UID_SIZE]; + } InventoryResponseLayout; + + if(bit_buffer_get_size_bytes(buf) != sizeof(InventoryResponseLayout)) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const InventoryResponseLayout* resp = + (const InventoryResponseLayout*)bit_buffer_get_data(buf); + // Reverse UID for backward compatibility + for(uint32_t i = 0; i < ISO15693_3_UID_SIZE; ++i) { + data[i] = resp->uid[ISO15693_3_UID_SIZE - i - 1]; + } + + } while(false); + + return ret; +} + +Iso15693_3Error + iso15693_3_system_info_response_parse(Iso15693_3SystemInfo* data, const BitBuffer* buf) { + furi_assert(data); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t info_flags; + uint8_t uid[ISO15693_3_UID_SIZE]; + uint8_t extra[]; + } SystemInfoResponseLayout; + + if(bit_buffer_get_size_bytes(buf) < sizeof(SystemInfoResponseLayout)) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const SystemInfoResponseLayout* resp = + (const SystemInfoResponseLayout*)bit_buffer_get_data(buf); + + const uint8_t* extra = resp->extra; + const size_t extra_size = (resp->info_flags & ISO15693_3_SYSINFO_FLAG_DSFID ? 1 : 0) + + (resp->info_flags & ISO15693_3_SYSINFO_FLAG_AFI ? 1 : 0) + + (resp->info_flags & ISO15693_3_SYSINFO_FLAG_MEMORY ? 2 : 0) + + (resp->info_flags & ISO15693_3_SYSINFO_FLAG_IC_REF ? 1 : 0); + + if(extra_size != bit_buffer_get_size_bytes(buf) - sizeof(SystemInfoResponseLayout)) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + data->flags = resp->info_flags; + + if(data->flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + data->dsfid = *extra++; + } + + if(data->flags & ISO15693_3_SYSINFO_FLAG_AFI) { + data->afi = *extra++; + } + + if(data->flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + // Add 1 to get actual values + data->block_count = *extra++ + 1; + data->block_size = (*extra++ & 0x1F) + 1; + } + + if(data->flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + data->ic_ref = *extra; + } + + } while(false); + + return ret; +} + +Iso15693_3Error + iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf) { + furi_assert(data); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t block_data[]; + } ReadBlockResponseLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(buf); + const size_t received_block_size = buf_size - sizeof(ReadBlockResponseLayout); + + if(buf_size <= sizeof(ReadBlockResponseLayout) || received_block_size != block_size) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const ReadBlockResponseLayout* resp = + (const ReadBlockResponseLayout*)bit_buffer_get_data(buf); + memcpy(data, resp->block_data, received_block_size); + + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_get_block_security_response_parse( + uint8_t* data, + uint16_t block_count, + const BitBuffer* buf) { + furi_assert(data); + furi_assert(block_count); + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t block_security[]; + } GetBlockSecurityResponseLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(buf); + const size_t received_block_count = buf_size - sizeof(GetBlockSecurityResponseLayout); + + if(buf_size <= sizeof(GetBlockSecurityResponseLayout) || + received_block_count != block_count) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const GetBlockSecurityResponseLayout* resp = + (const GetBlockSecurityResponseLayout*)bit_buffer_get_data(buf); + + memcpy(data, resp->block_security, received_block_count); + + } while(false); + + return ret; +} + +void iso15693_3_append_uid(const Iso15693_3Data* data, BitBuffer* buf) { + for(size_t i = 0; i < ISO15693_3_UID_SIZE; ++i) { + // Reverse the UID + bit_buffer_append_byte(buf, data->uid[ISO15693_3_UID_SIZE - i - 1]); + } +} + +void iso15693_3_append_block(const Iso15693_3Data* data, uint8_t block_num, BitBuffer* buf) { + furi_assert(block_num < data->system_info.block_count); + + const uint32_t block_offset = block_num * data->system_info.block_size; + const uint8_t* block_data = simple_array_cget(data->block_data, block_offset); + + bit_buffer_append_bytes(buf, block_data, data->system_info.block_size); +} + +void iso15693_3_set_block_locked(Iso15693_3Data* data, uint8_t block_index, bool locked) { + furi_assert(data); + furi_assert(block_index < data->system_info.block_count); + + *(uint8_t*)simple_array_get(data->block_security, block_index) = locked ? 1 : 0; +} + +void iso15693_3_set_block_data( + Iso15693_3Data* data, + uint8_t block_num, + const uint8_t* block_data, + size_t block_data_size) { + furi_assert(block_num < data->system_info.block_count); + furi_assert(block_data_size == data->system_info.block_size); + + const uint32_t block_offset = block_num * data->system_info.block_size; + uint8_t* block = simple_array_get(data->block_data, block_offset); + + memcpy(block, block_data, block_data_size); +} + +void iso15693_3_append_block_security( + const Iso15693_3Data* data, + uint8_t block_num, + BitBuffer* buf) { + bit_buffer_append_byte(buf, *(uint8_t*)simple_array_cget(data->block_security, block_num)); +} + +bool iso15693_3_is_equal_uid(const Iso15693_3Data* data, const uint8_t* uid) { + for(size_t i = 0; i < ISO15693_3_UID_SIZE; ++i) { + if(data->uid[i] != uid[ISO15693_3_UID_SIZE - i - 1]) return false; + } + return true; +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h new file mode 100644 index 0000000000..253bda7f59 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h @@ -0,0 +1,58 @@ +#pragma once + +#include "iso15693_3.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Check if the buffer contains an error frame and if it does, determine + * the error type. + * NOTE: No changes are done to the result if no error is present. + * + * @param [out] data Pointer to the resulting error value. + * @param [in] buf Data buffer to be checked + * + * @return True if data contains an error frame or is empty, false otherwise + */ +bool iso15693_3_error_response_parse(Iso15693_3Error* error, const BitBuffer* buf); + +Iso15693_3Error iso15693_3_inventory_response_parse(uint8_t* data, const BitBuffer* buf); + +Iso15693_3Error + iso15693_3_system_info_response_parse(Iso15693_3SystemInfo* data, const BitBuffer* buf); + +Iso15693_3Error + iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf); + +Iso15693_3Error iso15693_3_get_block_security_response_parse( + uint8_t* data, + uint16_t block_count, + const BitBuffer* buf); + +void iso15693_3_append_uid(const Iso15693_3Data* data, BitBuffer* buf); + +void iso15693_3_append_block(const Iso15693_3Data* data, uint8_t block_num, BitBuffer* buf); + +void iso15693_3_set_block_locked(Iso15693_3Data* data, uint8_t block_index, bool locked); + +void iso15693_3_set_block_data( + Iso15693_3Data* data, + uint8_t block_num, + const uint8_t* block_data, + size_t block_data_size); + +void iso15693_3_append_block_security( + const Iso15693_3Data* data, + uint8_t block_num, + BitBuffer* buf); + +// NOTE: the uid parameter has reversed byte order with respect to data +bool iso15693_3_is_equal_uid(const Iso15693_3Data* data, const uint8_t* uid); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c new file mode 100644 index 0000000000..84e7508585 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c @@ -0,0 +1,110 @@ +#include "iso15693_3_listener_i.h" + +#include + +#include +#include +#include + +#define TAG "Iso15693_3Listener" + +#define ISO15693_3_LISTENER_BUFFER_SIZE (64U) + +Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) { + furi_assert(nfc); + + Iso15693_3Listener* instance = malloc(sizeof(Iso15693_3Listener)); + instance->nfc = nfc; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(ISO15693_3_LISTENER_BUFFER_SIZE); + + instance->iso15693_3_event.data = &instance->iso15693_3_event_data; + instance->generic_event.protocol = NfcProtocolIso15693_3; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->iso15693_3_event; + + nfc_set_fdt_listen_fc(instance->nfc, ISO15693_3_FDT_LISTEN_FC); + nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693); + + return instance; +} + +void iso15693_3_listener_free(Iso15693_3Listener* instance) { + furi_assert(instance); + + bit_buffer_free(instance->tx_buffer); + + free(instance); +} + +void iso15693_3_listener_set_callback( + Iso15693_3Listener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +const Iso15693_3Data* iso15693_3_listener_get_data(Iso15693_3Listener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +NfcCommand iso15693_3_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso15693_3Listener* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypeRxEnd) { + BitBuffer* rx_buffer = nfc_event->data.buffer; + + if(iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) { + iso13239_crc_trim(rx_buffer); + + const Iso15693_3Error error = iso15693_3_listener_process_request(instance, rx_buffer); + + if(error == Iso15693_3ErrorNotSupported) { + if(instance->callback) { + instance->iso15693_3_event.type = Iso15693_3ListenerEventTypeCustomCommand; + instance->iso15693_3_event.data->buffer = rx_buffer; + command = instance->callback(instance->generic_event, instance->context); + } + + } else if(error == Iso15693_3ErrorUidMismatch) { + iso15693_3_listener_process_uid_mismatch(instance, rx_buffer); + } + + } else if(bit_buffer_get_size(rx_buffer) == 0) { + // Special case: Single EOF + const Iso15693_3Error error = iso15693_3_listener_process_single_eof(instance); + if(error == Iso15693_3ErrorUnexpectedResponse) { + if(instance->callback) { + instance->iso15693_3_event.type = Iso15693_3ListenerEventTypeSingleEof; + command = instance->callback(instance->generic_event, instance->context); + } + } + } else { + FURI_LOG_D( + TAG, "Wrong CRC, buffer size: %zu", bit_buffer_get_size(nfc_event->data.buffer)); + } + } + + return command; +} + +const NfcListenerBase nfc_listener_iso15693_3 = { + .alloc = (NfcListenerAlloc)iso15693_3_listener_alloc, + .free = (NfcListenerFree)iso15693_3_listener_free, + .set_callback = (NfcListenerSetCallback)iso15693_3_listener_set_callback, + .get_data = (NfcListenerGetData)iso15693_3_listener_get_data, + .run = (NfcListenerRun)iso15693_3_listener_run, +}; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.h b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.h new file mode 100644 index 0000000000..c69d18db4e --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "iso15693_3.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693_3Listener Iso15693_3Listener; + +typedef enum { + Iso15693_3ListenerEventTypeFieldOff, + Iso15693_3ListenerEventTypeCustomCommand, + Iso15693_3ListenerEventTypeSingleEof, +} Iso15693_3ListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Iso15693_3ListenerEventData; + +typedef struct { + Iso15693_3ListenerEventType type; + Iso15693_3ListenerEventData* data; +} Iso15693_3ListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h new file mode 100644 index 0000000000..93497512eb --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_iso15693_3; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c new file mode 100644 index 0000000000..a8dec7ae33 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c @@ -0,0 +1,883 @@ +#include "iso15693_3_listener_i.h" + +#include + +#define TAG "Iso15693_3Listener" + +typedef Iso15693_3Error (*Iso15693_3RequestHandler)( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags); + +typedef struct { + Iso15693_3RequestHandler mandatory[ISO15693_3_MANDATORY_COUNT]; + Iso15693_3RequestHandler optional[ISO15693_3_OPTIONAL_COUNT]; +} Iso15693_3ListenerHandlerTable; + +static Iso15693_3Error + iso15693_3_listener_extension_handler(Iso15693_3Listener* instance, uint32_t command, ...) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + if(instance->extension_table == NULL) break; + + Iso15693_3ExtensionHandler handler = NULL; + + if(command < ISO15693_3_CMD_MANDATORY_RFU) { + const Iso15693_3ExtensionHandler* mandatory = instance->extension_table->mandatory; + handler = mandatory[command - ISO15693_3_CMD_MANDATORY_START]; + } else if(command >= ISO15693_3_CMD_OPTIONAL_START && command < ISO15693_3_CMD_OPTIONAL_RFU) { + const Iso15693_3ExtensionHandler* optional = instance->extension_table->optional; + handler = optional[command - ISO15693_3_CMD_OPTIONAL_START]; + } + + if(handler == NULL) break; + + va_list args; + va_start(args, command); + + error = handler(instance->extension_context, args); + + va_end(args); + + } while(false); + return error; +} + +static Iso15693_3Error iso15693_3_listener_inventory_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const bool afi_flag = flags & ISO15693_3_REQ_FLAG_T5_AFI_PRESENT; + const size_t data_size_min = sizeof(uint8_t) * (afi_flag ? 2 : 1); + + if(data_size < data_size_min) { + error = Iso15693_3ErrorFormat; + break; + } + + if(afi_flag) { + const uint8_t afi = *data++; + // When AFI flag is set, ignore non-matching requests + if(afi != instance->data->system_info.afi) break; + } + + const uint8_t mask_len = *data++; + const size_t data_size_required = data_size_min + mask_len; + + if(data_size != data_size_required) { + error = Iso15693_3ErrorFormat; + break; + } + + if(mask_len != 0) { + // TODO FL-3633: Take mask_len and mask_value into account (if present) + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_INVENTORY); + if(error != Iso15693_3ErrorNone) break; + + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.dsfid); // DSFID + iso15693_3_append_uid(instance->data, instance->tx_buffer); // UID + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_stay_quiet_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + instance->state = Iso15693_3ListenerStateQuiet; + return Iso15693_3ErrorIgnore; +} + +static Iso15693_3Error iso15693_3_listener_read_block_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t block_num; + } Iso15693_3ReadBlockRequestLayout; + + const Iso15693_3ReadBlockRequestLayout* request = + (const Iso15693_3ReadBlockRequestLayout*)data; + + if(data_size != sizeof(Iso15693_3ReadBlockRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index = request->block_num; + const uint32_t block_count_max = instance->data->system_info.block_count; + + if(block_index >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_READ_BLOCK, block_index); + if(error != Iso15693_3ErrorNone) break; + + if(flags & ISO15693_3_REQ_FLAG_T4_OPTION) { + iso15693_3_append_block_security( + instance->data, block_index, instance->tx_buffer); // Block security (optional) + } + + iso15693_3_append_block(instance->data, block_index, instance->tx_buffer); // Block data + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_write_block_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t block_num; + uint8_t block_data[]; + } Iso15693_3WriteBlockRequestLayout; + + const Iso15693_3WriteBlockRequestLayout* request = + (const Iso15693_3WriteBlockRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteBlockRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index = request->block_num; + const uint32_t block_count_max = instance->data->system_info.block_count; + const uint32_t block_size_max = instance->data->system_info.block_size; + const size_t block_size_received = data_size - sizeof(Iso15693_3WriteBlockRequestLayout); + + if(block_index >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } else if(block_size_received != block_size_max) { + error = Iso15693_3ErrorInternal; + break; + } else if(iso15693_3_is_block_locked(instance->data, block_index)) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_WRITE_BLOCK, block_index, request->block_data); + if(error != Iso15693_3ErrorNone) break; + + iso15693_3_set_block_data( + instance->data, block_index, request->block_data, block_size_received); + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_lock_block_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t block_num; + } Iso15693_3LockBlockRequestLayout; + + const Iso15693_3LockBlockRequestLayout* request = + (const Iso15693_3LockBlockRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size != sizeof(Iso15693_3LockBlockRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index = request->block_num; + const uint32_t block_count_max = instance->data->system_info.block_count; + + if(block_index >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } else if(iso15693_3_is_block_locked(instance->data, block_index)) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_LOCK_BLOCK, block_index); + if(error != Iso15693_3ErrorNone) break; + + iso15693_3_set_block_locked(instance->data, block_index, true); + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t first_block_num; + uint8_t block_count; + } Iso15693_3ReadMultiBlocksRequestLayout; + + const Iso15693_3ReadMultiBlocksRequestLayout* request = + (const Iso15693_3ReadMultiBlocksRequestLayout*)data; + + if(data_size != sizeof(Iso15693_3ReadMultiBlocksRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index_start = request->first_block_num; + const uint32_t block_index_end = block_index_start + request->block_count; + + const uint32_t block_count = request->block_count + 1; + const uint32_t block_count_max = instance->data->system_info.block_count; + const uint32_t block_count_available = block_count_max - block_index_start; + + if(block_count > block_count_available) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, + ISO15693_3_CMD_READ_MULTI_BLOCKS, + (uint32_t)block_index_start, + (uint32_t)block_index_end); + if(error != Iso15693_3ErrorNone) break; + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + if(flags & ISO15693_3_REQ_FLAG_T4_OPTION) { + iso15693_3_append_block_security( + instance->data, i, instance->tx_buffer); // Block security (optional) + } + iso15693_3_append_block(instance->data, i, instance->tx_buffer); // Block data + } + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_write_multi_blocks_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t first_block_num; + uint8_t block_count; + uint8_t block_data[]; + } Iso15693_3WriteMultiBlocksRequestLayout; + + const Iso15693_3WriteMultiBlocksRequestLayout* request = + (const Iso15693_3WriteMultiBlocksRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteMultiBlocksRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index_start = request->first_block_num; + const uint32_t block_index_end = block_index_start + request->block_count; + + const uint32_t block_count = request->block_count + 1; + const uint32_t block_count_max = instance->data->system_info.block_count; + const uint32_t block_count_available = block_count_max - block_index_start; + + const size_t block_data_size = data_size - sizeof(Iso15693_3WriteMultiBlocksRequestLayout); + const size_t block_size = block_data_size / block_count; + const size_t block_size_max = instance->data->system_info.block_size; + + if(block_count > block_count_available) { + error = Iso15693_3ErrorInternal; + break; + } else if(block_size != block_size_max) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_WRITE_MULTI_BLOCKS, block_index_start, block_index_end); + if(error != Iso15693_3ErrorNone) break; + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + if(iso15693_3_is_block_locked(instance->data, i)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + if(error != Iso15693_3ErrorNone) break; + + for(uint32_t i = block_index_start; i < block_count + request->first_block_num; ++i) { + const uint8_t* block_data = &request->block_data[block_size * i]; + iso15693_3_set_block_data(instance->data, i, block_data, block_size); + } + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_select_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + if(!(flags & ISO15693_3_REQ_FLAG_T4_ADDRESSED)) { + error = Iso15693_3ErrorFormat; + break; + } + + instance->state = Iso15693_3ListenerStateSelected; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_reset_to_ready_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + instance->state = Iso15693_3ListenerStateReady; + return Iso15693_3ErrorNone; +} + +static Iso15693_3Error iso15693_3_listener_write_afi_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t afi; + } Iso15693_3WriteAfiRequestLayout; + + const Iso15693_3WriteAfiRequestLayout* request = + (const Iso15693_3WriteAfiRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteAfiRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } else if(instance->data->settings.lock_bits.afi) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_WRITE_AFI); + if(error != Iso15693_3ErrorNone) break; + + instance->data->system_info.afi = request->afi; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_lock_afi_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + Iso15693_3LockBits* lock_bits = &instance->data->settings.lock_bits; + + if(lock_bits->afi) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_LOCK_AFI); + if(error != Iso15693_3ErrorNone) break; + + lock_bits->afi = true; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_write_dsfid_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t dsfid; + } Iso15693_3WriteDsfidRequestLayout; + + const Iso15693_3WriteDsfidRequestLayout* request = + (const Iso15693_3WriteDsfidRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteDsfidRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } else if(instance->data->settings.lock_bits.dsfid) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_WRITE_DSFID); + if(error != Iso15693_3ErrorNone) break; + + instance->data->system_info.dsfid = request->dsfid; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_lock_dsfid_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + Iso15693_3LockBits* lock_bits = &instance->data->settings.lock_bits; + + if(lock_bits->dsfid) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_LOCK_DSFID); + if(error != Iso15693_3ErrorNone) break; + + lock_bits->dsfid = true; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_get_system_info_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint8_t system_flags = instance->data->system_info.flags; + bit_buffer_append_byte(instance->tx_buffer, system_flags); // System info flags + + iso15693_3_append_uid(instance->data, instance->tx_buffer); // UID + + if(system_flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.dsfid); + } + if(system_flags & ISO15693_3_SYSINFO_FLAG_AFI) { + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.afi); + } + if(system_flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + const uint8_t memory_info[2] = { + instance->data->system_info.block_count - 1, + instance->data->system_info.block_size - 1, + }; + bit_buffer_append_bytes(instance->tx_buffer, memory_info, COUNT_OF(memory_info)); + } + if(system_flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.ic_ref); + } + + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_get_multi_blocks_security_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t first_block_num; + uint8_t block_count; + } Iso15693_3GetMultiBlocksSecurityRequestLayout; + + const Iso15693_3GetMultiBlocksSecurityRequestLayout* request = + (const Iso15693_3GetMultiBlocksSecurityRequestLayout*)data; + + if(data_size < sizeof(Iso15693_3GetMultiBlocksSecurityRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index_start = request->first_block_num; + const uint32_t block_index_end = block_index_start + request->block_count; + + const uint32_t block_count_max = instance->data->system_info.block_count; + + if(block_index_end >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + bit_buffer_append_byte( + instance->tx_buffer, iso15693_3_is_block_locked(instance->data, i) ? 1 : 0); + } + } while(false); + + return error; +} + +const Iso15693_3ListenerHandlerTable iso15693_3_handler_table = { + .mandatory = + { + iso15693_3_listener_inventory_handler, + iso15693_3_listener_stay_quiet_handler, + }, + .optional = + { + iso15693_3_listener_read_block_handler, + iso15693_3_listener_write_block_handler, + iso15693_3_listener_lock_block_handler, + iso15693_3_listener_read_multi_blocks_handler, + iso15693_3_listener_write_multi_blocks_handler, + iso15693_3_listener_select_handler, + iso15693_3_listener_reset_to_ready_handler, + iso15693_3_listener_write_afi_handler, + iso15693_3_listener_lock_afi_handler, + iso15693_3_listener_write_dsfid_handler, + iso15693_3_listener_lock_dsfid_handler, + iso15693_3_listener_get_system_info_handler, + iso15693_3_listener_get_multi_blocks_security_handler, + }, +}; + +static Iso15693_3Error iso15693_3_listener_handle_standard_request( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t command, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + Iso15693_3RequestHandler handler = NULL; + + if(command < ISO15693_3_CMD_MANDATORY_RFU) { + handler = iso15693_3_handler_table.mandatory[command - ISO15693_3_CMD_MANDATORY_START]; + } else if(command >= ISO15693_3_CMD_OPTIONAL_START && command < ISO15693_3_CMD_OPTIONAL_RFU) { + handler = iso15693_3_handler_table.optional[command - ISO15693_3_CMD_OPTIONAL_START]; + } + + if(handler == NULL) { + error = Iso15693_3ErrorNotSupported; + break; + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_NONE); + + error = handler(instance, data, data_size, flags); + + // The request was fully handled in the protocol extension, no further action necessary + if(error == Iso15693_3ErrorFullyHandled) { + error = Iso15693_3ErrorNone; + } + + // Several commands may not require an answer + if(error == Iso15693_3ErrorFormat || error == Iso15693_3ErrorIgnore) break; + + if(error != Iso15693_3ErrorNone) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_ERROR); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_ERROR_UNKNOWN); + } + + Iso15693_3ListenerSessionState* session_state = &instance->session_state; + + if(!session_state->wait_for_eof) { + error = iso15693_3_listener_send_frame(instance, instance->tx_buffer); + } + + } while(false); + + return error; +} + +static inline Iso15693_3Error iso15693_3_listener_handle_custom_request( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size) { + Iso15693_3Error error; + + do { + typedef struct { + uint8_t manufacturer; + uint8_t extra[]; + } Iso15693_3CustomRequestLayout; + + if(data_size < sizeof(Iso15693_3CustomRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const Iso15693_3CustomRequestLayout* request = (const Iso15693_3CustomRequestLayout*)data; + + if(request->manufacturer != iso15693_3_get_manufacturer_id(instance->data)) { + error = Iso15693_3ErrorIgnore; + break; + } + + // This error code will trigger the CustomCommand listener event + error = Iso15693_3ErrorNotSupported; + } while(false); + + return error; +} + +Iso15693_3Error iso15693_3_listener_set_extension_handler_table( + Iso15693_3Listener* instance, + const Iso15693_3ExtensionHandlerTable* table, + void* context) { + furi_assert(instance); + furi_assert(context); + + instance->extension_table = table; + instance->extension_context = context; + return Iso15693_3ErrorNone; +} + +Iso15693_3Error iso15693_3_listener_ready(Iso15693_3Listener* instance) { + furi_assert(instance); + instance->state = Iso15693_3ListenerStateReady; + return Iso15693_3ErrorNone; +} + +static Iso15693_3Error iso15693_3_listener_process_nfc_error(NfcError error) { + Iso15693_3Error ret = Iso15693_3ErrorNone; + + if(error == NfcErrorNone) { + ret = Iso15693_3ErrorNone; + } else if(error == NfcErrorTimeout) { + ret = Iso15693_3ErrorTimeout; + } else { + ret = Iso15693_3ErrorFieldOff; + } + + return ret; +} + +Iso15693_3Error + iso15693_3_listener_send_frame(Iso15693_3Listener* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso13239_crc_append(Iso13239CrcTypeDefault, instance->tx_buffer); + + NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); + return iso15693_3_listener_process_nfc_error(error); +} + +Iso15693_3Error + iso15693_3_listener_process_request(Iso15693_3Listener* instance, const BitBuffer* rx_buffer) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t flags; + uint8_t command; + uint8_t data[]; + } Iso15693_3RequestLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer); + const size_t buf_size_min = sizeof(Iso15693_3RequestLayout); + + if(buf_size < buf_size_min) { + error = Iso15693_3ErrorFormat; + break; + } + + const Iso15693_3RequestLayout* request = + (const Iso15693_3RequestLayout*)bit_buffer_get_data(rx_buffer); + + Iso15693_3ListenerSessionState* session_state = &instance->session_state; + + if((request->flags & ISO15693_3_REQ_FLAG_INVENTORY_T5) == 0) { + session_state->selected = request->flags & ISO15693_3_REQ_FLAG_T4_SELECTED; + session_state->addressed = request->flags & ISO15693_3_REQ_FLAG_T4_ADDRESSED; + + if(session_state->selected && session_state->addressed) { + // A request mode can be either addressed or selected, but not both + error = Iso15693_3ErrorUnknown; + break; + } else if(instance->state == Iso15693_3ListenerStateQuiet) { + // If the card is quiet, ignore non-addressed commands + if(session_state->addressed) { + error = Iso15693_3ErrorIgnore; + break; + } + } else if(instance->state != Iso15693_3ListenerStateSelected) { + // If the card is not selected, ignore selected commands + if(session_state->selected) { + error = Iso15693_3ErrorIgnore; + break; + } + } + } else { + // If the card is quiet, ignore inventory commands + if(instance->state == Iso15693_3ListenerStateQuiet) { + error = Iso15693_3ErrorIgnore; + break; + } + + session_state->selected = false; + session_state->addressed = false; + } + + if(request->command >= ISO15693_3_CMD_CUSTOM_START) { + // Custom commands are properly handled in the protocol-specific top-level poller + error = iso15693_3_listener_handle_custom_request( + instance, request->data, buf_size - buf_size_min); + break; + } + + const uint8_t* data; + size_t data_size; + + if(session_state->addressed) { + // In addressed mode, UID must be included in each command + const size_t buf_size_min_addr = buf_size_min + ISO15693_3_UID_SIZE; + + if(buf_size < buf_size_min_addr) { + error = Iso15693_3ErrorFormat; + break; + } else if(!iso15693_3_is_equal_uid(instance->data, request->data)) { + error = Iso15693_3ErrorUidMismatch; + break; + } + + data = &request->data[ISO15693_3_UID_SIZE]; + data_size = buf_size - buf_size_min_addr; + + } else { + data = request->data; + data_size = buf_size - buf_size_min; + } + + error = iso15693_3_listener_handle_standard_request( + instance, data, data_size, request->command, request->flags); + + } while(false); + + return error; +} + +Iso15693_3Error iso15693_3_listener_process_single_eof(Iso15693_3Listener* instance) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + if(!instance->session_state.wait_for_eof) { + error = Iso15693_3ErrorUnexpectedResponse; + break; + } + + instance->session_state.wait_for_eof = false; + + error = iso15693_3_listener_send_frame(instance, instance->tx_buffer); + } while(false); + + return error; +} + +Iso15693_3Error iso15693_3_listener_process_uid_mismatch( + Iso15693_3Listener* instance, + const BitBuffer* rx_buffer) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + // No checks, assuming they have been made beforehand + typedef struct { + uint8_t flags; + uint8_t command; + } Iso15693_3RequestLayout; + + const Iso15693_3RequestLayout* request = + (const Iso15693_3RequestLayout*)bit_buffer_get_data(rx_buffer); + + if(request->command == ISO15693_3_CMD_SELECT) { + if(instance->state == Iso15693_3ListenerStateSelected) { + error = iso15693_3_listener_ready(instance); + } + } + + return error; +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h new file mode 100644 index 0000000000..a9e0822bff --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include "iso15693_3_listener.h" + +#include "iso15693_3_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso15693_3ListenerStateReady, + Iso15693_3ListenerStateSelected, + Iso15693_3ListenerStateQuiet, +} Iso15693_3ListenerState; + +typedef struct { + bool selected; + bool addressed; + bool wait_for_eof; +} Iso15693_3ListenerSessionState; + +typedef Iso15693_3Error (*Iso15693_3ExtensionHandler)(void* context, va_list args); + +typedef struct { + Iso15693_3ExtensionHandler mandatory[ISO15693_3_MANDATORY_COUNT]; + Iso15693_3ExtensionHandler optional[ISO15693_3_OPTIONAL_COUNT]; +} Iso15693_3ExtensionHandlerTable; + +struct Iso15693_3Listener { + Nfc* nfc; + Iso15693_3Data* data; + Iso15693_3ListenerState state; + Iso15693_3ListenerSessionState session_state; + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Iso15693_3ListenerEvent iso15693_3_event; + Iso15693_3ListenerEventData iso15693_3_event_data; + NfcGenericCallback callback; + void* context; + + const Iso15693_3ExtensionHandlerTable* extension_table; + void* extension_context; +}; + +Iso15693_3Error iso15693_3_listener_set_extension_handler_table( + Iso15693_3Listener* instance, + const Iso15693_3ExtensionHandlerTable* table, + void* context); + +Iso15693_3Error iso15693_3_listener_ready(Iso15693_3Listener* instance); + +Iso15693_3Error + iso15693_3_listener_send_frame(Iso15693_3Listener* instance, const BitBuffer* tx_buffer); + +Iso15693_3Error + iso15693_3_listener_process_request(Iso15693_3Listener* instance, const BitBuffer* rx_buffer); + +Iso15693_3Error iso15693_3_listener_process_single_eof(Iso15693_3Listener* instance); + +Iso15693_3Error iso15693_3_listener_process_uid_mismatch( + Iso15693_3Listener* instance, + const BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c new file mode 100644 index 0000000000..9500e16539 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c @@ -0,0 +1,122 @@ +#include "iso15693_3_poller_i.h" + +#include + +#include + +#define TAG "ISO15693_3Poller" + +const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static Iso15693_3Poller* iso15693_3_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + Iso15693_3Poller* instance = malloc(sizeof(Iso15693_3Poller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ISO15693_3_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO15693_3_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693); + nfc_set_guard_time_us(instance->nfc, ISO15693_3_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ISO15693_3_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ISO15693_3_POLL_POLL_MIN_US); + instance->data = iso15693_3_alloc(); + + instance->iso15693_3_event.data = &instance->iso15693_3_event_data; + instance->general_event.protocol = NfcProtocolIso15693_3; + instance->general_event.event_data = &instance->iso15693_3_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso15693_3_poller_free(Iso15693_3Poller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + iso15693_3_free(instance->data); + free(instance); +} + +static void iso15693_3_poller_set_callback( + Iso15693_3Poller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso15693_3_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso15693_3Poller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != Iso15693_3PollerStateActivated) { + Iso15693_3Error error = iso15693_3_poller_async_activate(instance, instance->data); + if(error == Iso15693_3ErrorNone) { + instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady; + instance->iso15693_3_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->iso15693_3_event.type = Iso15693_3PollerEventTypeError; + instance->iso15693_3_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady; + instance->iso15693_3_event_data.error = Iso15693_3ErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool iso15693_3_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + Iso15693_3Poller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == Iso15693_3PollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + uint8_t uid[ISO15693_3_UID_SIZE]; + Iso15693_3Error error = iso15693_3_poller_async_inventory(instance, uid); + protocol_detected = (error == Iso15693_3ErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso15693_3 = { + .alloc = (NfcPollerAlloc)iso15693_3_poller_alloc, + .free = (NfcPollerFree)iso15693_3_poller_free, + .set_callback = (NfcPollerSetCallback)iso15693_3_poller_set_callback, + .run = (NfcPollerRun)iso15693_3_poller_run, + .detect = (NfcPollerDetect)iso15693_3_poller_detect, + .get_data = (NfcPollerGetData)iso15693_3_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h new file mode 100644 index 0000000000..9d73242f1a --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include "iso15693_3.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693_3Poller Iso15693_3Poller; + +typedef enum { + Iso15693_3PollerEventTypeError, + Iso15693_3PollerEventTypeReady, +} Iso15693_3PollerEventType; + +typedef struct { + Iso15693_3Error error; +} Iso15693_3PollerEventData; + +typedef struct { + Iso15693_3PollerEventType type; + Iso15693_3PollerEventData* data; +} Iso15693_3PollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h new file mode 100644 index 0000000000..f0447c28ba --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso15693_3; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c new file mode 100644 index 0000000000..8b8d8cee04 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -0,0 +1,303 @@ +#include "iso15693_3_poller_i.h" + +#include + +#define TAG "Iso15693_3Poller" + +#define BITS_IN_BYTE (8) + +#define ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY (32U) + +static Iso15693_3Error iso15693_3_poller_process_nfc_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return Iso15693_3ErrorNone; + case NfcErrorTimeout: + return Iso15693_3ErrorTimeout; + default: + return Iso15693_3ErrorNotPresent; + } +} + +static Iso15693_3Error iso15693_3_poller_filter_error(Iso15693_3Error error) { + switch(error) { + /* If a particular optional command is not supported, the card might + * respond with a "Not supported" error or not respond at all. + * Therefore, treat these errors as non-critical ones. */ + case Iso15693_3ErrorNotSupported: + case Iso15693_3ErrorTimeout: + return Iso15693_3ErrorNone; + default: + return error; + } +} + +static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) { + furi_assert(instance); + + if(instance->state == Iso15693_3PollerStateIdle) { + return iso15693_3_poller_async_activate(instance, NULL); + } + + return Iso15693_3ErrorNone; +} + +static Iso15693_3Error iso15693_3_poller_frame_exchange( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(bit_buffer_get_size_bytes(tx_buffer) > + bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO13239_CRC_SIZE) { + ret = Iso15693_3ErrorBufferOverflow; + break; + } + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso13239_crc_append(Iso13239CrcTypeDefault, instance->tx_buffer); + + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso15693_3_poller_process_nfc_error(error); + break; + } + + if(!iso13239_crc_check(Iso13239CrcTypeDefault, instance->rx_buffer)) { + ret = Iso15693_3ErrorWrongCrc; + break; + } + + iso13239_crc_trim(instance->rx_buffer); + bit_buffer_copy(rx_buffer, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error + iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + iso15693_3_reset(data); + + Iso15693_3Error ret; + + do { + instance->state = Iso15693_3PollerStateColResInProgress; + + // Inventory: Mandatory command + ret = iso15693_3_poller_async_inventory(instance, data->uid); + if(ret != Iso15693_3ErrorNone) { + instance->state = Iso15693_3PollerStateColResFailed; + break; + } + + instance->state = Iso15693_3PollerStateActivated; + + // Get system info: Optional command + Iso15693_3SystemInfo* system_info = &data->system_info; + ret = iso15693_3_poller_async_get_system_info(instance, system_info); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + // Read blocks: Optional command + simple_array_init(data->block_data, system_info->block_count * system_info->block_size); + ret = iso15693_3_poller_async_read_blocks( + instance, + simple_array_get_data(data->block_data), + system_info->block_count, + system_info->block_size); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + // Get block security status: Optional command + simple_array_init(data->block_security, system_info->block_count); + + ret = iso15693_3_poller_async_get_blocks_security( + instance, simple_array_get_data(data->block_security), system_info->block_count); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(uid); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send INVENTORY + bit_buffer_append_byte( + instance->tx_buffer, + ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI | + ISO15693_3_REQ_FLAG_INVENTORY_T5 | ISO15693_3_REQ_FLAG_T5_N_SLOTS_1); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_INVENTORY); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_inventory_response_parse(uid, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_get_system_info( + Iso15693_3Poller* instance, + Iso15693_3SystemInfo* data) { + furi_assert(instance); + furi_assert(data); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send GET SYSTEM INFO + bit_buffer_append_byte( + instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI); + + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_GET_SYS_INFO); + + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_system_info_response_parse(data, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_read_block( + Iso15693_3Poller* instance, + uint8_t* data, + uint8_t block_number, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte( + instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_READ_BLOCK); + bit_buffer_append_byte(instance->tx_buffer, block_number); + + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_read_block_response_parse(data, block_size, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_read_blocks( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + furi_assert(block_count); + furi_assert(block_size); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + for(uint32_t i = 0; i < block_count; ++i) { + ret = iso15693_3_poller_async_read_block(instance, &data[block_size * i], i, block_size); + if(ret != Iso15693_3ErrorNone) break; + } + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_get_blocks_security( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count) { + furi_assert(instance); + furi_assert(data); + + // Limit the number of blocks to 32 in a single query + const uint32_t num_queries = block_count / ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY + + (block_count % ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY ? 1 : 0); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + for(uint32_t i = 0; i < num_queries; ++i) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte( + instance->tx_buffer, + ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI); + + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_GET_BLOCKS_SECURITY); + + const uint8_t start_block_num = i * ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY; + bit_buffer_append_byte(instance->tx_buffer, start_block_num); + + const uint8_t block_count_per_query = + MIN(block_count - start_block_num, (uint16_t)ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY); + // Block count byte must be 1 less than the desired count + bit_buffer_append_byte(instance->tx_buffer, block_count_per_query - 1); + + ret = iso15693_3_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_get_block_security_response_parse( + &data[start_block_num], block_count_per_query, instance->rx_buffer); + if(ret != Iso15693_3ErrorNone) break; + } + + return ret; +} + +Iso15693_3Error iso15693_3_poller_send_frame( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_prepare_trx(instance); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h new file mode 100644 index 0000000000..154ee684c9 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h @@ -0,0 +1,70 @@ +#pragma once + +#include "iso15693_3_poller.h" + +#include "iso15693_3_i.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO15693_3_POLLER_MAX_BUFFER_SIZE (64U) + +typedef enum { + Iso15693_3PollerStateIdle, + Iso15693_3PollerStateColResInProgress, + Iso15693_3PollerStateColResFailed, + Iso15693_3PollerStateActivated, +} Iso15693_3PollerState; + +struct Iso15693_3Poller { + Nfc* nfc; + Iso15693_3PollerState state; + Iso15693_3Data* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + Iso15693_3PollerEvent iso15693_3_event; + Iso15693_3PollerEventData iso15693_3_event_data; + NfcGenericCallback callback; + void* context; +}; + +const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance); + +Iso15693_3Error iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data); + +Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid); + +Iso15693_3Error + iso15693_3_poller_async_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data); + +Iso15693_3Error iso15693_3_poller_async_read_block( + Iso15693_3Poller* instance, + uint8_t* data, + uint8_t block_number, + uint8_t block_size); + +Iso15693_3Error iso15693_3_poller_async_read_blocks( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count, + uint8_t block_size); + +Iso15693_3Error iso15693_3_poller_async_get_blocks_security( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count); + +Iso15693_3Error iso15693_3_poller_send_frame( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/protocols/mf_classic/crypto1.c new file mode 100644 index 0000000000..df01a348c8 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/crypto1.c @@ -0,0 +1,172 @@ +#include "crypto1.h" + +#include +#include + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) + +#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) + +Crypto1* crypto1_alloc() { + Crypto1* instance = malloc(sizeof(Crypto1)); + + return instance; +} + +void crypto1_free(Crypto1* instance) { + furi_assert(instance); + + free(instance); +} + +void crypto1_reset(Crypto1* crypto1) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; +} + +void crypto1_init(Crypto1* crypto1, uint64_t key) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; + for(int8_t i = 47; i > 0; i -= 2) { + crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); + crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); + } +} + +static uint32_t crypto1_filter(uint32_t in) { + uint32_t out = 0; + out = 0xf22c0 >> (in & 0xf) & 16; + out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; + out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; + out |= 0x1e458 >> (in >> 12 & 0xf) & 2; + out |= 0x0d938 >> (in >> 16 & 0xf) & 1; + return FURI_BIT(0xEC57E80A, out); +} + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = crypto1_filter(crypto1->odd); + uint32_t feed = out & (!!is_encrypted); + feed ^= !!in; + feed ^= LF_POLY_ODD & crypto1->odd; + feed ^= LF_POLY_EVEN & crypto1->even; + crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); + + FURI_SWAP(crypto1->odd, crypto1->even); + return out; +} + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = 0; + for(uint8_t i = 0; i < 8; i++) { + out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; + } + return out; +} + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { + furi_assert(crypto1); + uint32_t out = 0; + for(uint8_t i = 0; i < 32; i++) { + out |= (uint32_t)crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); + } + return out; +} + +uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + + return SWAPENDIAN(x); +} + +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* encrypted_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t decrypted_byte = 0; + uint8_t encrypted_byte = encrypted_data[0]; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 3)) << 3; + bit_buffer_set_byte(out, 0, decrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t decrypted_byte = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + bit_buffer_set_byte(out, i, decrypted_byte); + } + } +} + +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* plain_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t encrypted_byte = 0; + for(size_t i = 0; i < bits; i++) { + encrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + bit_buffer_set_byte(out, 0, encrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t encrypted_byte = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + bool parity_bit = + ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, encrypted_byte, parity_bit); + } + } +} + +void crypto1_encrypt_reader_nonce( + Crypto1* crypto, + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out) { + furi_assert(crypto); + furi_assert(nt); + furi_assert(nr); + furi_assert(out); + + bit_buffer_set_size_bytes(out, 8); + uint32_t nt_num = nfc_util_bytes2num(nt, sizeof(uint32_t)); + + crypto1_init(crypto, key); + crypto1_word(crypto, nt_num ^ cuid, 0); + + for(size_t i = 0; i < 4; i++) { + uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + nr[i] = byte; + } + + nt_num = prng_successor(nt_num, 32); + for(size_t i = 4; i < 8; i++) { + nt_num = prng_successor(nt_num, 8); + uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num); + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + } +} diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/mf_classic/crypto1.h similarity index 56% rename from lib/nfc/protocols/crypto1.h rename to lib/nfc/protocols/mf_classic/crypto1.h index bbf6dc239c..f2bdb272b0 100644 --- a/lib/nfc/protocols/crypto1.h +++ b/lib/nfc/protocols/mf_classic/crypto1.h @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -12,6 +11,10 @@ typedef struct { uint32_t even; } Crypto1; +Crypto1* crypto1_alloc(); + +void crypto1_free(Crypto1* instance); + void crypto1_reset(Crypto1* crypto1); void crypto1_init(Crypto1* crypto1, uint64_t key); @@ -22,24 +25,20 @@ uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); -uint32_t crypto1_filter(uint32_t in); +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out); -uint32_t prng_successor(uint32_t x, uint32_t n); +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out); -void crypto1_decrypt( +void crypto1_encrypt_reader_nonce( Crypto1* crypto, - uint8_t* encrypted_data, - uint16_t encrypted_data_bits, - uint8_t* decrypted_data); + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out); -void crypto1_encrypt( - Crypto1* crypto, - uint8_t* keystream, - uint8_t* plain_data, - uint16_t plain_data_bits, - uint8_t* encrypted_data, - uint8_t* encrypted_parity); +uint32_t prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c new file mode 100644 index 0000000000..400cf0d7fb --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -0,0 +1,740 @@ +#include "mf_classic.h" + +#include +#include + +#include + +#define MF_CLASSIC_PROTOCOL_NAME "Mifare Classic" + +typedef struct { + uint8_t sectors_total; + uint16_t blocks_total; + const char* full_name; + const char* type_name; +} MfClassicFeatures; + +static const uint32_t mf_classic_data_format_version = 2; + +static const MfClassicFeatures mf_classic_features[MfClassicTypeNum] = { + [MfClassicTypeMini] = + { + .sectors_total = 5, + .blocks_total = 20, + .full_name = "Mifare Classic Mini 0.3K", + .type_name = "MINI", + }, + [MfClassicType1k] = + { + .sectors_total = 16, + .blocks_total = 64, + .full_name = "Mifare Classic 1K", + .type_name = "1K", + }, + [MfClassicType4k] = + { + .sectors_total = 40, + .blocks_total = 256, + .full_name = "Mifare Classic 4K", + .type_name = "4K", + }, +}; + +const NfcDeviceBase nfc_device_mf_classic = { + .protocol_name = MF_CLASSIC_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_classic_alloc, + .free = (NfcDeviceFree)mf_classic_free, + .reset = (NfcDeviceReset)mf_classic_reset, + .copy = (NfcDeviceCopy)mf_classic_copy, + .verify = (NfcDeviceVerify)mf_classic_verify, + .load = (NfcDeviceLoad)mf_classic_load, + .save = (NfcDeviceSave)mf_classic_save, + .is_equal = (NfcDeviceEqual)mf_classic_is_equal, + .get_name = (NfcDeviceGetName)mf_classic_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_classic_get_uid, + .set_uid = (NfcDeviceSetUid)mf_classic_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_classic_get_base_data, +}; + +MfClassicData* mf_classic_alloc() { + MfClassicData* data = malloc(sizeof(MfClassicData)); + data->iso14443_3a_data = iso14443_3a_alloc(); + return data; +} + +void mf_classic_free(MfClassicData* data) { + furi_assert(data); + + iso14443_3a_free(data->iso14443_3a_data); + free(data); +} + +void mf_classic_reset(MfClassicData* data) { + furi_assert(data); + + iso14443_3a_reset(data->iso14443_3a_data); +} + +void mf_classic_copy(MfClassicData* data, const MfClassicData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data); + for(size_t i = 0; i < COUNT_OF(data->block); i++) { + data->block[i] = other->block[i]; + } + for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) { + data->block_read_mask[i] = other->block_read_mask[i]; + } + data->type = other->type; + data->key_a_mask = other->key_a_mask; + data->key_b_mask = other->key_b_mask; +} + +bool mf_classic_verify(MfClassicData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, "Mifare Classic"); +} + +static void mf_classic_parse_block(FuriString* block_str, MfClassicData* data, uint8_t block_num) { + furi_string_trim(block_str); + MfClassicBlock block_tmp = {}; + bool is_sector_trailer = mf_classic_is_sector_trailer(block_num); + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint16_t block_unknown_bytes_mask = 0; + + furi_string_trim(block_str); + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { + char hi = furi_string_get_char(block_str, 3 * i); + char low = furi_string_get_char(block_str, 3 * i + 1); + uint8_t byte = 0; + if(hex_char_to_uint8(hi, low, &byte)) { + block_tmp.data[i] = byte; + } else { + FURI_BIT_SET(block_unknown_bytes_mask, i); + } + } + + if(block_unknown_bytes_mask != 0xffff) { + if(is_sector_trailer) { + MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp; + // Load Key A + // Key A mask 0b0000000000111111 = 0x003f + if((block_unknown_bytes_mask & 0x003f) == 0) { + uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a.data, sizeof(MfClassicKey)); + mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeA, key); + } + // Load Access Bits + // Access bits mask 0b0000001111000000 = 0x03c0 + if((block_unknown_bytes_mask & 0x03c0) == 0) { + mf_classic_set_block_read(data, block_num, &block_tmp); + } + // Load Key B + // Key B mask 0b1111110000000000 = 0xfc00 + if((block_unknown_bytes_mask & 0xfc00) == 0) { + uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b.data, sizeof(MfClassicKey)); + mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeB, key); + } + } else { + if(block_unknown_bytes_mask == 0) { + mf_classic_set_block_read(data, block_num, &block_tmp); + } + } + } +} + +bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool parsed = false; + + do { + // Read ISO14443_3A data + if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break; + + // Read Mifare Classic type + if(!flipper_format_read_string(ff, "Mifare Classic type", temp_str)) break; + bool type_parsed = false; + for(size_t i = 0; i < MfClassicTypeNum; i++) { + if(furi_string_equal_str(temp_str, mf_classic_features[i].type_name)) { + data->type = i; + type_parsed = true; + } + } + if(!type_parsed) break; + + // Read format version + uint32_t data_format_version = 0; + bool old_format = false; + // Read Mifare Classic format version + if(!flipper_format_read_uint32(ff, "Data format version", &data_format_version, 1)) { + // Load unread sectors with zero keys access for backward compatibility + if(!flipper_format_rewind(ff)) break; + old_format = true; + } else { + if(data_format_version < mf_classic_data_format_version) { + old_format = true; + } + } + + // Read Mifare Classic blocks + bool block_read = true; + FuriString* block_str = furi_string_alloc(); + uint16_t blocks_total = mf_classic_get_total_block_num(data->type); + for(size_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_string(ff, furi_string_get_cstr(temp_str), block_str)) { + block_read = false; + break; + } + mf_classic_parse_block(block_str, data, i); + } + furi_string_free(block_str); + if(!block_read) break; + + // Set keys and blocks as unknown for backward compatibility + if(old_format) { + data->key_a_mask = 0ULL; + data->key_b_mask = 0ULL; + memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); + } + + parsed = true; + } while(false); + + furi_string_free(temp_str); + + return parsed; +} + +static void + mf_classic_set_block_str(FuriString* block_str, const MfClassicData* data, uint8_t block_num) { + furi_string_reset(block_str); + bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); + if(is_sec_trailer) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + // Write key A + for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) { + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a.data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + // Write Access bytes + for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) { + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits.data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + // Write key B + for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) { + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b.data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + } else { + // Write data block + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf(block_str, "%02X ", data->block[block_num].data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + } + furi_string_trim(block_str); +} + +bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool saved = false; + + do { + if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, "Mifare Classic specific data")) break; + if(!flipper_format_write_string_cstr( + ff, "Mifare Classic type", mf_classic_features[data->type].type_name)) + break; + if(!flipper_format_write_uint32( + ff, "Data format version", &mf_classic_data_format_version, 1)) + break; + if(!flipper_format_write_comment_cstr( + ff, "Mifare Classic blocks, \'??\' means unknown data")) + break; + + uint16_t blocks_total = mf_classic_get_total_block_num(data->type); + FuriString* block_str = furi_string_alloc(); + bool block_saved = true; + for(size_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + mf_classic_set_block_str(block_str, data, i); + if(!flipper_format_write_string(ff, furi_string_get_cstr(temp_str), block_str)) { + block_saved = false; + break; + } + } + furi_string_free(block_str); + if(!block_saved) break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + + return saved; +} + +bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other) { + bool is_equal = false; + bool data_array_is_equal = true; + + do { + if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break; + if(data->type != other->type) break; + if(data->key_a_mask != other->key_a_mask) break; + if(data->key_b_mask != other->key_b_mask) break; + + for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) { + if(data->block_read_mask[i] != other->block_read_mask[i]) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + for(size_t i = 0; i < COUNT_OF(data->block); i++) { + if(memcmp(&data->block[i], &other->block[i], sizeof(data->block[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + is_equal = true; + } while(false); + + return is_equal; +} + +const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type) { + furi_assert(data); + furi_assert(data->type < MfClassicTypeNum); + + if(name_type == NfcDeviceNameTypeFull) { + return mf_classic_features[data->type].full_name; + } else { + return mf_classic_features[data->type].type_name; + } +} + +const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len); +} + +bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); +} + +Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data) { + furi_assert(data); + + return data->iso14443_3a_data; +} + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { + return mf_classic_features[type].sectors_total; +} + +uint16_t mf_classic_get_total_block_num(MfClassicType type) { + return mf_classic_features[type].blocks_total; +} + +uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector) { + uint8_t block_num = 0; + + if(sector < 32) { + block_num = sector * 4 + 3; + } else if(sector < 40) { + block_num = 32 * 4 + (sector - 32) * 16 + 15; + } else { + furi_crash("Wrong sector num"); + } + + return block_num; +} + +uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) { + uint8_t sec_tr_block_num = 0; + + if(block < 128) { + sec_tr_block_num = block | 0x03; + } else { + sec_tr_block_num = block | 0x0f; + } + + return sec_tr_block_num; +} + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + uint8_t sec_tr_block = mf_classic_get_sector_trailer_num_by_sector(sector_num); + MfClassicSectorTrailer* sec_trailer = (MfClassicSectorTrailer*)&data->block[sec_tr_block]; + + return sec_trailer; +} + +bool mf_classic_is_sector_trailer(uint8_t block) { + return block == mf_classic_get_sector_trailer_num_by_block(block); +} + +uint8_t mf_classic_get_sector_by_block(uint8_t block) { + uint8_t sector = 0; + + if(block < 128) { + sector = (block | 0x03) / 4; + } else { + sector = 32 + ((block | 0x0f) - 32 * 4) / 16; + } + + return sector; +} + +bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr) { + furi_assert(block); + + uint32_t v = *(uint32_t*)&block->data[0]; + uint32_t v_inv = *(uint32_t*)&block->data[sizeof(uint32_t)]; + uint32_t v1 = *(uint32_t*)&block->data[sizeof(uint32_t) * 2]; + + bool val_checks = + ((v == v1) && (v == ~v_inv) && (block->data[12] == (~block->data[13] & 0xFF)) && + (block->data[14] == (~block->data[15] & 0xFF)) && (block->data[12] == block->data[14])); + if(value) { + *value = (int32_t)v; + } + if(addr) { + *addr = block->data[12]; + } + return val_checks; +} + +void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block) { + furi_assert(block); + + uint32_t v_inv = ~((uint32_t)value); + + memcpy(&block->data[0], &value, 4); //-V1086 + memcpy(&block->data[4], &v_inv, 4); //-V1086 + memcpy(&block->data[8], &value, 4); //-V1086 + + block->data[12] = addr; + block->data[13] = ~addr & 0xFF; + block->data[14] = addr; + block->data[15] = ~addr & 0xFF; +} + +bool mf_classic_is_key_found( + const MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type) { + furi_assert(data); + + bool key_found = false; + if(key_type == MfClassicKeyTypeA) { + key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1); + } else if(key_type == MfClassicKeyTypeB) { + key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1); + } + + return key_found; +} + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type, + uint64_t key) { + furi_assert(data); + + uint8_t key_arr[6] = {}; + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + nfc_util_num2bytes(key, 6, key_arr); + if(key_type == MfClassicKeyTypeA) { + memcpy(sec_trailer->key_a.data, key_arr, sizeof(MfClassicKey)); + FURI_BIT_SET(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyTypeB) { + memcpy(sec_trailer->key_b.data, key_arr, sizeof(MfClassicKey)); + FURI_BIT_SET(data->key_b_mask, sector_num); + } +} + +void mf_classic_set_key_not_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type) { + furi_assert(data); + + if(key_type == MfClassicKeyTypeA) { + FURI_BIT_CLEAR(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyTypeB) { + FURI_BIT_CLEAR(data->key_b_mask, sector_num); + } +} + +bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) { + furi_assert(data); + + return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1); +} + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) { + furi_assert(data); + + if(mf_classic_is_sector_trailer(block_num)) { + memcpy(&data->block[block_num].data[6], &block_data->data[6], 4); + } else { + memcpy(data->block[block_num].data, block_data->data, MF_CLASSIC_BLOCK_SIZE); + } + FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); +} + +uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { + furi_assert(sector < 40); + + uint8_t block = 0; + if(sector < 32) { + block = sector * 4; + } else { + block = 32 * 4 + (sector - 32) * 16; + } + + return block; +} + +uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { + furi_assert(sector < 40); + return sector < 32 ? 4 : 16; +} + +void mf_classic_get_read_sectors_and_keys( + const MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found) { + furi_assert(data); + furi_assert(sectors_read); + furi_assert(keys_found); + + *sectors_read = 0; + *keys_found = 0; + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + for(size_t i = 0; i < sectors_total; i++) { + if(mf_classic_is_key_found(data, i, MfClassicKeyTypeA)) { + *keys_found += 1; + } + if(mf_classic_is_key_found(data, i, MfClassicKeyTypeB)) { + *keys_found += 1; + } + uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); + uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i); + bool blocks_read = true; + for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) { + blocks_read = mf_classic_is_block_read(data, j); + if(!blocks_read) break; + } + if(blocks_read) { + *sectors_read += 1; + } + } +} + +bool mf_classic_is_card_read(const MfClassicData* data) { + furi_assert(data); + + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2); + + return card_read; +} + +bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + bool sector_read = false; + do { + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) break; + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) break; + uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); + uint8_t block_read = true; + for(size_t i = start_block; i < start_block + total_blocks; i++) { + block_read = mf_classic_is_block_read(data, i); + if(!block_read) break; + } + sector_read = block_read; + } while(false); + + return sector_read; +} + +static bool mf_classic_is_allowed_access_sector_trailer( + MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + uint8_t* access_bits_arr = sec_tr->access_bits.data; + uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) | + ((access_bits_arr[2] >> 7) & 0x01); + + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: + case MfClassicActionKeyBWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionKeyBRead: { + return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionACRead: { + return ( + (key_type == MfClassicKeyTypeA) || + (key_type == MfClassicKeyTypeB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + } + case MfClassicActionACWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +bool mf_classic_is_allowed_access_data_block( + MfClassicSectorTrailer* sec_tr, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + furi_assert(sec_tr); + + uint8_t* access_bits_arr = sec_tr->access_bits.data; + + if(block_num == 0 && action == MfClassicActionDataWrite) { + return false; + } + + uint8_t sector_block = 0; + if(block_num <= 128) { + sector_block = block_num & 0x03; + } else { + sector_block = (block_num & 0x0f) / 5; + } + + uint8_t AC; + switch(sector_block) { + case 0x00: { + AC = ((access_bits_arr[1] >> 2) & 0x04) | ((access_bits_arr[2] << 1) & 0x02) | + ((access_bits_arr[2] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((access_bits_arr[1] >> 3) & 0x04) | ((access_bits_arr[2] >> 0) & 0x02) | + ((access_bits_arr[2] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((access_bits_arr[1] >> 4) & 0x04) | ((access_bits_arr[2] >> 1) & 0x02) | + ((access_bits_arr[2] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key_type == MfClassicKeyTypeA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key_type == MfClassicKeyTypeB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +bool mf_classic_is_allowed_access( + MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + furi_assert(data); + + bool access_allowed = false; + if(mf_classic_is_sector_trailer(block_num)) { + access_allowed = + mf_classic_is_allowed_access_sector_trailer(data, block_num, key_type, action); + } else { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + access_allowed = + mf_classic_is_allowed_access_data_block(sec_tr, block_num, key_type, action); + } + + return access_allowed; +} + +bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num) { + furi_assert(sec_tr); + + // Check if key A can write, if it can, it's transport configuration, not data block + return !mf_classic_is_allowed_access_data_block( + sec_tr, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) && + (mf_classic_is_allowed_access_data_block( + sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataInc) || + mf_classic_is_allowed_access_data_block( + sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataDec)); +} diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h new file mode 100644 index 0000000000..f180411df5 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -0,0 +1,235 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) +#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) +#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) +#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) +#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) +#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) +#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) +#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) + +#define MF_CLASSIC_CMD_HALT_MSB (0x50) +#define MF_CLASSIC_CMD_HALT_LSB (0x00) +#define MF_CLASSIC_CMD_ACK (0x0A) +#define MF_CLASSIC_CMD_NACK (0x00) + +#define MF_CLASSIC_TOTAL_SECTORS_MAX (40) +#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) +#define MF_CLASSIC_READ_MASK_SIZE (MF_CLASSIC_TOTAL_BLOCKS_MAX / 32) +#define MF_CLASSIC_BLOCK_SIZE (16) +#define MF_CLASSIC_KEY_SIZE (6) +#define MF_CLASSIC_ACCESS_BYTES_SIZE (4) + +#define MF_CLASSIC_NT_SIZE (4) +#define MF_CLASSIC_NR_SIZE (4) +#define MF_CLASSIC_AR_SIZE (4) +#define MF_CLASSIC_AT_SIZE (4) + +typedef enum { + MfClassicErrorNone, + MfClassicErrorNotPresent, + MfClassicErrorProtocol, + MfClassicErrorAuth, + MfClassicErrorTimeout, +} MfClassicError; + +typedef enum { + MfClassicTypeMini, + MfClassicType1k, + MfClassicType4k, + + MfClassicTypeNum, +} MfClassicType; + +typedef enum { + MfClassicActionDataRead, + MfClassicActionDataWrite, + MfClassicActionDataInc, + MfClassicActionDataDec, + + MfClassicActionKeyARead, + MfClassicActionKeyAWrite, + MfClassicActionKeyBRead, + MfClassicActionKeyBWrite, + MfClassicActionACRead, + MfClassicActionACWrite, +} MfClassicAction; + +typedef enum { + MfClassicValueCommandIncrement, + MfClassicValueCommandDecrement, + MfClassicValueCommandRestore, + + MfClassicValueCommandInvalid, +} MfClassicValueCommand; + +typedef struct { + uint8_t data[MF_CLASSIC_BLOCK_SIZE]; +} MfClassicBlock; + +typedef enum { + MfClassicKeyTypeA, + MfClassicKeyTypeB, +} MfClassicKeyType; + +typedef struct { + uint8_t data[MF_CLASSIC_KEY_SIZE]; +} MfClassicKey; + +typedef struct { + uint8_t data[MF_CLASSIC_ACCESS_BYTES_SIZE]; +} MfClassicAccessBits; + +typedef struct { + uint8_t data[MF_CLASSIC_NT_SIZE]; +} MfClassicNt; + +typedef struct { + uint8_t data[MF_CLASSIC_AT_SIZE]; +} MfClassicAt; + +typedef struct { + uint8_t data[MF_CLASSIC_NR_SIZE]; +} MfClassicNr; + +typedef struct { + uint8_t data[MF_CLASSIC_AR_SIZE]; +} MfClassicAr; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicNt nt; + MfClassicNr nr; + MfClassicAr ar; + MfClassicAt at; +} MfClassicAuthContext; + +typedef union { + MfClassicBlock block; + struct { + MfClassicKey key_a; + MfClassicAccessBits access_bits; + MfClassicKey key_b; + }; +} MfClassicSectorTrailer; + +typedef struct { + uint64_t key_a_mask; + MfClassicKey key_a[MF_CLASSIC_TOTAL_SECTORS_MAX]; + uint64_t key_b_mask; + MfClassicKey key_b[MF_CLASSIC_TOTAL_SECTORS_MAX]; +} MfClassicDeviceKeys; + +typedef struct { + Iso14443_3aData* iso14443_3a_data; + MfClassicType type; + uint32_t block_read_mask[MF_CLASSIC_READ_MASK_SIZE]; + uint64_t key_a_mask; + uint64_t key_b_mask; + MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; +} MfClassicData; + +extern const NfcDeviceBase nfc_device_mf_classic; + +MfClassicData* mf_classic_alloc(); + +void mf_classic_free(MfClassicData* data); + +void mf_classic_reset(MfClassicData* data); + +void mf_classic_copy(MfClassicData* data, const MfClassicData* other); + +bool mf_classic_verify(MfClassicData* data, const FuriString* device_type); + +bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version); + +bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff); + +bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other); + +const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len); + +bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data); + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type); + +uint16_t mf_classic_get_total_block_num(MfClassicType type); + +uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector); + +uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector); + +uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector); + +uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block); + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num); + +bool mf_classic_is_sector_trailer(uint8_t block); + +uint8_t mf_classic_get_sector_by_block(uint8_t block); + +bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr); + +void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block); + +bool mf_classic_is_key_found( + const MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type); + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type, + uint64_t key); + +void mf_classic_set_key_not_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type); + +bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num); + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); + +bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num); + +void mf_classic_get_read_sectors_and_keys( + const MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found); + +bool mf_classic_is_card_read(const MfClassicData* data); + +bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num); + +bool mf_classic_is_allowed_access_data_block( + MfClassicSectorTrailer* sec_tr, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action); + +bool mf_classic_is_allowed_access( + MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c new file mode 100644 index 0000000000..f7bd5b3f45 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -0,0 +1,656 @@ +#include "mf_classic_listener_i.h" + +#include + +#include +#include + +#include +#include + +#define TAG "MfClassicListener" + +#define MF_CLASSIC_MAX_BUFF_SIZE (64) + +typedef MfClassicListenerCommand ( + *MfClassicListenerCommandHandler)(MfClassicListener* instance, BitBuffer* buf); + +typedef struct { + uint8_t cmd_start_byte; + size_t cmd_len_bits; + size_t command_num; + MfClassicListenerCommandHandler* handler; +} MfClassicListenerCmd; + +static void mf_classic_listener_prepare_emulation(MfClassicListener* instance) { + instance->total_block_num = mf_classic_get_total_block_num(instance->data->type); +} + +static void mf_classic_listener_reset_state(MfClassicListener* instance) { + crypto1_reset(instance->crypto); + memset(&instance->auth_context, 0, sizeof(MfClassicAuthContext)); + instance->comm_state = MfClassicListenerCommStatePlain; + instance->state = MfClassicListenerStateIdle; + instance->cmd_in_progress = false; + instance->current_cmd_handler_idx = 0; + instance->transfer_value = 0; + instance->value_cmd = MfClassicValueCommandInvalid; +} + +static MfClassicListenerCommand + mf_classic_listener_halt_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + + if(bit_buffer_get_byte(buff, 1) == MF_CLASSIC_CMD_HALT_LSB) { + mf_classic_listener_reset_state(instance); + command = MfClassicListenerCommandSleep; + } + + return command; +} + +static MfClassicListenerCommand mf_classic_listener_auth_first_part_handler( + MfClassicListener* instance, + MfClassicKeyType key_type, + uint8_t block_num) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + + do { + instance->state = MfClassicListenerStateIdle; + + if(block_num >= instance->total_block_num) { + mf_classic_listener_reset_state(instance); + break; + } + + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(instance->data, sector_num); + MfClassicKey* key = (key_type == MfClassicKeyTypeA) ? &sec_tr->key_a : &sec_tr->key_b; + uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey)); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + instance->auth_context.key_type = key_type; + instance->auth_context.block_num = block_num; + + furi_hal_random_fill_buf(instance->auth_context.nt.data, sizeof(MfClassicNt)); + uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); + + crypto1_init(instance->crypto, key_num); + if(instance->comm_state == MfClassicListenerCommStatePlain) { + crypto1_word(instance->crypto, nt_num ^ cuid, 0); + bit_buffer_copy_bytes( + instance->tx_encrypted_buffer, + instance->auth_context.nt.data, + sizeof(MfClassicNt)); + iso14443_3a_listener_tx(instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + command = MfClassicListenerCommandProcessed; + } else { + uint8_t key_stream[4] = {}; + nfc_util_num2bytes(nt_num ^ cuid, sizeof(uint32_t), key_stream); + bit_buffer_copy_bytes( + instance->tx_plain_buffer, instance->auth_context.nt.data, sizeof(MfClassicNt)); + crypto1_encrypt( + instance->crypto, + key_stream, + instance->tx_plain_buffer, + instance->tx_encrypted_buffer); + + iso14443_3a_listener_tx_with_custom_parity( + instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + + command = MfClassicListenerCommandProcessed; + } + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_auth_key_a_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler( + instance, MfClassicKeyTypeA, bit_buffer_get_byte(buff, 1)); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_auth_key_b_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler( + instance, MfClassicKeyTypeB, bit_buffer_get_byte(buff, 1)); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_auth_second_part_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandSilent; + + do { + instance->cmd_in_progress = false; + + if(bit_buffer_get_size_bytes(buff) != (sizeof(MfClassicNr) + sizeof(MfClassicAr))) { + mf_classic_listener_reset_state(instance); + break; + } + bit_buffer_write_bytes_mid(buff, instance->auth_context.nr.data, 0, sizeof(MfClassicNr)); + bit_buffer_write_bytes_mid( + buff, instance->auth_context.ar.data, sizeof(MfClassicNr), sizeof(MfClassicAr)); + + if(instance->callback) { + instance->mfc_event.type = MfClassicListenerEventTypeAuthContextPartCollected, + instance->mfc_event_data.auth_context = instance->auth_context; + instance->callback(instance->generic_event, instance->context); + } + + uint32_t nr_num = nfc_util_bytes2num(instance->auth_context.nr.data, sizeof(MfClassicNr)); + uint32_t ar_num = nfc_util_bytes2num(instance->auth_context.ar.data, sizeof(MfClassicAr)); + + crypto1_word(instance->crypto, nr_num, 1); + uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); + uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); + if(secret_poller != prng_successor(nt_num, 64)) { + FURI_LOG_D( + TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); + mf_classic_listener_reset_state(instance); + break; + } + + uint32_t at_num = prng_successor(nt_num, 96); + nfc_util_num2bytes(at_num, sizeof(uint32_t), instance->auth_context.at.data); + bit_buffer_copy_bytes( + instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr)); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + iso14443_3a_listener_tx_with_custom_parity( + instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + + instance->state = MfClassicListenerStateAuthComplete; + instance->comm_state = MfClassicListenerCommStateEncrypted; + command = MfClassicListenerCommandProcessed; + + if(instance->callback) { + instance->mfc_event.type = MfClassicListenerEventTypeAuthContextFullCollected, + instance->mfc_event_data.auth_context = instance->auth_context; + instance->callback(instance->generic_event, instance->context); + } + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_read_block_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + if(instance->state != MfClassicListenerStateAuthComplete) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); + if(sector_num != auth_sector_num) break; + + MfClassicBlock access_block = instance->data->block[block_num]; + + if(mf_classic_is_sector_trailer(block_num)) { + MfClassicSectorTrailer* access_sec_tr = (MfClassicSectorTrailer*)&access_block; + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyARead)) { + memset(access_sec_tr->key_a.data, 0, sizeof(MfClassicKey)); + } + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyBRead)) { + memset(access_sec_tr->key_b.data, 0, sizeof(MfClassicKey)); + } + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionACRead)) { + memset(access_sec_tr->access_bits.data, 0, sizeof(MfClassicAccessBits)); + } + } else if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionDataRead)) { + break; + } + + bit_buffer_copy_bytes( + instance->tx_plain_buffer, access_block.data, sizeof(MfClassicBlock)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + iso14443_3a_listener_tx_with_custom_parity( + instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + command = MfClassicListenerCommandProcessed; + } while(false); + + return command; +} + +static MfClassicListenerCommand mf_classic_listener_write_block_first_part_handler( + MfClassicListener* instance, + BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + if(instance->state != MfClassicListenerStateAuthComplete) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + if(block_num >= instance->total_block_num) break; + + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); + if(sector_num != auth_sector_num) break; + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommand mf_classic_listener_write_block_second_part_handler( + MfClassicListener* instance, + BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + instance->cmd_in_progress = false; + + size_t buff_size = bit_buffer_get_size_bytes(buff); + if(buff_size != sizeof(MfClassicBlock)) break; + + uint8_t block_num = auth_ctx->block_num; + MfClassicKeyType key_type = auth_ctx->key_type; + MfClassicBlock block = instance->data->block[block_num]; + + if(mf_classic_is_sector_trailer(block_num)) { + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)█ + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyAWrite)) { + bit_buffer_write_bytes_mid(buff, sec_tr->key_a.data, 0, sizeof(MfClassicKey)); + } + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyBWrite)) { + bit_buffer_write_bytes_mid(buff, sec_tr->key_b.data, 10, sizeof(MfClassicKey)); + } + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionACWrite)) { + bit_buffer_write_bytes_mid( + buff, sec_tr->access_bits.data, 6, sizeof(MfClassicAccessBits)); + } + } else { + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionDataWrite)) { + bit_buffer_write_bytes_mid(buff, block.data, 0, sizeof(MfClassicBlock)); + } else { + break; + } + } + + instance->data->block[block_num] = block; + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_value_cmd_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + if(instance->state != MfClassicListenerStateAuthComplete) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + if(block_num >= instance->total_block_num) break; + + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); + if(sector_num != auth_sector_num) break; + + uint8_t cmd = bit_buffer_get_byte(buff, 0); + MfClassicAction action = MfClassicActionDataDec; + if(cmd == MF_CLASSIC_CMD_VALUE_DEC) { + instance->value_cmd = MfClassicValueCommandDecrement; + } else if(cmd == MF_CLASSIC_CMD_VALUE_INC) { + instance->value_cmd = MfClassicValueCommandIncrement; + action = MfClassicActionDataInc; + } else if(cmd == MF_CLASSIC_CMD_VALUE_RESTORE) { + instance->value_cmd = MfClassicValueCommandRestore; + } else { + break; + } + + if(!mf_classic_is_allowed_access(instance->data, block_num, auth_ctx->key_type, action)) { + break; + } + + if(!mf_classic_block_to_value( + &instance->data->block[block_num], &instance->transfer_value, NULL)) { + break; + } + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_value_dec_handler(MfClassicListener* instance, BitBuffer* buff) { + return mf_classic_listener_value_cmd_handler(instance, buff); +} + +static MfClassicListenerCommand + mf_classic_listener_value_inc_handler(MfClassicListener* instance, BitBuffer* buff) { + return mf_classic_listener_value_cmd_handler(instance, buff); +} + +static MfClassicListenerCommand + mf_classic_listener_value_restore_handler(MfClassicListener* instance, BitBuffer* buff) { + return mf_classic_listener_value_cmd_handler(instance, buff); +} + +static MfClassicListenerCommand + mf_classic_listener_value_data_receive_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + + do { + if(bit_buffer_get_size_bytes(buff) != 4) break; + + int32_t data; + bit_buffer_write_bytes_mid(buff, &data, 0, sizeof(data)); + + if(data < 0) { + data = -data; + } + + if(instance->value_cmd == MfClassicValueCommandDecrement) { + data = -data; + } else if(instance->value_cmd == MfClassicValueCommandRestore) { + data = 0; + } + + instance->transfer_value += data; + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + command = MfClassicListenerCommandSilent; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_value_transfer_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + instance->cmd_in_progress = false; + + if(bit_buffer_get_size_bytes(buff) != 2) break; + if(bit_buffer_get_byte(buff, 0) != MF_CLASSIC_CMD_VALUE_TRANSFER) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionDataDec)) { + break; + } + + mf_classic_value_to_block( + instance->transfer_value, block_num, &instance->data->block[block_num]); + instance->transfer_value = 0; + + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = { + mf_classic_listener_halt_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = { + mf_classic_listener_auth_key_a_handler, + mf_classic_listener_auth_second_part_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = { + mf_classic_listener_auth_key_b_handler, + mf_classic_listener_auth_second_part_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = { + mf_classic_listener_read_block_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = { + mf_classic_listener_write_block_first_part_handler, + mf_classic_listener_write_block_second_part_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = { + mf_classic_listener_value_dec_handler, + mf_classic_listener_value_data_receive_handler, + mf_classic_listener_value_transfer_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = { + mf_classic_listener_value_inc_handler, + mf_classic_listener_value_data_receive_handler, + mf_classic_listener_value_transfer_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = { + mf_classic_listener_value_restore_handler, + mf_classic_listener_value_data_receive_handler, + mf_classic_listener_value_transfer_handler, +}; + +static const MfClassicListenerCmd mf_classic_listener_cmd_handlers[] = { + { + .cmd_start_byte = MF_CLASSIC_CMD_HALT_MSB, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_halt_handlers), + .handler = mf_classic_listener_halt_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_A, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_auth_key_a_handlers), + .handler = mf_classic_listener_auth_key_a_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_B, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_auth_key_b_handlers), + .handler = mf_classic_listener_auth_key_b_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_READ_BLOCK, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_read_block_handlers), + .handler = mf_classic_listener_read_block_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_WRITE_BLOCK, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_write_block_handlers), + .handler = mf_classic_listener_write_block_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_VALUE_DEC, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_value_dec_handlers), + .handler = mf_classic_listener_value_dec_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_VALUE_INC, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_value_inc_handlers), + .handler = mf_classic_listener_value_inc_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_VALUE_RESTORE, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_value_restore_handlers), + .handler = mf_classic_listener_value_restore_handlers, + }, +}; + +static void mf_classic_listener_send_short_frame(MfClassicListener* instance, uint8_t data) { + BitBuffer* tx_buffer = instance->tx_plain_buffer; + + bit_buffer_set_size(instance->tx_plain_buffer, 4); + bit_buffer_set_byte(instance->tx_plain_buffer, 0, data); + if(instance->comm_state == MfClassicListenerCommStateEncrypted) { + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + tx_buffer = instance->tx_encrypted_buffer; + } + + iso14443_3a_listener_tx_with_custom_parity(instance->iso14443_3a_listener, tx_buffer); +} + +NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + NfcCommand command = NfcCommandContinue; + MfClassicListener* instance = context; + Iso14443_3aListenerEvent* iso3_event = event.event_data; + BitBuffer* rx_buffer_plain; + + if(iso3_event->type == Iso14443_3aListenerEventTypeFieldOff) { + mf_classic_listener_reset_state(instance); + command = NfcCommandSleep; + } else if( + (iso3_event->type == Iso14443_3aListenerEventTypeReceivedData) || + (iso3_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame)) { + if(instance->comm_state == MfClassicListenerCommStateEncrypted) { + if(instance->state == MfClassicListenerStateAuthComplete) { + crypto1_decrypt( + instance->crypto, iso3_event->data->buffer, instance->rx_plain_buffer); + rx_buffer_plain = instance->rx_plain_buffer; + if(iso14443_crc_check(Iso14443CrcTypeA, rx_buffer_plain)) { + iso14443_crc_trim(rx_buffer_plain); + } + } else { + rx_buffer_plain = iso3_event->data->buffer; + } + } else { + rx_buffer_plain = iso3_event->data->buffer; + } + + MfClassicListenerCommand mfc_command = MfClassicListenerCommandNack; + if(instance->cmd_in_progress) { + mfc_command = + mf_classic_listener_cmd_handlers[instance->current_cmd_idx] + .handler[instance->current_cmd_handler_idx](instance, rx_buffer_plain); + } else { + for(size_t i = 0; i < COUNT_OF(mf_classic_listener_cmd_handlers); i++) { + if(bit_buffer_get_size(rx_buffer_plain) != + mf_classic_listener_cmd_handlers[i].cmd_len_bits) { + continue; + } + if(bit_buffer_get_byte(rx_buffer_plain, 0) != + mf_classic_listener_cmd_handlers[i].cmd_start_byte) { + continue; + } + instance->current_cmd_idx = i; + instance->current_cmd_handler_idx = 0; + mfc_command = + mf_classic_listener_cmd_handlers[i].handler[0](instance, rx_buffer_plain); + break; + } + } + + if(mfc_command == MfClassicListenerCommandAck) { + mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_ACK); + } else if(mfc_command == MfClassicListenerCommandNack) { + mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_NACK); + } else if(mfc_command == MfClassicListenerCommandSilent) { + command = NfcCommandReset; + } else if(mfc_command == MfClassicListenerCommandSleep) { + command = NfcCommandSleep; + } + } else if(iso3_event->type == Iso14443_3aListenerEventTypeHalted) { + mf_classic_listener_reset_state(instance); + } + + return command; +} + +MfClassicListener* + mf_classic_listener_alloc(Iso14443_3aListener* iso14443_3a_listener, MfClassicData* data) { + MfClassicListener* instance = malloc(sizeof(MfClassicListener)); + instance->iso14443_3a_listener = iso14443_3a_listener; + instance->data = data; + mf_classic_listener_prepare_emulation(instance); + + instance->crypto = crypto1_alloc(); + instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + + instance->mfc_event.data = &instance->mfc_event_data; + instance->generic_event.protocol = NfcProtocolMfClassic; + instance->generic_event.event_data = &instance->mfc_event; + instance->generic_event.instance = instance; + + return instance; +} + +void mf_classic_listener_free(MfClassicListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->crypto); + furi_assert(instance->rx_plain_buffer); + furi_assert(instance->tx_encrypted_buffer); + furi_assert(instance->tx_plain_buffer); + + crypto1_free(instance->crypto); + bit_buffer_free(instance->rx_plain_buffer); + bit_buffer_free(instance->tx_encrypted_buffer); + bit_buffer_free(instance->tx_plain_buffer); + + free(instance); +} + +void mf_classic_listener_set_callback( + MfClassicListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +const MfClassicData* mf_classic_listener_get_data(const MfClassicListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +const NfcListenerBase mf_classic_listener = { + .alloc = (NfcListenerAlloc)mf_classic_listener_alloc, + .free = (NfcListenerFree)mf_classic_listener_free, + .set_callback = (NfcListenerSetCallback)mf_classic_listener_set_callback, + .get_data = (NfcListenerGetData)mf_classic_listener_get_data, + .run = (NfcListenerRun)mf_classic_listener_run, +}; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.h b/lib/nfc/protocols/mf_classic/mf_classic_listener.h new file mode 100644 index 0000000000..5169de62d8 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.h @@ -0,0 +1,27 @@ +#pragma once + +#include "mf_classic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfClassicListener MfClassicListener; + +typedef enum { + MfClassicListenerEventTypeAuthContextPartCollected, + MfClassicListenerEventTypeAuthContextFullCollected, +} MfClassicListenerEventType; + +typedef union { + MfClassicAuthContext auth_context; +} MfClassicListenerEventData; + +typedef struct { + MfClassicListenerEventType type; + MfClassicListenerEventData* data; +} MfClassicListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h new file mode 100644 index 0000000000..6c7a1dd6a7 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase mf_classic_listener; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h new file mode 100644 index 0000000000..4b040bec12 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -0,0 +1,62 @@ +#pragma once + +#include "mf_classic_listener.h" +#include +#include +#include "crypto1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfClassicListenerCommandProcessed, + MfClassicListenerCommandAck, + MfClassicListenerCommandNack, + MfClassicListenerCommandSilent, + MfClassicListenerCommandSleep, +} MfClassicListenerCommand; + +typedef enum { + MfClassicListenerStateIdle, + MfClassicListenerStateAuthComplete, +} MfClassicListenerState; + +typedef enum { + MfClassicListenerCommStatePlain, + MfClassicListenerCommStateEncrypted, +} MfClassicListenerCommState; + +struct MfClassicListener { + Iso14443_3aListener* iso14443_3a_listener; + MfClassicListenerState state; + MfClassicListenerCommState comm_state; + + MfClassicData* data; + BitBuffer* tx_plain_buffer; + BitBuffer* tx_encrypted_buffer; + BitBuffer* rx_plain_buffer; + + Crypto1* crypto; + MfClassicAuthContext auth_context; + + // Value operation data + int32_t transfer_value; + MfClassicValueCommand value_cmd; + + NfcGenericEvent generic_event; + MfClassicListenerEvent mfc_event; + MfClassicListenerEventData mfc_event_data; + NfcGenericCallback callback; + void* context; + + bool cmd_in_progress; + size_t current_cmd_idx; + size_t current_cmd_handler_idx; + + size_t total_block_num; +}; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c new file mode 100644 index 0000000000..3eba6ee569 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -0,0 +1,951 @@ +#include "mf_classic_poller_i.h" + +#include + +#include + +#define TAG "MfClassicPoller" + +#define MF_CLASSIC_MAX_BUFF_SIZE (64) + +typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance); + +MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { + furi_assert(iso14443_3a_poller); + + MfClassicPoller* instance = malloc(sizeof(MfClassicPoller)); + instance->iso14443_3a_poller = iso14443_3a_poller; + instance->data = mf_classic_alloc(); + instance->crypto = crypto1_alloc(); + instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->rx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->current_type_check = MfClassicType4k; + + instance->mfc_event.data = &instance->mfc_event_data; + + instance->general_event.protocol = NfcProtocolMfClassic; + instance->general_event.event_data = &instance->mfc_event; + instance->general_event.instance = instance; + + return instance; +} + +void mf_classic_poller_free(MfClassicPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->crypto); + furi_assert(instance->tx_plain_buffer); + furi_assert(instance->rx_plain_buffer); + furi_assert(instance->tx_encrypted_buffer); + furi_assert(instance->rx_encrypted_buffer); + + mf_classic_free(instance->data); + crypto1_free(instance->crypto); + bit_buffer_free(instance->tx_plain_buffer); + bit_buffer_free(instance->rx_plain_buffer); + bit_buffer_free(instance->tx_encrypted_buffer); + bit_buffer_free(instance->rx_encrypted_buffer); + + free(instance); +} + +static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance) { + MfClassicPollerEventDataUpdate* data_update = &instance->mfc_event_data.data_update; + + mf_classic_get_read_sectors_and_keys( + instance->data, &data_update->sectors_read, &data_update->keys_found); + data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector; + instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; + return instance->callback(instance->general_event, instance->context); +} + +static void mf_classic_poller_check_key_b_is_readable( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data) { + do { + if(!mf_classic_is_sector_trailer(block_num)) break; + if(!mf_classic_is_allowed_access( + instance->data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBRead)) + break; + + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data; + uint64_t key_b = nfc_util_bytes2num(sec_tr->key_b.data, sizeof(MfClassicKey)); + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + mf_classic_set_key_found(instance->data, sector_num, MfClassicKeyTypeB, key_b); + } while(false); +} + +NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + + if(instance->current_type_check == MfClassicType4k) { + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + MfClassicError error = mf_classic_async_get_nt(instance, 254, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + instance->data->type = MfClassicType4k; + instance->state = MfClassicPollerStateStart; + instance->current_type_check = MfClassicType4k; + FURI_LOG_D(TAG, "4K detected"); + } else { + instance->current_type_check = MfClassicType1k; + } + } else if(instance->current_type_check == MfClassicType1k) { + MfClassicError error = mf_classic_async_get_nt(instance, 62, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + instance->data->type = MfClassicType1k; + FURI_LOG_D(TAG, "1K detected"); + } else { + instance->data->type = MfClassicTypeMini; + FURI_LOG_D(TAG, "Mini detected"); + } + instance->current_type_check = MfClassicType4k; + instance->state = MfClassicPollerStateStart; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->sectors_total = mf_classic_get_total_sectors_num(instance->data->type); + memset(&instance->mode_ctx, 0, sizeof(MfClassicPollerModeContext)); + + instance->mfc_event.type = MfClassicPollerEventTypeRequestMode; + command = instance->callback(instance->general_event, instance->context); + + if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) { + mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); + instance->state = MfClassicPollerStateRequestKey; + } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { + instance->state = MfClassicPollerStateRequestReadSector; + } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) { + instance->state = MfClassicPollerStateRequestSectorTrailer; + } else { + furi_crash("Invalid mode selected"); + } + + return command; +} + +NfcCommand mf_classic_poller_handler_request_sector_trailer(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + + if(write_ctx->current_sector == instance->sectors_total) { + instance->state = MfClassicPollerStateSuccess; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeRequestSectorTrailer; + instance->mfc_event_data.sec_tr_data.sector_num = write_ctx->current_sector; + command = instance->callback(instance->general_event, instance->context); + if(instance->mfc_event_data.sec_tr_data.sector_trailer_provided) { + instance->state = MfClassicPollerStateCheckWriteConditions; + memcpy( + &write_ctx->sec_tr, + &instance->mfc_event_data.sec_tr_data.sector_trailer, + sizeof(MfClassicSectorTrailer)); + write_ctx->current_block = + MAX(1, mf_classic_get_first_block_num_of_sector(write_ctx->current_sector)); + + } else { + write_ctx->current_sector++; + } + } + + return command; +} + +NfcCommand mf_classic_handler_check_write_conditions(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + MfClassicSectorTrailer* sec_tr = &write_ctx->sec_tr; + + do { + // Check last block in sector to write + uint8_t sec_tr_block_num = + mf_classic_get_sector_trailer_num_by_sector(write_ctx->current_sector); + if(write_ctx->current_block == sec_tr_block_num) { + write_ctx->current_sector++; + instance->state = MfClassicPollerStateRequestSectorTrailer; + break; + } + + // Check write and read access + if(mf_classic_is_allowed_access_data_block( + sec_tr, write_ctx->current_block, MfClassicKeyTypeA, MfClassicActionDataWrite)) { + write_ctx->key_type_write = MfClassicKeyTypeA; + } else if(mf_classic_is_allowed_access_data_block( + sec_tr, + write_ctx->current_block, + MfClassicKeyTypeB, + MfClassicActionDataWrite)) { + write_ctx->key_type_write = MfClassicKeyTypeB; + } else if(mf_classic_is_value_block(sec_tr, write_ctx->current_block)) { + write_ctx->is_value_block = true; + } else { + FURI_LOG_D(TAG, "Not allowed to write block %d", write_ctx->current_block); + write_ctx->current_block++; + break; + } + + if(mf_classic_is_allowed_access_data_block( + sec_tr, + write_ctx->current_block, + write_ctx->key_type_write, + MfClassicActionDataRead)) { + write_ctx->key_type_read = write_ctx->key_type_write; + } else { + write_ctx->key_type_read = write_ctx->key_type_write == MfClassicKeyTypeA ? + MfClassicKeyTypeB : + MfClassicKeyTypeA; + if(!mf_classic_is_allowed_access_data_block( + sec_tr, + write_ctx->current_block, + write_ctx->key_type_read, + MfClassicActionDataRead)) { + FURI_LOG_D(TAG, "Not allowed to read block %d", write_ctx->current_block); + write_ctx->current_block++; + break; + } + } + + write_ctx->need_halt_before_write = + (write_ctx->key_type_read != write_ctx->key_type_write); + instance->state = MfClassicPollerStateReadBlock; + } while(false); + + return command; +} + +NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + + MfClassicKey* auth_key = write_ctx->key_type_read == MfClassicKeyTypeA ? + &write_ctx->sec_tr.key_a : + &write_ctx->sec_tr.key_b; + MfClassicError error = MfClassicErrorNone; + + do { + // Authenticate to sector + error = mf_classic_async_auth( + instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + + // Read block from tag + error = + mf_classic_async_read_block(instance, write_ctx->current_block, &write_ctx->tag_block); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %d", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + + if(write_ctx->is_value_block) { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateWriteValueBlock; + } else { + if(write_ctx->need_halt_before_write) { + mf_classic_async_halt(instance); + } + instance->state = MfClassicPollerStateWriteBlock; + } + } while(false); + + return command; +} + +NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + MfClassicKey* auth_key = write_ctx->key_type_write == MfClassicKeyTypeA ? + &write_ctx->sec_tr.key_a : + &write_ctx->sec_tr.key_b; + MfClassicError error = MfClassicErrorNone; + + do { + // Request block to write + instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock; + instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfc_event_data.write_block_data.write_block_provided) break; + + // Compare tag and saved block + if(memcmp( + write_ctx->tag_block.data, + instance->mfc_event_data.write_block_data.write_block.data, + sizeof(MfClassicBlock)) == 0) { + FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block); + break; + } + + // Reauth if necessary + if(write_ctx->need_halt_before_write) { + error = mf_classic_async_auth( + instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to auth to block %d for writing", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + } + + // Write block + error = mf_classic_async_write_block( + instance, + write_ctx->current_block, + &instance->mfc_event_data.write_block_data.write_block); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + + } while(false); + + mf_classic_async_halt(instance); + write_ctx->current_block++; + instance->state = MfClassicPollerStateCheckWriteConditions; + + return command; +} + +NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + + do { + // Request block to write + instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock; + instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfc_event_data.write_block_data.write_block_provided) break; + + // Compare tag and saved block + if(memcmp( + write_ctx->tag_block.data, + instance->mfc_event_data.write_block_data.write_block.data, + sizeof(MfClassicBlock)) == 0) { + FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block); + break; + } + + bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeA, + MfClassicActionDataInc); + bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeB, + MfClassicActionDataInc); + bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeA, + MfClassicActionDataDec); + bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeB, + MfClassicActionDataDec); + + int32_t source_value = 0; + int32_t target_value = 0; + if(!mf_classic_block_to_value( + &instance->mfc_event_data.write_block_data.write_block, &source_value, NULL)) + break; + if(!mf_classic_block_to_value(&write_ctx->tag_block, &target_value, NULL)) break; + + MfClassicKeyType auth_key_type = MfClassicKeyTypeA; + MfClassicValueCommand value_cmd = MfClassicValueCommandIncrement; + int32_t diff = source_value - target_value; + if(diff > 0) { + if(key_a_inc_allowed) { + auth_key_type = MfClassicKeyTypeA; + value_cmd = MfClassicValueCommandIncrement; + } else if(key_b_inc_allowed) { + auth_key_type = MfClassicKeyTypeB; + value_cmd = MfClassicValueCommandIncrement; + } else { + FURI_LOG_D(TAG, "Unable to increment value block"); + break; + } + } else { + if(key_a_dec_allowed) { + auth_key_type = MfClassicKeyTypeA; + value_cmd = MfClassicValueCommandDecrement; + diff *= -1; + } else if(key_b_dec_allowed) { + auth_key_type = MfClassicKeyTypeB; + value_cmd = MfClassicValueCommandDecrement; + diff *= -1; + } else { + FURI_LOG_D(TAG, "Unable to decrement value block"); + break; + } + } + + MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a : + &write_ctx->sec_tr.key_b; + + MfClassicError error = + mf_classic_async_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_cmd(instance, write_ctx->current_block, value_cmd, diff); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_transfer(instance, write_ctx->current_block); + if(error != MfClassicErrorNone) break; + + } while(false); + + mf_classic_async_halt(instance); + write_ctx->is_value_block = false; + write_ctx->current_block++; + instance->state = MfClassicPollerStateCheckWriteConditions; + + return command; +} + +NfcCommand mf_classic_poller_handler_request_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx; + MfClassicPollerEventDataReadSectorRequest* sec_read = + &instance->mfc_event_data.read_sector_request_data; + instance->mfc_event.type = MfClassicPollerEventTypeRequestReadSector; + command = instance->callback(instance->general_event, instance->context); + + if(!sec_read->key_provided) { + instance->state = MfClassicPollerStateSuccess; + } else { + sec_read_ctx->current_sector = sec_read->sector_num; + sec_read_ctx->key = sec_read->key; + sec_read_ctx->key_type = sec_read->key_type; + sec_read_ctx->current_block = + mf_classic_get_first_block_num_of_sector(sec_read->sector_num); + sec_read_ctx->auth_passed = false; + instance->state = MfClassicPollerStateReadSectorBlocks; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx; + + do { + MfClassicError error = MfClassicErrorNone; + + if(!sec_read_ctx->auth_passed) { + uint64_t key = nfc_util_bytes2num(sec_read_ctx->key.data, sizeof(MfClassicKey)); + FURI_LOG_D( + TAG, + "Auth to block %d with key %c: %06llx", + sec_read_ctx->current_block, + sec_read_ctx->key_type == MfClassicKeyTypeA ? 'A' : 'B', + key); + error = mf_classic_async_auth( + instance, + sec_read_ctx->current_block, + &sec_read_ctx->key, + sec_read_ctx->key_type, + NULL); + if(error != MfClassicErrorNone) break; + + sec_read_ctx->auth_passed = true; + if(!mf_classic_is_key_found( + instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type)) { + mf_classic_set_key_found( + instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type, key); + } + } + if(mf_classic_is_block_read(instance->data, sec_read_ctx->current_block)) break; + + FURI_LOG_D(TAG, "Reading block %d", sec_read_ctx->current_block); + MfClassicBlock read_block = {}; + error = mf_classic_async_read_block(instance, sec_read_ctx->current_block, &read_block); + if(error == MfClassicErrorNone) { + mf_classic_set_block_read(instance->data, sec_read_ctx->current_block, &read_block); + if(sec_read_ctx->key_type == MfClassicKeyTypeA) { + mf_classic_poller_check_key_b_is_readable( + instance, sec_read_ctx->current_block, &read_block); + } + } else { + mf_classic_async_halt(instance); + sec_read_ctx->auth_passed = false; + } + } while(false); + + uint8_t sec_tr_num = mf_classic_get_sector_trailer_num_by_sector(sec_read_ctx->current_sector); + sec_read_ctx->current_block++; + if(sec_read_ctx->current_block > sec_tr_num) { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateRequestReadSector; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + instance->mfc_event.type = MfClassicPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(instance->mfc_event_data.key_request_data.key_provided) { + dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key; + instance->state = MfClassicPollerStateAuthKeyA; + } else { + instance->state = MfClassicPollerStateNextSector; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) { + instance->state = MfClassicPollerStateAuthKeyB; + } else { + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key A found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + dict_attack_ctx->current_block = block; + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateReadSector; + } else { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateAuthKeyB; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) { + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) { + instance->state = MfClassicPollerStateNextSector; + } else { + instance->state = MfClassicPollerStateRequestKey; + } + } else { + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key B found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + dict_attack_ctx->current_block = block; + + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateReadSector; + } else { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateRequestKey; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_next_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + dict_attack_ctx->current_sector++; + if(dict_attack_ctx->current_sector == instance->sectors_total) { + instance->state = MfClassicPollerStateSuccess; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeNextSector; + instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateRequestKey; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + MfClassicError error = MfClassicErrorNone; + uint8_t block_num = dict_attack_ctx->current_block; + MfClassicBlock block = {}; + + do { + if(mf_classic_is_block_read(instance->data, block_num)) break; + + if(!dict_attack_ctx->auth_passed) { + error = mf_classic_async_auth( + instance, + block_num, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + NULL); + if(error != MfClassicErrorNone) { + instance->state = MfClassicPollerStateNextSector; + FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector"); + break; + } + } + + FURI_LOG_D(TAG, "Reading block %d", block_num); + error = mf_classic_async_read_block(instance, block_num, &block); + + if(error != MfClassicErrorNone) { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + FURI_LOG_D(TAG, "Failed to read block %d", block_num); + } else { + mf_classic_set_block_read(instance->data, block_num, &block); + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + mf_classic_poller_check_key_b_is_readable(instance, block_num, &block); + } + } + } while(false); + + uint8_t sec_tr_block_num = + mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->current_sector); + dict_attack_ctx->current_block++; + if(dict_attack_ctx->current_block > sec_tr_block_num) { + mf_classic_poller_handle_data_update(instance); + + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + + if(dict_attack_ctx->current_sector == instance->sectors_total) { + instance->state = MfClassicPollerStateNextSector; + } else { + dict_attack_ctx->reuse_key_sector = dict_attack_ctx->current_sector; + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = + dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateKeyReuseStart; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; + } else { + dict_attack_ctx->reuse_key_sector++; + if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateRequestKey; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = + dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); + + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA)) { + instance->state = MfClassicPollerStateKeyReuseStart; + } else { + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key A found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + dict_attack_ctx->current_block = block; + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateKeyReuseReadSector; + } else { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStart; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB)) { + instance->state = MfClassicPollerStateKeyReuseStart; + } else { + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key B found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + dict_attack_ctx->current_block = block; + + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateKeyReuseReadSector; + } else { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStart; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + MfClassicError error = MfClassicErrorNone; + uint8_t block_num = dict_attack_ctx->current_block; + MfClassicBlock block = {}; + + do { + if(mf_classic_is_block_read(instance->data, block_num)) break; + + if(!dict_attack_ctx->auth_passed) { + error = mf_classic_async_auth( + instance, + block_num, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + NULL); + if(error != MfClassicErrorNone) { + instance->state = MfClassicPollerStateKeyReuseStart; + break; + } + } + + FURI_LOG_D(TAG, "Reading block %d", block_num); + error = mf_classic_async_read_block(instance, block_num, &block); + + if(error != MfClassicErrorNone) { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + FURI_LOG_D(TAG, "Failed to read block %d", block_num); + } else { + mf_classic_set_block_read(instance->data, block_num, &block); + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + mf_classic_poller_check_key_b_is_readable(instance, block_num, &block); + } + } + } while(false); + + uint16_t sec_tr_block_num = + mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->reuse_key_sector); + dict_attack_ctx->current_block++; + if(dict_attack_ctx->current_block > sec_tr_block_num) { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + + mf_classic_poller_handle_data_update(instance); + instance->state = MfClassicPollerStateKeyReuseStart; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->mfc_event.type = MfClassicPollerEventTypeSuccess; + command = instance->callback(instance->general_event, instance->context); + + return command; +} + +NfcCommand mf_classic_poller_handler_fail(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->mfc_event.type = MfClassicPollerEventTypeFail; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateDetectType; + + return command; +} + +static const MfClassicPollerReadHandler + mf_classic_poller_dict_attack_handler[MfClassicPollerStateNum] = { + [MfClassicPollerStateDetectType] = mf_classic_poller_handler_detect_type, + [MfClassicPollerStateStart] = mf_classic_poller_handler_start, + [MfClassicPollerStateRequestSectorTrailer] = + mf_classic_poller_handler_request_sector_trailer, + [MfClassicPollerStateCheckWriteConditions] = mf_classic_handler_check_write_conditions, + [MfClassicPollerStateReadBlock] = mf_classic_poller_handler_read_block, + [MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block, + [MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block, + [MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector, + [MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key, + [MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector, + [MfClassicPollerStateReadSectorBlocks] = + mf_classic_poller_handler_request_read_sector_blocks, + [MfClassicPollerStateAuthKeyA] = mf_classic_poller_handler_auth_a, + [MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b, + [MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector, + [MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start, + [MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a, + [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, + [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, + [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, + [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, +}; + +NfcCommand mf_classic_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(context); + + MfClassicPoller* instance = context; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + if(instance->card_state == MfClassicCardStateLost) { + instance->card_state = MfClassicCardStateDetected; + instance->mfc_event.type = MfClassicPollerEventTypeCardDetected; + instance->callback(instance->general_event, instance->context); + } + command = mf_classic_poller_dict_attack_handler[instance->state](instance); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + if(instance->card_state == MfClassicCardStateDetected) { + instance->card_state = MfClassicCardStateLost; + instance->mfc_event.type = MfClassicPollerEventTypeCardLost; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +bool mf_classic_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(context); + + Iso14443_3aPoller* iso3_poller = event.instance; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + bool detected = false; + const uint8_t auth_cmd[] = {MF_CLASSIC_CMD_AUTH_KEY_A, 0}; + BitBuffer* tx_buffer = bit_buffer_alloc(COUNT_OF(auth_cmd)); + bit_buffer_copy_bytes(tx_buffer, auth_cmd, COUNT_OF(auth_cmd)); + BitBuffer* rx_buffer = bit_buffer_alloc(sizeof(MfClassicNt)); + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + Iso14443_3aError error = iso14443_3a_poller_send_standard_frame( + iso3_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC); + if(error == Iso14443_3aErrorWrongCrc) { + if(bit_buffer_get_size_bytes(rx_buffer) == sizeof(MfClassicNt)) { + detected = true; + } + } + } + + bit_buffer_free(tx_buffer); + bit_buffer_free(rx_buffer); + + return detected; +} + +void mf_classic_poller_set_callback( + MfClassicPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +const MfClassicData* mf_classic_poller_get_data(const MfClassicPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +const NfcPollerBase mf_classic_poller = { + .alloc = (NfcPollerAlloc)mf_classic_poller_alloc, + .free = (NfcPollerFree)mf_classic_poller_free, + .set_callback = (NfcPollerSetCallback)mf_classic_poller_set_callback, + .run = (NfcPollerRun)mf_classic_poller_run, + .detect = (NfcPollerDetect)mf_classic_poller_detect, + .get_data = (NfcPollerGetData)mf_classic_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h new file mode 100644 index 0000000000..da1f3c3dce --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -0,0 +1,109 @@ +#pragma once + +#include "mf_classic.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfClassicPoller MfClassicPoller; + +typedef enum { + // Start event + MfClassicPollerEventTypeRequestMode, + + // Read with key cache events + MfClassicPollerEventTypeRequestReadSector, + + // Write events + MfClassicPollerEventTypeRequestSectorTrailer, + MfClassicPollerEventTypeRequestWriteBlock, + + // Dictionary attack events + MfClassicPollerEventTypeRequestKey, + MfClassicPollerEventTypeNextSector, + MfClassicPollerEventTypeDataUpdate, + MfClassicPollerEventTypeFoundKeyA, + MfClassicPollerEventTypeFoundKeyB, + MfClassicPollerEventTypeCardNotDetected, + MfClassicPollerEventTypeKeyAttackStart, + MfClassicPollerEventTypeKeyAttackStop, + MfClassicPollerEventTypeKeyAttackNextSector, + + // Common events + MfClassicPollerEventTypeCardDetected, + MfClassicPollerEventTypeCardLost, + MfClassicPollerEventTypeSuccess, + MfClassicPollerEventTypeFail, +} MfClassicPollerEventType; + +typedef enum { + MfClassicPollerModeRead, + MfClassicPollerModeWrite, + MfClassicPollerModeDictAttack, +} MfClassicPollerMode; + +typedef struct { + MfClassicPollerMode mode; + const MfClassicData* data; +} MfClassicPollerEventDataRequestMode; + +typedef struct { + uint8_t current_sector; +} MfClassicPollerEventDataDictAttackNextSector; + +typedef struct { + uint8_t sectors_read; + uint8_t keys_found; + uint8_t current_sector; +} MfClassicPollerEventDataUpdate; + +typedef struct { + MfClassicKey key; + bool key_provided; +} MfClassicPollerEventDataKeyRequest; + +typedef struct { + uint8_t sector_num; + MfClassicKey key; + MfClassicKeyType key_type; + bool key_provided; +} MfClassicPollerEventDataReadSectorRequest; + +typedef struct { + uint8_t sector_num; + MfClassicBlock sector_trailer; + bool sector_trailer_provided; +} MfClassicPollerEventDataSectorTrailerRequest; + +typedef struct { + uint8_t block_num; + MfClassicBlock write_block; + bool write_block_provided; +} MfClassicPollerEventDataWriteBlockRequest; + +typedef struct { + uint8_t current_sector; +} MfClassicPollerEventKeyAttackData; + +typedef union { + MfClassicError error; + MfClassicPollerEventDataRequestMode poller_mode; + MfClassicPollerEventDataDictAttackNextSector next_sector_data; + MfClassicPollerEventDataKeyRequest key_request_data; + MfClassicPollerEventDataUpdate data_update; + MfClassicPollerEventDataReadSectorRequest read_sector_request_data; + MfClassicPollerEventKeyAttackData key_attack_data; + MfClassicPollerEventDataSectorTrailerRequest sec_tr_data; + MfClassicPollerEventDataWriteBlockRequest write_block_data; +} MfClassicPollerEventData; + +typedef struct { + MfClassicPollerEventType type; + MfClassicPollerEventData* data; +} MfClassicPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h new file mode 100644 index 0000000000..d0c0b18fcf --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase mf_classic_poller; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c new file mode 100644 index 0000000000..7eab4fe3b5 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -0,0 +1,386 @@ +#include "mf_classic_poller_i.h" + +#include +#include + +#include + +#define TAG "MfCLassicPoller" + +MfClassicError mf_classic_process_error(Iso14443_3aError error) { + MfClassicError ret = MfClassicErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = MfClassicErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = MfClassicErrorNotPresent; + break; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + ret = MfClassicErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = MfClassicErrorTimeout; + break; + default: + ret = MfClassicErrorProtocol; + break; + } + + return ret; +} + +MfClassicError mf_classic_async_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_plain_buffer, + instance->rx_plain_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { + ret = MfClassicErrorProtocol; + break; + } + + if(nt) { + bit_buffer_write_bytes(instance->rx_plain_buffer, nt->data, sizeof(MfClassicNt)); + } + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + + MfClassicNt nt = {}; + ret = mf_classic_async_get_nt(instance, block_num, key_type, &nt); + if(ret != MfClassicErrorNone) break; + if(data) { + data->nt = nt; + } + + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey)); + MfClassicNr nr = {}; + furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr)); + + crypto1_encrypt_reader_nonce( + instance->crypto, key_num, cuid, nt.data, nr.data, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorAuth; + } + + crypto1_word(instance->crypto, 0, 0); + instance->auth_state = MfClassicAuthStatePassed; + + if(data) { + data->nr = nr; + const uint8_t* nr_ar = bit_buffer_get_data(instance->tx_encrypted_buffer); + memcpy(data->ar.data, &nr_ar[4], sizeof(MfClassicAr)); + bit_buffer_write_bytes( + instance->rx_encrypted_buffer, data->at.data, sizeof(MfClassicAt)); + } + } while(false); + + if(ret != MfClassicErrorNone) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + + return ret; +} + +MfClassicError mf_classic_async_halt(MfClassicPoller* instance) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t halt_cmd[2] = {MF_CLASSIC_CMD_HALT_MSB, MF_CLASSIC_CMD_HALT_LSB}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, halt_cmd, sizeof(halt_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorTimeout) { + ret = mf_classic_process_error(error); + break; + } + instance->auth_state = MfClassicAuthStateIdle; + instance->iso14443_3a_poller->state = Iso14443_3aPollerStateIdle; + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_read_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t read_block_cmd[2] = {MF_CLASSIC_CMD_READ_BLOCK, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, read_block_cmd, sizeof(read_block_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != + (sizeof(MfClassicBlock) + 2)) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_plain_buffer)) { + FURI_LOG_D(TAG, "CRC error"); + ret = MfClassicErrorProtocol; + break; + } + + iso14443_crc_trim(instance->rx_plain_buffer); + bit_buffer_write_bytes(instance->rx_plain_buffer, data->data, sizeof(MfClassicBlock)); + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_write_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t write_block_cmd[2] = {MF_CLASSIC_CMD_WRITE_BLOCK, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, write_block_cmd, sizeof(write_block_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + + bit_buffer_copy_bytes(instance->tx_plain_buffer, data->data, sizeof(MfClassicBlock)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_value_cmd( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicValueCommand cmd, + int32_t data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t cmd_value = 0; + if(cmd == MfClassicValueCommandDecrement) { + cmd_value = MF_CLASSIC_CMD_VALUE_DEC; + } else if(cmd == MfClassicValueCommandIncrement) { + cmd_value = MF_CLASSIC_CMD_VALUE_INC; + } else { + cmd_value = MF_CLASSIC_CMD_VALUE_RESTORE; + } + uint8_t value_cmd[2] = {cmd_value, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, value_cmd, sizeof(value_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + + bit_buffer_copy_bytes(instance->tx_plain_buffer, (uint8_t*)&data, sizeof(data)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + + // Command processed if tag doesn't respond + if(error != Iso14443_3aErrorTimeout) { + ret = MfClassicErrorProtocol; + break; + } + ret = MfClassicErrorNone; + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t transfer_cmd[2] = {MF_CLASSIC_CMD_VALUE_TRANSFER, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, transfer_cmd, sizeof(transfer_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h new file mode 100644 index 0000000000..c6f4ccf7f0 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -0,0 +1,205 @@ +#pragma once + +#include "mf_classic_poller.h" +#include +#include +#include "crypto1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_CLASSIC_FWT_FC (60000) + +typedef enum { + MfClassicAuthStateIdle, + MfClassicAuthStatePassed, +} MfClassicAuthState; + +typedef enum { + MfClassicCardStateDetected, + MfClassicCardStateLost, +} MfClassicCardState; + +typedef enum { + MfClassicPollerStateDetectType, + MfClassicPollerStateStart, + + // Write states + MfClassicPollerStateRequestSectorTrailer, + MfClassicPollerStateCheckWriteConditions, + MfClassicPollerStateReadBlock, + MfClassicPollerStateWriteBlock, + MfClassicPollerStateWriteValueBlock, + + // Read states + MfClassicPollerStateRequestReadSector, + MfClassicPollerStateReadSectorBlocks, + + // Dict attack states + MfClassicPollerStateNextSector, + MfClassicPollerStateRequestKey, + MfClassicPollerStateReadSector, + MfClassicPollerStateAuthKeyA, + MfClassicPollerStateAuthKeyB, + MfClassicPollerStateKeyReuseStart, + MfClassicPollerStateKeyReuseAuthKeyA, + MfClassicPollerStateKeyReuseAuthKeyB, + MfClassicPollerStateKeyReuseReadSector, + MfClassicPollerStateSuccess, + MfClassicPollerStateFail, + + MfClassicPollerStateNum, +} MfClassicPollerState; + +typedef struct { + uint8_t current_sector; + MfClassicSectorTrailer sec_tr; + uint16_t current_block; + bool is_value_block; + MfClassicKeyType key_type_read; + MfClassicKeyType key_type_write; + bool need_halt_before_write; + MfClassicBlock tag_block; +} MfClassicPollerWriteContext; + +typedef struct { + uint8_t current_sector; + MfClassicKey current_key; + MfClassicKeyType current_key_type; + bool auth_passed; + uint16_t current_block; + uint8_t reuse_key_sector; +} MfClassicPollerDictAttackContext; + +typedef struct { + uint8_t current_sector; + uint16_t current_block; + MfClassicKeyType key_type; + MfClassicKey key; + bool auth_passed; +} MfClassicPollerReadContext; + +typedef union { + MfClassicPollerWriteContext write_ctx; + MfClassicPollerDictAttackContext dict_attack_ctx; + MfClassicPollerReadContext read_ctx; + +} MfClassicPollerModeContext; + +struct MfClassicPoller { + Iso14443_3aPoller* iso14443_3a_poller; + + MfClassicPollerState state; + MfClassicAuthState auth_state; + MfClassicCardState card_state; + + MfClassicType current_type_check; + uint8_t sectors_total; + MfClassicPollerModeContext mode_ctx; + + Crypto1* crypto; + BitBuffer* tx_plain_buffer; + BitBuffer* tx_encrypted_buffer; + BitBuffer* rx_plain_buffer; + BitBuffer* rx_encrypted_buffer; + MfClassicData* data; + + NfcGenericEvent general_event; + MfClassicPollerEvent mfc_event; + MfClassicPollerEventData mfc_event_data; + NfcGenericCallback callback; + void* context; +}; + +typedef struct { + uint8_t block; + MfClassicKeyType key_type; + MfClassicNt nt; +} MfClassicCollectNtContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} MfClassicReadBlockContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} MfClassicWriteBlockContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + int32_t value; +} MfClassicReadValueContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicValueCommand value_cmd; + int32_t data; + int32_t new_value; +} MfClassicChangeValueContext; + +typedef struct { + MfClassicDeviceKeys keys; + uint8_t current_sector; +} MfClassicReadContext; + +typedef union { + MfClassicCollectNtContext collect_nt_context; + MfClassicAuthContext auth_context; + MfClassicReadBlockContext read_block_context; + MfClassicWriteBlockContext write_block_context; + MfClassicReadValueContext read_value_context; + MfClassicChangeValueContext change_value_context; + MfClassicReadContext read_context; +} MfClassicPollerContextData; + +MfClassicError mf_classic_process_error(Iso14443_3aError error); + +MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller); + +void mf_classic_poller_free(MfClassicPoller* instance); + +MfClassicError mf_classic_async_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + +MfClassicError mf_classic_async_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +MfClassicError mf_classic_async_halt(MfClassicPoller* instance); + +MfClassicError + mf_classic_async_read_block(MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data); + +MfClassicError mf_classic_async_write_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data); + +MfClassicError mf_classic_async_value_cmd( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicValueCommand cmd, + int32_t data); + +MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c new file mode 100644 index 0000000000..8b9fb69f16 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c @@ -0,0 +1,524 @@ +#include "mf_classic_poller_i.h" + +#include + +#include + +#define TAG "MfClassicPoller" + +#define MF_CLASSIC_POLLER_COMPLETE_EVENT (1UL << 0) + +typedef enum { + MfClassicPollerCmdTypeCollectNt, + MfClassicPollerCmdTypeAuth, + MfClassicPollerCmdTypeReadBlock, + MfClassicPollerCmdTypeWriteBlock, + MfClassicPollerCmdTypeReadValue, + MfClassicPollerCmdTypeChangeValue, + + MfClassicPollerCmdTypeNum, +} MfClassicPollerCmdType; + +typedef struct { + MfClassicPollerCmdType cmd_type; + FuriThreadId thread_id; + MfClassicError error; + MfClassicPollerContextData data; +} MfClassicPollerContext; + +typedef MfClassicError ( + *MfClassicPollerCmdHandler)(MfClassicPoller* poller, MfClassicPollerContextData* data); + +static MfClassicError mf_classic_poller_collect_nt_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + return mf_classic_async_get_nt( + poller, + data->collect_nt_context.block, + data->collect_nt_context.key_type, + &data->collect_nt_context.nt); +} + +static MfClassicError + mf_classic_poller_auth_handler(MfClassicPoller* poller, MfClassicPollerContextData* data) { + return mf_classic_async_auth( + poller, + data->auth_context.block_num, + &data->auth_context.key, + data->auth_context.key_type, + &data->auth_context); +} + +static MfClassicError mf_classic_poller_read_block_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->read_block_context.block_num, + &data->read_block_context.key, + data->read_block_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_read_block( + poller, data->read_block_context.block_num, &data->read_block_context.block); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + } while(false); + + return error; +} + +static MfClassicError mf_classic_poller_write_block_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->read_block_context.block_num, + &data->read_block_context.key, + data->read_block_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_write_block( + poller, data->write_block_context.block_num, &data->write_block_context.block); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + } while(false); + + return error; +} + +static MfClassicError mf_classic_poller_read_value_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->read_value_context.block_num, + &data->read_value_context.key, + data->read_value_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + MfClassicBlock block = {}; + error = mf_classic_async_read_block(poller, data->read_value_context.block_num, &block); + if(error != MfClassicErrorNone) break; + + if(!mf_classic_block_to_value(&block, &data->read_value_context.value, NULL)) { + error = MfClassicErrorProtocol; + break; + } + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + } while(false); + + return error; +} + +static MfClassicError mf_classic_poller_change_value_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->change_value_context.block_num, + &data->change_value_context.key, + data->change_value_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_cmd( + poller, + data->change_value_context.block_num, + data->change_value_context.value_cmd, + data->change_value_context.data); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_transfer(poller, data->change_value_context.block_num); + if(error != MfClassicErrorNone) break; + + MfClassicBlock block = {}; + error = mf_classic_async_read_block(poller, data->change_value_context.block_num, &block); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + if(!mf_classic_block_to_value(&block, &data->change_value_context.new_value, NULL)) { + error = MfClassicErrorProtocol; + break; + } + + } while(false); + + return error; +} + +static const MfClassicPollerCmdHandler mf_classic_poller_cmd_handlers[MfClassicPollerCmdTypeNum] = { + [MfClassicPollerCmdTypeCollectNt] = mf_classic_poller_collect_nt_handler, + [MfClassicPollerCmdTypeAuth] = mf_classic_poller_auth_handler, + [MfClassicPollerCmdTypeReadBlock] = mf_classic_poller_read_block_handler, + [MfClassicPollerCmdTypeWriteBlock] = mf_classic_poller_write_block_handler, + [MfClassicPollerCmdTypeReadValue] = mf_classic_poller_read_value_handler, + [MfClassicPollerCmdTypeChangeValue] = mf_classic_poller_change_value_handler, +}; + +static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* context) { + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(context); + + MfClassicPollerContext* poller_context = context; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + Iso14443_3aPoller* iso14443_3a_poller = event.instance; + MfClassicPoller* mfc_poller = mf_classic_poller_alloc(iso14443_3a_poller); + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + poller_context->error = mf_classic_poller_cmd_handlers[poller_context->cmd_type]( + mfc_poller, &poller_context->data); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + poller_context->error = mf_classic_process_error(iso14443_3a_event->data->error); + } + + furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT); + + mf_classic_poller_free(mfc_poller); + + return NfcCommandStop; +} + +static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerContext* poller_ctx) { + furi_assert(poller_ctx->cmd_type < MfClassicPollerCmdTypeNum); + + poller_ctx->thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + nfc_poller_start(poller, mf_classic_poller_cmd_callback, poller_ctx); + furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_ctx->error; +} + +MfClassicError mf_classic_poller_collect_nt( + Nfc* nfc, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + furi_assert(nfc); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeCollectNt, + .data.collect_nt_context.block = block_num, + .data.collect_nt_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + if(nt) { + *nt = poller_context.data.collect_nt_context.nt; + } + } + + return error; +} + +MfClassicError mf_classic_poller_auth( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + furi_assert(nfc); + furi_assert(key); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeAuth, + .data.auth_context.block_num = block_num, + .data.auth_context.key = *key, + .data.auth_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + if(data) { + *data = poller_context.data.auth_context; + } + } + + return error; +} + +MfClassicError mf_classic_poller_read_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data) { + furi_assert(nfc); + furi_assert(key); + furi_assert(data); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeReadBlock, + .data.read_block_context.block_num = block_num, + .data.read_block_context.key = *key, + .data.read_block_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + *data = poller_context.data.read_block_context.block; + } + + return error; +} + +MfClassicError mf_classic_poller_write_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data) { + furi_assert(nfc); + furi_assert(key); + furi_assert(data); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeWriteBlock, + .data.write_block_context.block_num = block_num, + .data.write_block_context.key = *key, + .data.write_block_context.key_type = key_type, + .data.write_block_context.block = *data, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + return error; +} + +MfClassicError mf_classic_poller_read_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t* value) { + furi_assert(nfc); + furi_assert(key); + furi_assert(value); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeReadValue, + .data.write_block_context.block_num = block_num, + .data.write_block_context.key = *key, + .data.write_block_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + *value = poller_context.data.read_value_context.value; + } + + return error; +} + +MfClassicError mf_classic_poller_change_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t data, + int32_t* new_value) { + furi_assert(nfc); + furi_assert(key); + furi_assert(new_value); + + MfClassicValueCommand command = MfClassicValueCommandRestore; + int32_t command_data = 0; + if(data > 0) { + command = MfClassicValueCommandIncrement; + command_data = data; + } else if(data < 0) { + command = MfClassicValueCommandDecrement; + command_data = -data; + } + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeChangeValue, + .data.change_value_context.block_num = block_num, + .data.change_value_context.key = *key, + .data.change_value_context.key_type = key_type, + .data.change_value_context.value_cmd = command, + .data.change_value_context.data = command_data, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + *new_value = poller_context.data.change_value_context.new_value; + } + + return error; +} + +static bool mf_classic_poller_read_get_next_key( + MfClassicReadContext* read_ctx, + uint8_t* sector_num, + MfClassicKey* key, + MfClassicKeyType* key_type) { + bool next_key_found = false; + + for(uint8_t i = read_ctx->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) { + if(FURI_BIT(read_ctx->keys.key_a_mask, i)) { + FURI_BIT_CLEAR(read_ctx->keys.key_a_mask, i); + *key = read_ctx->keys.key_a[i]; + *key_type = MfClassicKeyTypeA; + *sector_num = i; + + next_key_found = true; + break; + } + if(FURI_BIT(read_ctx->keys.key_b_mask, i)) { + FURI_BIT_CLEAR(read_ctx->keys.key_b_mask, i); + *key = read_ctx->keys.key_b[i]; + *key_type = MfClassicKeyTypeB; + *sector_num = i; + + next_key_found = true; + read_ctx->current_sector = i; + break; + } + } + + return next_key_found; +} + +NfcCommand mf_classic_poller_read_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + MfClassicPollerContext* poller_context = context; + MfClassicPollerEvent* mfc_event = event.event_data; + + if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + poller_context->error = MfClassicErrorNotPresent; + command = NfcCommandStop; + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeRead; + } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) { + MfClassicPollerEventDataReadSectorRequest* req_data = + &mfc_event->data->read_sector_request_data; + MfClassicKey key = {}; + MfClassicKeyType key_type = MfClassicKeyTypeA; + uint8_t sector_num = 0; + if(mf_classic_poller_read_get_next_key( + &poller_context->data.read_context, §or_num, &key, &key_type)) { + req_data->sector_num = sector_num; + req_data->key = key; + req_data->key_type = key_type; + req_data->key_provided = true; + } else { + req_data->key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT); + } + + return command; +} + +MfClassicError + mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) { + furi_assert(nfc); + furi_assert(keys); + furi_assert(data); + + MfClassicError error = MfClassicErrorNone; + MfClassicPollerContext poller_context = {}; + poller_context.thread_id = furi_thread_get_current_id(); + poller_context.data.read_context.keys = *keys; + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfClassic); + nfc_poller_start(poller, mf_classic_poller_read_callback, &poller_context); + furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + + if(poller_context.error != MfClassicErrorNone) { + error = poller_context.error; + } else { + const MfClassicData* mfc_data = nfc_poller_get_data(poller); + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + + mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found); + if((sectors_read > 0) || (keys_found > 0)) { + mf_classic_copy(data, mfc_data); + } else { + error = MfClassicErrorNotPresent; + } + } + + nfc_poller_free(poller); + + return error; +} + +MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) { + furi_assert(nfc); + furi_assert(type); + + MfClassicError error = MfClassicErrorNone; + + const uint8_t mf_classic_verify_block[MfClassicTypeNum] = { + [MfClassicTypeMini] = 0, + [MfClassicType1k] = 62, + [MfClassicType4k] = 254, + }; + + size_t i = 0; + for(i = 0; i < COUNT_OF(mf_classic_verify_block); i++) { + error = mf_classic_poller_collect_nt( + nfc, mf_classic_verify_block[MfClassicTypeNum - i - 1], MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + *type = MfClassicTypeNum - i - 1; + break; + } + } + + return error; +} diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h new file mode 100644 index 0000000000..11db291b80 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h @@ -0,0 +1,59 @@ +#pragma once + +#include "mf_classic.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +MfClassicError mf_classic_poller_collect_nt( + Nfc* nfc, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + +MfClassicError mf_classic_poller_auth( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +MfClassicError mf_classic_poller_read_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data); + +MfClassicError mf_classic_poller_write_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data); + +MfClassicError mf_classic_poller_read_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t* value); + +MfClassicError mf_classic_poller_change_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t data, + int32_t* new_value); + +MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type); + +MfClassicError + mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.c b/lib/nfc/protocols/mf_desfire/mf_desfire.c new file mode 100644 index 0000000000..b83198a25e --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.c @@ -0,0 +1,290 @@ +#include "mf_desfire_i.h" + +#include + +#define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire" + +const NfcDeviceBase nfc_device_mf_desfire = { + .protocol_name = MF_DESFIRE_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_desfire_alloc, + .free = (NfcDeviceFree)mf_desfire_free, + .reset = (NfcDeviceReset)mf_desfire_reset, + .copy = (NfcDeviceCopy)mf_desfire_copy, + .verify = (NfcDeviceVerify)mf_desfire_verify, + .load = (NfcDeviceLoad)mf_desfire_load, + .save = (NfcDeviceSave)mf_desfire_save, + .is_equal = (NfcDeviceEqual)mf_desfire_is_equal, + .get_name = (NfcDeviceGetName)mf_desfire_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_desfire_get_uid, + .set_uid = (NfcDeviceSetUid)mf_desfire_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_desfire_get_base_data, +}; + +MfDesfireData* mf_desfire_alloc() { + MfDesfireData* data = malloc(sizeof(MfDesfireData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config); + data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config); + data->applications = simple_array_alloc(&mf_desfire_application_array_config); + + return data; +} + +void mf_desfire_free(MfDesfireData* data) { + furi_assert(data); + + mf_desfire_reset(data); + simple_array_free(data->applications); + simple_array_free(data->application_ids); + simple_array_free(data->master_key_versions); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void mf_desfire_reset(MfDesfireData* data) { + furi_assert(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->version, 0, sizeof(MfDesfireVersion)); + memset(&data->free_memory, 0, sizeof(MfDesfireFreeMemory)); + + simple_array_reset(data->master_key_versions); + simple_array_reset(data->application_ids); + simple_array_reset(data->applications); +} + +void mf_desfire_copy(MfDesfireData* data, const MfDesfireData* other) { + furi_assert(data); + furi_assert(other); + + mf_desfire_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->version = other->version; + data->free_memory = other->free_memory; + data->master_key_settings = other->master_key_settings; + + simple_array_copy(data->master_key_versions, other->master_key_versions); + simple_array_copy(data->application_ids, other->application_ids); + simple_array_copy(data->applications, other->applications); +} + +bool mf_desfire_verify(MfDesfireData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, MF_DESFIRE_PROTOCOL_NAME); +} + +bool mf_desfire_load(MfDesfireData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!mf_desfire_version_load(&data->version, ff)) break; + if(!mf_desfire_free_memory_load(&data->free_memory, ff)) break; + if(!mf_desfire_key_settings_load( + &data->master_key_settings, MF_DESFIRE_FFF_PICC_PREFIX, ff)) + break; + + const uint32_t master_key_version_count = data->master_key_settings.max_keys; + simple_array_init(data->master_key_versions, master_key_version_count); + + uint32_t i; + for(i = 0; i < master_key_version_count; ++i) { + if(!mf_desfire_key_version_load( + simple_array_get(data->master_key_versions, i), + MF_DESFIRE_FFF_PICC_PREFIX, + i, + ff)) + break; + } + + if(i != master_key_version_count) break; + + uint32_t application_count; + if(!mf_desfire_application_count_load(&application_count, ff)) break; + + if(application_count > 0) { + simple_array_init(data->application_ids, application_count); + if(!mf_desfire_application_ids_load( + simple_array_get_data(data->application_ids), application_count, ff)) + break; + + simple_array_init(data->applications, application_count); + for(i = 0; i < application_count; ++i) { + const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); + furi_string_printf( + prefix, + "%s %02x%02x%02x", + MF_DESFIRE_FFF_APP_PREFIX, + app_id->data[0], + app_id->data[1], + app_id->data[2]); + + if(!mf_desfire_application_load( + simple_array_get(data->applications, i), furi_string_get_cstr(prefix), ff)) + break; + } + + if(i != application_count) break; + } + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool mf_desfire_save(const MfDesfireData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, MF_DESFIRE_PROTOCOL_NAME " specific data")) + break; + if(!mf_desfire_version_save(&data->version, ff)) break; + if(!mf_desfire_free_memory_save(&data->free_memory, ff)) break; + if(!mf_desfire_key_settings_save( + &data->master_key_settings, MF_DESFIRE_FFF_PICC_PREFIX, ff)) + break; + + const uint32_t master_key_version_count = + simple_array_get_count(data->master_key_versions); + + uint32_t i; + for(i = 0; i < master_key_version_count; ++i) { + if(!mf_desfire_key_version_save( + simple_array_cget(data->master_key_versions, i), + MF_DESFIRE_FFF_PICC_PREFIX, + i, + ff)) + break; + } + + if(i != master_key_version_count) break; + + const uint32_t application_count = simple_array_get_count(data->application_ids); + if(!mf_desfire_application_count_save(&application_count, ff)) break; + + if(application_count > 0) { + if(!mf_desfire_application_ids_save( + simple_array_cget_data(data->application_ids), application_count, ff)) + break; + + for(i = 0; i < application_count; ++i) { + const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); + furi_string_printf( + prefix, + "%s %02x%02x%02x", + MF_DESFIRE_FFF_APP_PREFIX, + app_id->data[0], + app_id->data[1], + app_id->data[2]); + + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + if(!mf_desfire_application_save(app, furi_string_get_cstr(prefix), ff)) break; + } + + if(i != application_count) break; + } + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other) { + furi_assert(data); + furi_assert(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + memcmp(&data->version, &other->version, sizeof(MfDesfireVersion)) == 0 && + memcmp(&data->free_memory, &other->free_memory, sizeof(MfDesfireFreeMemory)) == 0 && + memcmp( + &data->master_key_settings, + &other->master_key_settings, + sizeof(MfDesfireKeySettings)) == 0 && + simple_array_is_equal(data->master_key_versions, other->master_key_versions) && + simple_array_is_equal(data->application_ids, other->application_ids) && + simple_array_is_equal(data->applications, other->applications); +} + +const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return MF_DESFIRE_PROTOCOL_NAME; +} + +const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool mf_desfire_set_uid(MfDesfireData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* mf_desfire_get_base_data(const MfDesfireData* data) { + furi_assert(data); + + return data->iso14443_4a_data; +} + +const MfDesfireApplication* + mf_desfire_get_application(const MfDesfireData* data, const MfDesfireApplicationId* app_id) { + MfDesfireApplication* app = NULL; + + for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { + const MfDesfireApplicationId* current_app_id = simple_array_cget(data->application_ids, i); + if(memcmp(app_id, current_app_id, sizeof(MfDesfireApplicationId)) == 0) { + app = simple_array_get(data->applications, i); + } + } + + return app; +} + +const MfDesfireFileSettings* + mf_desfire_get_file_settings(const MfDesfireApplication* data, const MfDesfireFileId* file_id) { + MfDesfireFileSettings* file_settings = NULL; + + for(uint32_t i = 0; i < simple_array_get_count(data->file_ids); ++i) { + const MfDesfireFileId* current_file_id = simple_array_cget(data->file_ids, i); + if(*file_id == *current_file_id) { + file_settings = simple_array_get(data->file_settings, i); + } + } + + return file_settings; +} + +const MfDesfireFileData* + mf_desfire_get_file_data(const MfDesfireApplication* data, const MfDesfireFileId* file_id) { + MfDesfireFileData* file_data = NULL; + + for(uint32_t i = 0; i < simple_array_get_count(data->file_ids); ++i) { + const MfDesfireFileId* current_file_id = simple_array_cget(data->file_ids, i); + if(*file_id == *current_file_id) { + file_data = simple_array_get(data->file_data, i); + } + } + + return file_data; +} diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h new file mode 100644 index 0000000000..4d09dc8510 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -0,0 +1,191 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_DESFIRE_CMD_GET_VERSION (0x60) +#define MF_DESFIRE_CMD_GET_FREE_MEMORY (0x6E) +#define MF_DESFIRE_CMD_GET_KEY_SETTINGS (0x45) +#define MF_DESFIRE_CMD_GET_KEY_VERSION (0x64) +#define MF_DESFIRE_CMD_GET_APPLICATION_IDS (0x6A) +#define MF_DESFIRE_CMD_SELECT_APPLICATION (0x5A) +#define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F) +#define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5) + +#define MF_DESFIRE_CMD_READ_DATA (0xBD) +#define MF_DESFIRE_CMD_GET_VALUE (0x6C) +#define MF_DESFIRE_CMD_READ_RECORDS (0xBB) + +#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF) + +#define MF_DESFIRE_MAX_KEYS (14) +#define MF_DESFIRE_MAX_FILES (32) + +#define MF_DESFIRE_UID_SIZE (7) +#define MF_DESFIRE_BATCH_SIZE (5) +#define MF_DESFIRE_APP_ID_SIZE (3) + +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[MF_DESFIRE_UID_SIZE]; + uint8_t batch[MF_DESFIRE_BATCH_SIZE]; + uint8_t prod_week; + uint8_t prod_year; +} MfDesfireVersion; + +typedef struct { + uint32_t bytes_free; + bool is_present; +} MfDesfireFreeMemory; // EV1+ only + +typedef struct { + bool is_master_key_changeable; + bool is_free_directory_list; + bool is_free_create_delete; + bool is_config_changeable; + uint8_t change_key_id; + uint8_t max_keys; + uint8_t flags; +} MfDesfireKeySettings; + +typedef uint8_t MfDesfireKeyVersion; + +typedef struct { + MfDesfireKeySettings key_settings; + SimpleArray* key_versions; +} MfDesfireKeyConfiguration; + +typedef enum { + MfDesfireFileTypeStandard = 0, + MfDesfireFileTypeBackup = 1, + MfDesfireFileTypeValue = 2, + MfDesfireFileTypeLinearRecord = 3, + MfDesfireFileTypeCyclicRecord = 4, +} MfDesfireFileType; + +typedef enum { + MfDesfireFileCommunicationSettingsPlaintext = 0, + MfDesfireFileCommunicationSettingsAuthenticated = 1, + MfDesfireFileCommunicationSettingsEnciphered = 3, +} MfDesfireFileCommunicationSettings; + +typedef uint8_t MfDesfireFileId; +typedef uint16_t MfDesfireFileAccessRights; + +typedef struct { + MfDesfireFileType type; + MfDesfireFileCommunicationSettings comm; + MfDesfireFileAccessRights access_rights; + union { + struct { + uint32_t size; + } data; + struct { + uint32_t lo_limit; + uint32_t hi_limit; + uint32_t limited_credit_value; + bool limited_credit_enabled; + } value; + struct { + uint32_t size; + uint32_t max; + uint32_t cur; + } record; + }; +} MfDesfireFileSettings; + +typedef struct { + SimpleArray* data; +} MfDesfireFileData; + +typedef struct { + uint8_t data[MF_DESFIRE_APP_ID_SIZE]; +} MfDesfireApplicationId; + +typedef struct MfDesfireApplication { + MfDesfireKeySettings key_settings; + SimpleArray* key_versions; + SimpleArray* file_ids; + SimpleArray* file_settings; + SimpleArray* file_data; +} MfDesfireApplication; + +typedef enum { + MfDesfireErrorNone, + MfDesfireErrorNotPresent, + MfDesfireErrorProtocol, + MfDesfireErrorTimeout, +} MfDesfireError; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + MfDesfireVersion version; + MfDesfireFreeMemory free_memory; + MfDesfireKeySettings master_key_settings; + SimpleArray* master_key_versions; + SimpleArray* application_ids; + SimpleArray* applications; +} MfDesfireData; + +extern const NfcDeviceBase nfc_device_mf_desfire; + +// Virtual methods + +MfDesfireData* mf_desfire_alloc(); + +void mf_desfire_free(MfDesfireData* data); + +void mf_desfire_reset(MfDesfireData* data); + +void mf_desfire_copy(MfDesfireData* data, const MfDesfireData* other); + +bool mf_desfire_verify(MfDesfireData* data, const FuriString* device_type); + +bool mf_desfire_load(MfDesfireData* data, FlipperFormat* ff, uint32_t version); + +bool mf_desfire_save(const MfDesfireData* data, FlipperFormat* ff); + +bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other); + +const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len); + +bool mf_desfire_set_uid(MfDesfireData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* mf_desfire_get_base_data(const MfDesfireData* data); + +// Getters and tests + +const MfDesfireApplication* + mf_desfire_get_application(const MfDesfireData* data, const MfDesfireApplicationId* app_id); + +const MfDesfireFileSettings* + mf_desfire_get_file_settings(const MfDesfireApplication* data, const MfDesfireFileId* file_id); + +const MfDesfireFileData* + mf_desfire_get_file_data(const MfDesfireApplication* data, const MfDesfireFileId* file_id); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c new file mode 100644 index 0000000000..30313ae2bc --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -0,0 +1,790 @@ +#include "mf_desfire_i.h" + +#define BITS_IN_BYTE (8U) + +#define MF_DESFIRE_FFF_VERSION_KEY \ + MF_DESFIRE_FFF_PICC_PREFIX " " \ + "Version" +#define MF_DESFIRE_FFF_FREE_MEM_KEY \ + MF_DESFIRE_FFF_PICC_PREFIX " " \ + "Free Memory" + +#define MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY "Change Key ID" +#define MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY "Config Changeable" +#define MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY "Free Create Delete" +#define MF_DESFIRE_FFF_FREE_DIR_LIST_KEY "Free Directory List" +#define MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY "Key Changeable" +#define MF_DESFIRE_FFF_FLAGS_KEY "Flags" +#define MF_DESFIRE_FFF_MAX_KEYS_KEY "Max Keys" + +#define MF_DESFIRE_FFF_KEY_SUB_PREFIX "Key" +#define MF_DESFIRE_FFF_KEY_VERSION_KEY "Version" + +#define MF_DESFIRE_FFF_APPLICATION_COUNT_KEY \ + MF_DESFIRE_FFF_APP_PREFIX " " \ + "Count" +#define MF_DESFIRE_FFF_APPLICATION_IDS_KEY \ + MF_DESFIRE_FFF_APP_PREFIX " " \ + "IDs" + +#define MF_DESFIRE_FFF_FILE_SUB_PREFIX "File" +#define MF_DESFIRE_FFF_FILE_IDS_KEY \ + MF_DESFIRE_FFF_FILE_SUB_PREFIX " " \ + "IDs" +#define MF_DESFIRE_FFF_FILE_TYPE_KEY "Type" +#define MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY "Communication Settings" +#define MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY "Access Rights" + +#define MF_DESFIRE_FFF_FILE_SIZE_KEY "Size" + +#define MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY "Hi Limit" +#define MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY "Lo Limit" +#define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY "Limited Credit Value" +#define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY "Limited Credit Enabled" + +#define MF_DESFIRE_FFF_FILE_MAX_KEY "Max" +#define MF_DESFIRE_FFF_FILE_CUR_KEY "Cur" + +bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireVersion); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion)); + } + + return can_parse; +} + +bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) { + typedef struct __attribute__((packed)) { + uint32_t bytes_free : 3 * BITS_IN_BYTE; + } MfDesfireFreeMemoryLayout; + + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireFreeMemoryLayout); + + if(can_parse) { + MfDesfireFreeMemoryLayout layout; + bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFreeMemoryLayout)); + data->bytes_free = layout.bytes_free; + } + + data->is_present = can_parse; + + return can_parse; +} + +bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { + typedef struct __attribute__((packed)) { + bool is_master_key_changeable : 1; + bool is_free_directory_list : 1; + bool is_free_create_delete : 1; + bool is_config_changeable : 1; + uint8_t change_key_id : 4; + uint8_t max_keys : 4; + uint8_t flags : 4; + } MfDesfireKeySettingsLayout; + + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout); + + if(can_parse) { + MfDesfireKeySettingsLayout layout; + bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireKeySettingsLayout)); + + data->is_master_key_changeable = layout.is_master_key_changeable; + data->is_free_directory_list = layout.is_free_directory_list; + data->is_free_create_delete = layout.is_free_create_delete; + data->is_config_changeable = layout.is_config_changeable; + + data->change_key_id = layout.change_key_id; + data->max_keys = layout.max_keys; + data->flags = layout.flags; + } + + return can_parse; +} + +bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfDesfireKeyVersion)); + } + + return can_parse; +} + +bool mf_desfire_application_id_parse( + MfDesfireApplicationId* data, + uint32_t index, + const BitBuffer* buf) { + const bool can_parse = + bit_buffer_get_size_bytes(buf) >= + (index * sizeof(MfDesfireApplicationId) + sizeof(MfDesfireApplicationId)); + + if(can_parse) { + bit_buffer_write_bytes_mid( + buf, data, index * sizeof(MfDesfireApplicationId), sizeof(MfDesfireApplicationId)); + } + + return can_parse; +} + +bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) >= + (index * sizeof(MfDesfireFileId) + sizeof(MfDesfireFileId)); + if(can_parse) { + bit_buffer_write_bytes_mid( + buf, data, index * sizeof(MfDesfireFileId), sizeof(MfDesfireFileId)); + } + + return can_parse; +} + +bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf) { + bool parsed = false; + + typedef struct __attribute__((packed)) { + uint8_t type; + uint8_t comm; + uint16_t access_rights; + } MfDesfireFileSettingsHeader; + + typedef struct __attribute__((packed)) { + uint32_t size : 3 * BITS_IN_BYTE; + } MfDesfireFileSettingsData; + + typedef struct __attribute__((packed)) { + uint32_t lo_limit; + uint32_t hi_limit; + uint32_t limited_credit_value; + uint8_t limited_credit_enabled; + } MfDesfireFileSettingsValue; + + typedef struct __attribute__((packed)) { + uint32_t size : 3 * BITS_IN_BYTE; + uint32_t max : 3 * BITS_IN_BYTE; + uint32_t cur : 3 * BITS_IN_BYTE; + } MfDesfireFileSettingsRecord; + + typedef struct __attribute__((packed)) { + MfDesfireFileSettingsHeader header; + union { + MfDesfireFileSettingsData data; + MfDesfireFileSettingsValue value; + MfDesfireFileSettingsRecord record; + }; + } MfDesfireFileSettingsLayout; + + do { + const size_t data_size = bit_buffer_get_size_bytes(buf); + const size_t min_data_size = + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); + + if(data_size < min_data_size) break; + + MfDesfireFileSettingsLayout layout; + bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); + + data->type = layout.header.type; + data->comm = layout.header.comm; + data->access_rights = layout.header.access_rights; + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + if(data_size != min_data_size) break; + + data->data.size = layout.data.size; + + } else if(data->type == MfDesfireFileTypeValue) { + if(data_size != + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) + break; + + data->value.lo_limit = layout.value.lo_limit; + data->value.hi_limit = layout.value.hi_limit; + data->value.limited_credit_value = layout.value.hi_limit; + data->value.limited_credit_enabled = layout.value.limited_credit_enabled; + + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + if(data_size != + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) + break; + + data->record.size = layout.record.size; + data->record.max = layout.record.max; + data->record.cur = layout.record.cur; + + } else { + break; + } + + parsed = true; + } while(false); + + return parsed; +} + +bool mf_desfire_file_data_parse(MfDesfireFileData* data, const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + + if(data_size > 0) { + simple_array_init(data->data, data_size); + bit_buffer_write_bytes(buf, simple_array_get_data(data->data), data_size); + } + + // Success no matter whether there is data or not + return true; +} + +void mf_desfire_file_data_init(MfDesfireFileData* data) { + data->data = simple_array_alloc(&simple_array_config_uint8_t); +} + +void mf_desfire_application_init(MfDesfireApplication* data) { + data->key_versions = simple_array_alloc(&mf_desfire_key_version_array_config); + data->file_ids = simple_array_alloc(&mf_desfire_file_id_array_config); + data->file_settings = simple_array_alloc(&mf_desfire_file_settings_array_config); + data->file_data = simple_array_alloc(&mf_desfire_file_data_array_config); +} + +void mf_desfire_file_data_reset(MfDesfireFileData* data) { + simple_array_free(data->data); + memset(data, 0, sizeof(MfDesfireFileData)); +} + +void mf_desfire_application_reset(MfDesfireApplication* data) { + simple_array_free(data->key_versions); + simple_array_free(data->file_ids); + simple_array_free(data->file_settings); + simple_array_free(data->file_data); + memset(data, 0, sizeof(MfDesfireApplication)); +} + +void mf_desfire_file_data_copy(MfDesfireFileData* data, const MfDesfireFileData* other) { + simple_array_copy(data->data, other->data); +} + +void mf_desfire_application_copy(MfDesfireApplication* data, const MfDesfireApplication* other) { + data->key_settings = other->key_settings; + simple_array_copy(data->key_versions, other->key_versions); + simple_array_copy(data->file_ids, other->file_ids); + simple_array_copy(data->file_settings, other->file_settings); + simple_array_copy(data->file_data, other->file_data); +} + +bool mf_desfire_version_load(MfDesfireVersion* data, FlipperFormat* ff) { + return flipper_format_read_hex( + ff, MF_DESFIRE_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfDesfireVersion)); +} + +bool mf_desfire_free_memory_load(MfDesfireFreeMemory* data, FlipperFormat* ff) { + data->is_present = flipper_format_key_exist(ff, MF_DESFIRE_FFF_FREE_MEM_KEY); + return data->is_present ? + flipper_format_read_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) : + true; +} + +bool mf_desfire_key_settings_load( + MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1)) break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY); + if(!flipper_format_read_bool(ff, furi_string_get_cstr(key), &data->is_config_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY); + if(flipper_format_key_exist(ff, furi_string_get_cstr(key))) { + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break; + } + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break; + + success = true; + } while(false); + + furi_string_free(key); + return success; +} + +bool mf_desfire_key_version_load( + MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf( + "%s %s %lu %s", + prefix, + MF_DESFIRE_FFF_KEY_SUB_PREFIX, + index, + MF_DESFIRE_FFF_KEY_VERSION_KEY); + const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, 1); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_count_load(uint32_t* data, const char* prefix, FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY); + const bool success = flipper_format_get_value_count(ff, furi_string_get_cstr(key), data); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_ids_load( + MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY); + const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, count); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_settings_load( + MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->type, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->comm, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(key), + (uint8_t*)&data->access_rights, + sizeof(MfDesfireFileAccessRights))) + break; + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) + break; + + } else if(data->type == MfDesfireFileTypeValue) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.lo_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY); + if(!flipper_format_read_uint32( + ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1)) + break; + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) + break; + } + + success = true; + } while(false); + + furi_string_free(key); + + return success; +} + +bool mf_desfire_file_data_load(MfDesfireFileData* data, const char* prefix, FlipperFormat* ff) { + bool success = false; + do { + if(!flipper_format_key_exist(ff, prefix)) { + success = true; + break; + } + + uint32_t data_size; + if(!flipper_format_get_value_count(ff, prefix, &data_size)) break; + + simple_array_init(data->data, data_size); + + if(!flipper_format_read_hex(ff, prefix, simple_array_get_data(data->data), data_size)) + break; + + success = true; + } while(false); + + return success; +} + +bool mf_desfire_application_count_load(uint32_t* data, FlipperFormat* ff) { + return flipper_format_read_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1); +} + +bool mf_desfire_application_ids_load( + MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff) { + return flipper_format_read_hex( + ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId)); +} + +bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, FlipperFormat* ff) { + FuriString* sub_prefix = furi_string_alloc(); + bool success = false; + + do { + if(!mf_desfire_key_settings_load(&data->key_settings, prefix, ff)) break; + + const uint32_t key_version_count = data->key_settings.max_keys; + simple_array_init(data->key_versions, key_version_count); + + uint32_t i; + for(i = 0; i < key_version_count; ++i) { + if(!mf_desfire_key_version_load(simple_array_get(data->key_versions, i), prefix, i, ff)) + break; + } + + if(i != key_version_count) break; + + uint32_t file_count; + if(!mf_desfire_file_count_load(&file_count, prefix, ff)) break; + + simple_array_init(data->file_ids, file_count); + if(!mf_desfire_file_ids_load(simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + + simple_array_init(data->file_settings, file_count); + simple_array_init(data->file_data, file_count); + + for(i = 0; i < file_count; ++i) { + const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); + furi_string_printf( + sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id); + + MfDesfireFileSettings* file_settings = simple_array_get(data->file_settings, i); + if(!mf_desfire_file_settings_load(file_settings, furi_string_get_cstr(sub_prefix), ff)) + break; + + MfDesfireFileData* file_data = simple_array_get(data->file_data, i); + if(!mf_desfire_file_data_load(file_data, furi_string_get_cstr(sub_prefix), ff)) break; + } + + if(i != file_count) break; + + success = true; + } while(false); + + furi_string_free(sub_prefix); + return success; +} + +bool mf_desfire_version_save(const MfDesfireVersion* data, FlipperFormat* ff) { + return flipper_format_write_hex( + ff, MF_DESFIRE_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfDesfireVersion)); +} + +bool mf_desfire_free_memory_save(const MfDesfireFreeMemory* data, FlipperFormat* ff) { + return data->is_present ? + flipper_format_write_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) : + true; +} + +bool mf_desfire_key_settings_save( + const MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_config_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break; + + success = true; + } while(false); + + furi_string_free(key); + return success; +} + +bool mf_desfire_key_version_save( + const MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf( + "%s %s %lu %s", + prefix, + MF_DESFIRE_FFF_KEY_SUB_PREFIX, + index, + MF_DESFIRE_FFF_KEY_VERSION_KEY); + const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, 1); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_ids_save( + const MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY); + const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, count); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_settings_save( + const MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->type, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->comm, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(key), + (const uint8_t*)&data->access_rights, + sizeof(MfDesfireFileAccessRights))) + break; + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) + break; + + } else if(data->type == MfDesfireFileTypeValue) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->value.lo_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1)) + break; + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) + break; + } + + success = true; + } while(false); + + furi_string_free(key); + return success; +} + +bool mf_desfire_file_data_save( + const MfDesfireFileData* data, + const char* prefix, + FlipperFormat* ff) { + const uint32_t data_size = simple_array_get_count(data->data); + return data_size > 0 ? flipper_format_write_hex( + ff, prefix, simple_array_cget_data(data->data), data_size) : + true; +} + +bool mf_desfire_application_count_save(const uint32_t* data, FlipperFormat* ff) { + return flipper_format_write_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1); +} + +bool mf_desfire_application_ids_save( + const MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff) { + return flipper_format_write_hex( + ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId)); +} + +bool mf_desfire_application_save( + const MfDesfireApplication* data, + const char* prefix, + FlipperFormat* ff) { + FuriString* sub_prefix = furi_string_alloc(); + bool success = false; + + do { + if(!mf_desfire_key_settings_save(&data->key_settings, prefix, ff)) break; + + const uint32_t key_version_count = data->key_settings.max_keys; + + uint32_t i; + for(i = 0; i < key_version_count; ++i) { + if(!mf_desfire_key_version_save( + simple_array_cget(data->key_versions, i), prefix, i, ff)) + break; + } + + if(i != key_version_count) break; + + const uint32_t file_count = simple_array_get_count(data->file_ids); + if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + + for(i = 0; i < file_count; ++i) { + const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); + furi_string_printf( + sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id); + + const MfDesfireFileSettings* file_settings = simple_array_cget(data->file_settings, i); + if(!mf_desfire_file_settings_save(file_settings, furi_string_get_cstr(sub_prefix), ff)) + break; + + const MfDesfireFileData* file_data = simple_array_cget(data->file_data, i); + if(!mf_desfire_file_data_save(file_data, furi_string_get_cstr(sub_prefix), ff)) break; + } + + if(i != file_count) break; + + success = true; + } while(false); + + furi_string_free(sub_prefix); + return success; +} + +const SimpleArrayConfig mf_desfire_key_version_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireKeyVersion), +}; + +const SimpleArrayConfig mf_desfire_app_id_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireApplicationId), +}; + +const SimpleArrayConfig mf_desfire_file_id_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireFileId), +}; + +const SimpleArrayConfig mf_desfire_file_settings_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireFileSettings), +}; + +const SimpleArrayConfig mf_desfire_file_data_array_config = { + .init = (SimpleArrayInit)mf_desfire_file_data_init, + .copy = (SimpleArrayCopy)mf_desfire_file_data_copy, + .reset = (SimpleArrayReset)mf_desfire_file_data_reset, + .type_size = sizeof(MfDesfireData), +}; + +const SimpleArrayConfig mf_desfire_application_array_config = { + .init = (SimpleArrayInit)mf_desfire_application_init, + .copy = (SimpleArrayCopy)mf_desfire_application_copy, + .reset = (SimpleArrayReset)mf_desfire_application_reset, + .type_size = sizeof(MfDesfireApplication), +}; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h new file mode 100644 index 0000000000..05381096d1 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -0,0 +1,140 @@ +#pragma once + +#include "mf_desfire.h" + +#define MF_DESFIRE_FFF_PICC_PREFIX "PICC" +#define MF_DESFIRE_FFF_APP_PREFIX "Application" + +// SimpleArray configurations + +extern const SimpleArrayConfig mf_desfire_key_version_array_config; +extern const SimpleArrayConfig mf_desfire_app_id_array_config; +extern const SimpleArrayConfig mf_desfire_file_id_array_config; +extern const SimpleArrayConfig mf_desfire_file_settings_array_config; +extern const SimpleArrayConfig mf_desfire_file_data_array_config; +extern const SimpleArrayConfig mf_desfire_application_array_config; + +// Parse internal MfDesfire structures + +bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf); + +bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf); + +bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf); + +bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf); + +bool mf_desfire_application_id_parse( + MfDesfireApplicationId* data, + uint32_t index, + const BitBuffer* buf); + +bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBuffer* buf); + +bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf); + +bool mf_desfire_file_data_parse(MfDesfireFileData* data, const BitBuffer* buf); + +// Init internal MfDesfire structures + +void mf_desfire_file_data_init(MfDesfireFileData* data); + +void mf_desfire_application_init(MfDesfireApplication* data); + +// Reset internal MfDesfire structures + +void mf_desfire_file_data_reset(MfDesfireFileData* data); + +void mf_desfire_application_reset(MfDesfireApplication* data); + +// Copy internal MfDesfire structures + +void mf_desfire_file_data_copy(MfDesfireFileData* data, const MfDesfireFileData* other); + +void mf_desfire_application_copy(MfDesfireApplication* data, const MfDesfireApplication* other); + +// Load internal MfDesfire structures + +bool mf_desfire_version_load(MfDesfireVersion* data, FlipperFormat* ff); + +bool mf_desfire_free_memory_load(MfDesfireFreeMemory* data, FlipperFormat* ff); + +bool mf_desfire_key_settings_load( + MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_key_version_load( + MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff); + +bool mf_desfire_file_count_load(uint32_t* data, const char* prefix, FlipperFormat* ff); + +bool mf_desfire_file_ids_load( + MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_settings_load( + MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_data_load(MfDesfireFileData* data, const char* prefix, FlipperFormat* ff); + +bool mf_desfire_application_count_load(uint32_t* data, FlipperFormat* ff); + +bool mf_desfire_application_ids_load( + MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff); + +bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, FlipperFormat* ff); + +// Save internal MFDesfire structures + +bool mf_desfire_version_save(const MfDesfireVersion* data, FlipperFormat* ff); + +bool mf_desfire_free_memory_save(const MfDesfireFreeMemory* data, FlipperFormat* ff); + +bool mf_desfire_key_settings_save( + const MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_key_version_save( + const MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff); + +bool mf_desfire_file_ids_save( + const MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_settings_save( + const MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_data_save( + const MfDesfireFileData* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_application_count_save(const uint32_t* data, FlipperFormat* ff); + +bool mf_desfire_application_ids_save( + const MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff); + +bool mf_desfire_application_save( + const MfDesfireApplication* data, + const char* prefix, + FlipperFormat* ff); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c new file mode 100644 index 0000000000..c74bbc89da --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -0,0 +1,243 @@ +#include "mf_desfire_poller_i.h" + +#include + +#include + +#define TAG "MfDesfirePoller" + +#define MF_DESFIRE_BUF_SIZE_MAX (64U) + +typedef NfcCommand (*MfDesfirePollerReadHandler)(MfDesfirePoller* instance); + +const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static MfDesfirePoller* mf_desfire_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + MfDesfirePoller* instance = malloc(sizeof(MfDesfirePoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = mf_desfire_alloc(); + instance->tx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->rx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->input_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->result_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + + instance->mf_desfire_event.data = &instance->mf_desfire_event_data; + + instance->general_event.protocol = NfcProtocolMfDesfire; + instance->general_event.event_data = &instance->mf_desfire_event; + instance->general_event.instance = instance; + + return instance; +} + +static void mf_desfire_poller_free(MfDesfirePoller* instance) { + furi_assert(instance); + + mf_desfire_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + bit_buffer_free(instance->input_buffer); + bit_buffer_free(instance->result_buffer); + free(instance); +} + +static NfcCommand mf_desfire_poller_handler_idle(MfDesfirePoller* instance) { + bit_buffer_reset(instance->input_buffer); + bit_buffer_reset(instance->result_buffer); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = MfDesfirePollerStateReadVersion; + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instance) { + instance->error = mf_desfire_poller_async_read_version(instance, &instance->data->version); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read version success"); + instance->state = MfDesfirePollerStateReadFreeMemory; + } else { + FURI_LOG_E(TAG, "Failed to read version"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { + instance->error = + mf_desfire_poller_async_read_free_memory(instance, &instance->data->free_memory); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read free memory success"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; + } else { + FURI_LOG_E(TAG, "Failed to read free memory"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { + instance->error = + mf_desfire_poller_async_read_key_settings(instance, &instance->data->master_key_settings); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read master key settings success"); + instance->state = MfDesfirePollerStateReadMasterKeyVersion; + } else { + FURI_LOG_E(TAG, "Failed to read master key settings"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePoller* instance) { + instance->error = mf_desfire_poller_async_read_key_versions( + instance, + instance->data->master_key_versions, + instance->data->master_key_settings.max_keys); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read master key version success"); + instance->state = MfDesfirePollerStateReadApplicationIds; + } else { + FURI_LOG_E(TAG, "Failed to read master key version"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller* instance) { + instance->error = + mf_desfire_poller_async_read_application_ids(instance, instance->data->application_ids); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read application ids success"); + instance->state = MfDesfirePollerStateReadApplications; + } else { + FURI_LOG_E(TAG, "Failed to read application ids"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_applications(MfDesfirePoller* instance) { + instance->error = mf_desfire_poller_async_read_applications( + instance, instance->data->application_ids, instance->data->applications); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read applications success"); + instance->state = MfDesfirePollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read applications"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_fail(MfDesfirePoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mf_desfire_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = MfDesfirePollerStateIdle; + return command; +} + +static NfcCommand mf_desfire_poller_handler_read_success(MfDesfirePoller* instance) { + FURI_LOG_D(TAG, "Read success."); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mf_desfire_event.type = MfDesfirePollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const MfDesfirePollerReadHandler mf_desfire_poller_read_handler[MfDesfirePollerStateNum] = { + [MfDesfirePollerStateIdle] = mf_desfire_poller_handler_idle, + [MfDesfirePollerStateReadVersion] = mf_desfire_poller_handler_read_version, + [MfDesfirePollerStateReadFreeMemory] = mf_desfire_poller_handler_read_free_memory, + [MfDesfirePollerStateReadMasterKeySettings] = + mf_desfire_poller_handler_read_master_key_settings, + [MfDesfirePollerStateReadMasterKeyVersion] = mf_desfire_poller_handler_read_master_key_version, + [MfDesfirePollerStateReadApplicationIds] = mf_desfire_poller_handler_read_application_ids, + [MfDesfirePollerStateReadApplications] = mf_desfire_poller_handler_read_applications, + [MfDesfirePollerStateReadFailed] = mf_desfire_poller_handler_read_fail, + [MfDesfirePollerStateReadSuccess] = mf_desfire_poller_handler_read_success, +}; + +static void mf_desfire_poller_set_callback( + MfDesfirePoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand mf_desfire_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + MfDesfirePoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = mf_desfire_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->mf_desfire_event.type = MfDesfirePollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + MfDesfirePoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + MfDesfireVersion version = {}; + const MfDesfireError error = mf_desfire_poller_async_read_version(instance, &version); + protocol_detected = (error == MfDesfireErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase mf_desfire_poller = { + .alloc = (NfcPollerAlloc)mf_desfire_poller_alloc, + .free = (NfcPollerFree)mf_desfire_poller_free, + .set_callback = (NfcPollerSetCallback)mf_desfire_poller_set_callback, + .run = (NfcPollerRun)mf_desfire_poller_run, + .detect = (NfcPollerDetect)mf_desfire_poller_detect, + .get_data = (NfcPollerGetData)mf_desfire_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h new file mode 100644 index 0000000000..360b0508ff --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -0,0 +1,31 @@ +#pragma once + +#include "mf_desfire.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfDesfirePoller MfDesfirePoller; + +typedef enum { + MfDesfirePollerEventTypeReadSuccess, + MfDesfirePollerEventTypeReadFailed, +} MfDesfirePollerEventType; + +typedef struct { + union { + MfDesfireError error; + }; +} MfDesfirePollerEventData; + +typedef struct { + MfDesfirePollerEventType type; + MfDesfirePollerEventData* data; +} MfDesfirePollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h new file mode 100644 index 0000000000..0c14ceee4d --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase mf_desfire_poller; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c new file mode 100644 index 0000000000..e86cb4c68c --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -0,0 +1,474 @@ +#include "mf_desfire_poller_i.h" + +#include + +#include "mf_desfire_i.h" + +#define TAG "MfDesfirePoller" + +MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return MfDesfireErrorNone; + case Iso14443_4aErrorNotPresent: + return MfDesfireErrorNotPresent; + case Iso14443_4aErrorTimeout: + return MfDesfireErrorTimeout; + default: + return MfDesfireErrorProtocol; + } +} + +MfDesfireError mf_desfire_send_chunks( + MfDesfirePoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_assert(instance); + furi_assert(instance->iso14443_4a_poller); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + MfDesfireError error = MfDesfireErrorNone; + + do { + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + error = mf_desfire_process_error(iso14443_4a_error); + break; + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT); + + if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { + bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } else { + bit_buffer_reset(rx_buffer); + } + + while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) { + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + error = mf_desfire_process_error(iso14443_4a_error); + break; + } + + bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } + } while(false); + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_VERSION); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_version_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FREE_MEMORY); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_key_settings( + MfDesfirePoller* instance, + MfDesfireKeySettings* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_KEY_SETTINGS); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_key_settings_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_key_versions( + MfDesfirePoller* instance, + SimpleArray* data, + uint32_t count) { + furi_assert(instance); + furi_assert(count > 0); + + simple_array_init(data, count); + + bit_buffer_set_size_bytes(instance->input_buffer, sizeof(uint8_t) * 2); + bit_buffer_set_byte(instance->input_buffer, 0, MF_DESFIRE_CMD_GET_KEY_VERSION); + + MfDesfireError error = MfDesfireErrorNone; + + for(uint32_t i = 0; i < count; ++i) { + bit_buffer_set_byte(instance->input_buffer, 1, i); + + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_key_version_parse(simple_array_get(data, i), instance->result_buffer)) { + error = MfDesfireErrorProtocol; + break; + } + } + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_APPLICATION_IDS); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + const uint32_t app_id_count = + bit_buffer_get_size_bytes(instance->result_buffer) / sizeof(MfDesfireApplicationId); + if(app_id_count == 0) break; + + simple_array_init(data, app_id_count); + + for(uint32_t i = 0; i < app_id_count; ++i) { + if(!mf_desfire_application_id_parse( + simple_array_get(data, i), i, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + break; + } + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_select_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_SELECT_APPLICATION); + bit_buffer_append_bytes( + instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); + + MfDesfireError error = + mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FILE_IDS); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + const uint32_t id_count = + bit_buffer_get_size_bytes(instance->result_buffer) / sizeof(MfDesfireFileId); + + if(id_count == 0) break; + simple_array_init(data, id_count); + + for(uint32_t i = 0; i < id_count; ++i) { + if(!mf_desfire_file_id_parse(simple_array_get(data, i), i, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + break; + } + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_settings( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileSettings* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FILE_SETTINGS); + bit_buffer_append_byte(instance->input_buffer, id); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_settings_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_settings_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + SimpleArray* data) { + furi_assert(instance); + + MfDesfireError error = MfDesfireErrorNone; + + const uint32_t file_id_count = simple_array_get_count(file_ids); + if(file_id_count > 0) { + simple_array_init(data, file_id_count); + } + + for(uint32_t i = 0; i < file_id_count; ++i) { + const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); + error = mf_desfire_poller_async_read_file_settings( + instance, file_id, simple_array_get(data, i)); + if(error != MfDesfireErrorNone) break; + } + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); + bit_buffer_append_byte(instance->input_buffer, id); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_value( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_VALUE); + bit_buffer_append_byte(instance->input_buffer, id); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_records( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); + bit_buffer_append_byte(instance->input_buffer, id); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_data_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + const SimpleArray* file_settings, + SimpleArray* data) { + furi_assert(instance); + furi_assert(simple_array_get_count(file_ids) == simple_array_get_count(file_settings)); + + MfDesfireError error = MfDesfireErrorNone; + + const uint32_t file_id_count = simple_array_get_count(file_ids); + if(file_id_count > 0) { + simple_array_init(data, file_id_count); + } + + for(uint32_t i = 0; i < file_id_count; ++i) { + const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); + const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i); + const MfDesfireFileType file_type = file_settings_cur->type; + + MfDesfireFileData* file_data = simple_array_get(data, i); + + if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { + error = mf_desfire_poller_async_read_file_data( + instance, file_id, 0, file_settings_cur->data.size, file_data); + } else if(file_type == MfDesfireFileTypeValue) { + error = mf_desfire_poller_async_read_file_value(instance, file_id, file_data); + } else if( + file_type == MfDesfireFileTypeLinearRecord || + file_type == MfDesfireFileTypeCyclicRecord) { + error = mf_desfire_poller_async_read_file_records( + instance, file_id, 0, file_settings_cur->data.size, file_data); + } + + if(error != MfDesfireErrorNone) break; + } + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_application( + MfDesfirePoller* instance, + MfDesfireApplication* data) { + furi_assert(instance); + furi_assert(data); + + MfDesfireError error; + + do { + error = mf_desfire_poller_async_read_key_settings(instance, &data->key_settings); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_key_versions( + instance, data->key_versions, data->key_settings.max_keys); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_file_ids(instance, data->file_ids); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_file_settings_multi( + instance, data->file_ids, data->file_settings); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_file_data_multi( + instance, data->file_ids, data->file_settings, data->file_data); + if(error != MfDesfireErrorNone) break; + + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_applications( + MfDesfirePoller* instance, + const SimpleArray* app_ids, + SimpleArray* data) { + furi_assert(instance); + + MfDesfireError error = MfDesfireErrorNone; + + const uint32_t app_id_count = simple_array_get_count(app_ids); + if(app_id_count > 0) { + simple_array_init(data, app_id_count); + } + + for(uint32_t i = 0; i < app_id_count; ++i) { + do { + error = mf_desfire_poller_async_select_application( + instance, simple_array_cget(app_ids, i)); + if(error != MfDesfireErrorNone) break; + + MfDesfireApplication* current_app = simple_array_get(data, i); + error = mf_desfire_poller_async_read_application(instance, current_app); + + } while(false); + } + + return error; +} diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h new file mode 100644 index 0000000000..abc48d0eb5 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -0,0 +1,126 @@ +#pragma once + +#include "mf_desfire_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfDesfirePollerStateIdle, + MfDesfirePollerStateReadVersion, + MfDesfirePollerStateReadFreeMemory, + MfDesfirePollerStateReadMasterKeySettings, + MfDesfirePollerStateReadMasterKeyVersion, + MfDesfirePollerStateReadApplicationIds, + MfDesfirePollerStateReadApplications, + MfDesfirePollerStateReadFailed, + MfDesfirePollerStateReadSuccess, + + MfDesfirePollerStateNum, +} MfDesfirePollerState; + +typedef enum { + MfDesfirePollerSessionStateIdle, + MfDesfirePollerSessionStateActive, + MfDesfirePollerSessionStateStopRequest, +} MfDesfirePollerSessionState; + +struct MfDesfirePoller { + Iso14443_4aPoller* iso14443_4a_poller; + MfDesfirePollerSessionState session_state; + MfDesfirePollerState state; + MfDesfireError error; + MfDesfireData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* input_buffer; + BitBuffer* result_buffer; + MfDesfirePollerEventData mf_desfire_event_data; + MfDesfirePollerEvent mf_desfire_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +MfDesfireError mf_desfire_process_error(Iso14443_4aError error); + +const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); + +MfDesfireError mf_desfire_send_chunks( + MfDesfirePoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +MfDesfireError + mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data); + +MfDesfireError + mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data); + +MfDesfireError mf_desfire_poller_async_read_key_settings( + MfDesfirePoller* instance, + MfDesfireKeySettings* data); + +MfDesfireError mf_desfire_poller_async_read_key_versions( + MfDesfirePoller* instance, + SimpleArray* data, + uint32_t count); + +MfDesfireError + mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data); + +MfDesfireError mf_desfire_poller_async_select_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id); + +MfDesfireError mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data); + +MfDesfireError mf_desfire_poller_async_read_file_settings( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileSettings* data); + +MfDesfireError mf_desfire_poller_async_read_file_settings_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + SimpleArray* data); + +MfDesfireError mf_desfire_poller_async_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +MfDesfireError mf_desfire_poller_async_read_file_value( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileData* data); + +MfDesfireError mf_desfire_poller_async_read_file_records( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +MfDesfireError mf_desfire_poller_async_read_file_data_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + const SimpleArray* file_settings, + SimpleArray* data); + +MfDesfireError + mf_desfire_poller_async_read_application(MfDesfirePoller* instance, MfDesfireApplication* data); + +MfDesfireError mf_desfire_poller_async_read_applications( + MfDesfirePoller* instance, + const SimpleArray* app_ids, + SimpleArray* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c new file mode 100644 index 0000000000..40d9976863 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -0,0 +1,627 @@ +#include "mf_ultralight.h" + +#include +#include + +#define MF_ULTRALIGHT_PROTOCOL_NAME "NTAG/Ultralight" + +#define MF_ULTRALIGHT_FORMAT_VERSION_KEY "Data format version" +#define MF_ULTRALIGHT_TYPE_KEY MF_ULTRALIGHT_PROTOCOL_NAME " type" +#define MF_ULTRALIGHT_SIGNATURE_KEY "Signature" +#define MF_ULTRALIGHT_MIFARE_VERSION_KEY "Mifare version" +#define MF_ULTRALIGHT_COUNTER_KEY "Counter" +#define MF_ULTRALIGHT_TEARING_KEY "Tearing" +#define MF_ULTRALIGHT_PAGES_TOTAL_KEY "Pages total" +#define MF_ULTRALIGHT_PAGES_READ_KEY "Pages read" +#define MF_ULTRALIGHT_PAGE_KEY "Page" +#define MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY "Failed authentication attempts" + +typedef struct { + const char* device_name; + uint16_t total_pages; + uint16_t config_page; + uint32_t feature_set; +} MfUltralightFeatures; + +static const uint32_t mf_ultralight_data_format_version = 2; + +static const MfUltralightFeatures mf_ultralight_features[MfUltralightTypeNum] = { + [MfUltralightTypeUnknown] = + { + .device_name = "Mifare Ultralight", + .total_pages = 16, + .config_page = 0, + .feature_set = MfUltralightFeatureSupportCompatibleWrite, + }, + [MfUltralightTypeMfulC] = + { + .device_name = "Mifare Ultralight C", + .total_pages = 48, + .config_page = 0, + .feature_set = MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportAuthenticate, + }, + [MfUltralightTypeNTAG203] = + { + .device_name = "NTAG203", + .total_pages = 42, + .config_page = 0, + .feature_set = MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportCounterInMemory, + }, + [MfUltralightTypeUL11] = + { + .device_name = "Mifare Ultralight 11", + .total_pages = 20, + .config_page = 16, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | + MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl, + }, + [MfUltralightTypeUL21] = + { + .device_name = "Mifare Ultralight 21", + .total_pages = 41, + .config_page = 37, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | + MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl | + MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAG213] = + { + .device_name = "NTAG213", + .total_pages = 45, + .config_page = 41, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter | + MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAG215] = + { + .device_name = "NTAG215", + .total_pages = 135, + .config_page = 131, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter | + MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAG216] = + { + .device_name = "NTAG216", + .total_pages = 231, + .config_page = 227, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter | + MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2C1K] = + { + .device_name = "NTAG I2C 1K", + .total_pages = 231, + .config_page = 0, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2C2K] = + { + .device_name = "NTAG I2C 2K", + .total_pages = 485, + .config_page = 0, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2CPlus1K] = + { + .device_name = "NTAG I2C Plus 1K", + .total_pages = 236, + .config_page = 227, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite | + MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2CPlus2K] = + { + .device_name = "NTAG I2C Plus 2K", + .total_pages = 492, + .config_page = 227, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite | + MfUltralightFeatureSupportDynamicLock, + }, +}; + +const NfcDeviceBase nfc_device_mf_ultralight = { + .protocol_name = MF_ULTRALIGHT_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_ultralight_alloc, + .free = (NfcDeviceFree)mf_ultralight_free, + .reset = (NfcDeviceReset)mf_ultralight_reset, + .copy = (NfcDeviceCopy)mf_ultralight_copy, + .verify = (NfcDeviceVerify)mf_ultralight_verify, + .load = (NfcDeviceLoad)mf_ultralight_load, + .save = (NfcDeviceSave)mf_ultralight_save, + .is_equal = (NfcDeviceEqual)mf_ultralight_is_equal, + .get_name = (NfcDeviceGetName)mf_ultralight_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_ultralight_get_uid, + .set_uid = (NfcDeviceSetUid)mf_ultralight_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_ultralight_get_base_data, +}; + +MfUltralightData* mf_ultralight_alloc() { + MfUltralightData* data = malloc(sizeof(MfUltralightData)); + data->iso14443_3a_data = iso14443_3a_alloc(); + return data; +} + +void mf_ultralight_free(MfUltralightData* data) { + furi_assert(data); + + iso14443_3a_free(data->iso14443_3a_data); + free(data); +} + +void mf_ultralight_reset(MfUltralightData* data) { + furi_assert(data); + + iso14443_3a_reset(data->iso14443_3a_data); +} + +void mf_ultralight_copy(MfUltralightData* data, const MfUltralightData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data); + for(size_t i = 0; i < COUNT_OF(data->counter); i++) { + data->counter[i] = other->counter[i]; + } + for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) { + data->tearing_flag[i] = other->tearing_flag[i]; + } + for(size_t i = 0; i < COUNT_OF(data->page); i++) { + data->page[i] = other->page[i]; + } + + data->type = other->type; + data->version = other->version; + data->signature = other->signature; + + data->pages_read = other->pages_read; + data->pages_total = other->pages_total; + data->auth_attempts = other->auth_attempts; +} + +static const char* + mf_ultralight_get_device_name_by_type(MfUltralightType type, NfcDeviceNameType name_type) { + if(name_type == NfcDeviceNameTypeShort && + (type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21)) { + type = MfUltralightTypeUnknown; + } + + return mf_ultralight_features[type].device_name; +} + +bool mf_ultralight_verify(MfUltralightData* data, const FuriString* device_type) { + furi_assert(data); + + bool verified = false; + + for(MfUltralightType i = 0; i < MfUltralightTypeNum; i++) { + const char* name = mf_ultralight_get_device_name_by_type(i, NfcDeviceNameTypeFull); + verified = furi_string_equal(device_type, name); + if(verified) { + data->type = i; + break; + } + } + + return verified; +} + +bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool parsed = false; + + do { + // Read ISO14443_3A data + if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break; + + // Read Ultralight specific data + // Read Mifare Ultralight format version + uint32_t data_format_version = 0; + if(!flipper_format_read_uint32( + ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &data_format_version, 1)) { + if(!flipper_format_rewind(ff)) break; + } + + // Read Mifare Ultralight type + if(data_format_version > 1) { + if(!flipper_format_read_string(ff, MF_ULTRALIGHT_TYPE_KEY, temp_str)) break; + if(!mf_ultralight_verify(data, temp_str)) break; + } + + // Read signature + if(!flipper_format_read_hex( + ff, + MF_ULTRALIGHT_SIGNATURE_KEY, + data->signature.data, + sizeof(MfUltralightSignature))) + break; + // Read Mifare version + if(!flipper_format_read_hex( + ff, + MF_ULTRALIGHT_MIFARE_VERSION_KEY, + (uint8_t*)&data->version, + sizeof(MfUltralightVersion))) + break; + // Read counters and tearing flags + bool counters_parsed = true; + for(size_t i = 0; i < 3; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i); + if(!flipper_format_read_uint32( + ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) { + counters_parsed = false; + break; + } + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) { + counters_parsed = false; + break; + } + } + if(!counters_parsed) break; + // Read pages + uint32_t pages_total = 0; + if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break; + uint32_t pages_read = 0; + if(data_format_version < mf_ultralight_data_format_version) { + pages_read = pages_total; + } else { + if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) + break; + } + data->pages_total = pages_total; + data->pages_read = pages_read; + + if((pages_read > MF_ULTRALIGHT_MAX_PAGE_NUM) || (pages_total > MF_ULTRALIGHT_MAX_PAGE_NUM)) + break; + + bool pages_parsed = true; + for(size_t i = 0; i < pages_total; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + data->page[i].data, + sizeof(MfUltralightPage))) { + pages_parsed = false; + break; + } + } + if(!pages_parsed) break; + + // Read authentication counter + if(!flipper_format_read_uint32( + ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1)) { + data->auth_attempts = 0; + } + + parsed = true; + } while(false); + + furi_string_free(temp_str); + + return parsed; +} + +bool mf_ultralight_save(const MfUltralightData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool saved = false; + + do { + if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, MF_ULTRALIGHT_PROTOCOL_NAME " specific data")) + break; + if(!flipper_format_write_uint32( + ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &mf_ultralight_data_format_version, 1)) + break; + + const char* device_type_name = + mf_ultralight_get_device_name_by_type(data->type, NfcDeviceNameTypeFull); + if(!flipper_format_write_string_cstr(ff, MF_ULTRALIGHT_TYPE_KEY, device_type_name)) break; + if(!flipper_format_write_hex( + ff, + MF_ULTRALIGHT_SIGNATURE_KEY, + data->signature.data, + sizeof(MfUltralightSignature))) + break; + if(!flipper_format_write_hex( + ff, + MF_ULTRALIGHT_MIFARE_VERSION_KEY, + (uint8_t*)&data->version, + sizeof(MfUltralightVersion))) + break; + + // Write conters and tearing flags data + bool counters_saved = true; + for(size_t i = 0; i < 3; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) { + counters_saved = false; + break; + } + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) { + counters_saved = false; + break; + } + } + if(!counters_saved) break; + + // Write pages data + uint32_t pages_total = data->pages_total; + uint32_t pages_read = data->pages_read; + if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break; + if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) break; + bool pages_saved = true; + for(size_t i = 0; i < data->pages_total; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(temp_str), + data->page[i].data, + sizeof(MfUltralightPage))) { + pages_saved = false; + break; + } + } + if(!pages_saved) break; + + // Write authentication counter + if(!flipper_format_write_uint32( + ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1)) + break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + + return saved; +} + +bool mf_ultralight_is_equal(const MfUltralightData* data, const MfUltralightData* other) { + bool is_equal = false; + bool data_array_is_equal = true; + + do { + if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break; + if(data->type != other->type) break; + if(data->pages_read != other->pages_read) break; + if(data->pages_total != other->pages_total) break; + if(data->auth_attempts != other->auth_attempts) break; + + if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break; + if(memcmp(&data->signature, &other->signature, sizeof(data->signature)) != 0) break; + + for(size_t i = 0; i < COUNT_OF(data->counter); i++) { + if(memcmp(&data->counter[i], &other->counter[i], sizeof(data->counter[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) { + if(memcmp( + &data->tearing_flag[i], + &other->tearing_flag[i], + sizeof(data->tearing_flag[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + for(size_t i = 0; i < COUNT_OF(data->page); i++) { + if(memcmp(&data->page[i], &other->page[i], sizeof(data->page[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + is_equal = true; + } while(false); + + return is_equal; +} + +const char* + mf_ultralight_get_device_name(const MfUltralightData* data, NfcDeviceNameType name_type) { + furi_assert(data); + furi_assert(data->type < MfUltralightTypeNum); + + return mf_ultralight_get_device_name_by_type(data->type, name_type); +} + +const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len); +} + +bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); +} + +Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data) { + furi_assert(data); + + return data->iso14443_3a_data; +} + +MfUltralightType mf_ultralight_get_type_by_version(MfUltralightVersion* version) { + furi_assert(version); + + MfUltralightType type = MfUltralightTypeUnknown; + + if(version->storage_size == 0x0B || version->storage_size == 0x00) { + type = MfUltralightTypeUL11; + } else if(version->storage_size == 0x0E) { + type = MfUltralightTypeUL21; + } else if(version->storage_size == 0x0F) { + type = MfUltralightTypeNTAG213; + } else if(version->storage_size == 0x11) { + type = MfUltralightTypeNTAG215; + } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) { + if(version->prod_ver_minor == 1) { + if(version->storage_size == 0x13) { + type = MfUltralightTypeNTAGI2C1K; + } else if(version->storage_size == 0x15) { + type = MfUltralightTypeNTAGI2C2K; + } + } else if(version->prod_ver_minor == 2) { + if(version->storage_size == 0x13) { + type = MfUltralightTypeNTAGI2CPlus1K; + } else if(version->storage_size == 0x15) { + type = MfUltralightTypeNTAGI2CPlus2K; + } + } + } else if(version->storage_size == 0x13) { + type = MfUltralightTypeNTAG216; + } + + return type; +} + +uint16_t mf_ultralight_get_pages_total(MfUltralightType type) { + return mf_ultralight_features[type].total_pages; +} + +uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type) { + return mf_ultralight_features[type].feature_set; +} + +bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data) { + furi_assert(iso14443_3a_data); + + bool mfu_detected = (iso14443_3a_data->atqa[0] == 0x44) && + (iso14443_3a_data->atqa[1] == 0x00) && (iso14443_3a_data->sak == 0x00); + + return mfu_detected; +} + +uint16_t mf_ultralight_get_config_page_num(MfUltralightType type) { + return mf_ultralight_features[type].config_page; +} + +uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type) { + uint8_t config_page = mf_ultralight_features[type].config_page; + return (config_page != 0) ? config_page + 2 : 0; +} + +bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page) { + uint8_t pwd_page = mf_ultralight_get_pwd_page_num(type); + uint8_t pack_page = pwd_page + 1; + return ((pwd_page != 0) && (page == pwd_page || page == pack_page)); +} + +bool mf_ultralight_support_feature(const uint32_t feature_set, const uint32_t features_to_check) { + return (feature_set & features_to_check) != 0; +} + +bool mf_ultralight_get_config_page(const MfUltralightData* data, MfUltralightConfigPages** config) { + furi_assert(data); + furi_assert(config); + + bool config_pages_found = false; + + uint16_t config_page = mf_ultralight_features[data->type].config_page; + if(config_page != 0) { + *config = (MfUltralightConfigPages*)&data->page[config_page]; //-V1027 + config_pages_found = true; + } + + return config_pages_found; +} + +bool mf_ultralight_is_all_data_read(const MfUltralightData* data) { + furi_assert(data); + + bool all_read = false; + if(data->pages_read == data->pages_total || + (data->type == MfUltralightTypeMfulC && data->pages_read == data->pages_total - 4)) { + // Having read all the pages doesn't mean that we've got everything. + // By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000, + // so a default read on an auth-supported NTAG is never complete. + uint32_t feature_set = mf_ultralight_get_feature_support_set(data->type); + if(!mf_ultralight_support_feature(feature_set, MfUltralightFeatureSupportPasswordAuth)) { + all_read = true; + } else { + MfUltralightConfigPages* config = NULL; + if(mf_ultralight_get_config_page(data, &config)) { + uint32_t pass = + nfc_util_bytes2num(config->password.data, sizeof(MfUltralightAuthPassword)); + uint16_t pack = + nfc_util_bytes2num(config->pack.data, sizeof(MfUltralightAuthPack)); + all_read = ((pass != 0) || (pack != 0)); + } + } + } + + return all_read; +} + +bool mf_ultralight_is_counter_configured(const MfUltralightData* data) { + furi_assert(data); + + MfUltralightConfigPages* config = NULL; + bool configured = false; + + switch(data->type) { + case MfUltralightTypeNTAG213: + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + if(mf_ultralight_get_config_page(data, &config)) { + configured = config->access.nfc_cnt_en; + } + break; + + default: + configured = true; + break; + } + + return configured; +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h new file mode 100644 index 0000000000..747f5937ad --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -0,0 +1,229 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_ULTRALIGHT_TEARING_FLAG_DEFAULT (0xBD) + +#define MF_ULTRALIGHT_CMD_GET_VERSION (0x60) +#define MF_ULTRALIGHT_CMD_READ_PAGE (0x30) +#define MF_ULTRALIGHT_CMD_FAST_READ (0x3A) +#define MF_ULTRALIGHT_CMD_SECTOR_SELECT (0xC2) +#define MF_ULTRALIGHT_CMD_COMP_WRITE (0xA0) +#define MF_ULTRALIGHT_CMD_WRITE_PAGE (0xA2) +#define MF_ULTRALIGHT_CMD_FAST_WRITE (0xA6) +#define MF_ULTRALIGHT_CMD_READ_SIG (0x3C) +#define MF_ULTRALIGHT_CMD_READ_CNT (0x39) +#define MF_ULTRALIGHT_CMD_INCR_CNT (0xA5) +#define MF_ULTRALIGHT_CMD_CHECK_TEARING (0x3E) +#define MF_ULTRALIGHT_CMD_PWD_AUTH (0x1B) +#define MF_ULTRALIGHT_CMD_AUTH (0x1A) +#define MF_ULTRALIGHT_CMD_VCSL (0x4B) + +#define MF_ULTRALIGHT_CMD_ACK (0x0A) +#define MF_ULTRALIGHT_CMD_NACK (0x00) +#define MF_ULTRALIGHT_CMD_AUTH_NAK (0x04) + +#define MF_ULTRALIGHT_MAX_CNTR_VAL (0x00FFFFFF) +#define MF_ULTRALIGHT_MAX_PAGE_NUM (510) +#define MF_ULTRALIGHT_PAGE_SIZE (4U) +#define MF_ULTRALIGHT_SIGNATURE_SIZE (32) +#define MF_ULTRALIGHT_COUNTER_SIZE (3) +#define MF_ULTRALIGHT_COUNTER_NUM (3) +#define MF_ULTRALIGHT_TEARING_FLAG_SIZE (1) +#define MF_ULTRALIGHT_TEARING_FLAG_NUM (3) +#define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4) +#define MF_ULTRALIGHT_AUTH_PACK_SIZE (2) +#define MF_ULTRALIGHT_AUTH_RESPONSE_SIZE (9) + +typedef enum { + MfUltralightErrorNone, + MfUltralightErrorNotPresent, + MfUltralightErrorProtocol, + MfUltralightErrorAuth, + MfUltralightErrorTimeout, +} MfUltralightError; + +typedef enum { + MfUltralightTypeUnknown, + MfUltralightTypeNTAG203, + MfUltralightTypeMfulC, + MfUltralightTypeUL11, + MfUltralightTypeUL21, + MfUltralightTypeNTAG213, + MfUltralightTypeNTAG215, + MfUltralightTypeNTAG216, + MfUltralightTypeNTAGI2C1K, + MfUltralightTypeNTAGI2C2K, + MfUltralightTypeNTAGI2CPlus1K, + MfUltralightTypeNTAGI2CPlus2K, + + MfUltralightTypeNum, +} MfUltralightType; + +typedef enum { + MfUltralightFeatureSupportReadVersion = (1U << 0), + MfUltralightFeatureSupportReadSignature = (1U << 1), + MfUltralightFeatureSupportReadCounter = (1U << 2), + MfUltralightFeatureSupportCheckTearingFlag = (1U << 3), + MfUltralightFeatureSupportFastRead = (1U << 4), + MfUltralightFeatureSupportIncCounter = (1U << 5), + MfUltralightFeatureSupportFastWrite = (1U << 6), + MfUltralightFeatureSupportCompatibleWrite = (1U << 7), + MfUltralightFeatureSupportPasswordAuth = (1U << 8), + MfUltralightFeatureSupportVcsl = (1U << 9), + MfUltralightFeatureSupportSectorSelect = (1U << 10), + MfUltralightFeatureSupportSingleCounter = (1U << 11), + MfUltralightFeatureSupportAsciiMirror = (1U << 12), + MfUltralightFeatureSupportCounterInMemory = (1U << 13), + MfUltralightFeatureSupportDynamicLock = (1U << 14), + MfUltralightFeatureSupportAuthenticate = (1U << 15), +} MfUltralightFeatureSupport; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_PAGE_SIZE]; +} MfUltralightPage; + +typedef struct { + MfUltralightPage page[4]; +} MfUltralightPageReadCommandData; + +typedef struct { + uint8_t header; + uint8_t vendor_id; + uint8_t prod_type; + uint8_t prod_subtype; + uint8_t prod_ver_major; + uint8_t prod_ver_minor; + uint8_t storage_size; + uint8_t protocol_type; +} MfUltralightVersion; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_SIGNATURE_SIZE]; +} MfUltralightSignature; + +typedef union { + uint32_t counter; + uint8_t data[MF_ULTRALIGHT_COUNTER_SIZE]; +} MfUltralightCounter; + +typedef struct { + uint8_t data; +} MfUltralightTearingFlag; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_AUTH_PASSWORD_SIZE]; +} MfUltralightAuthPassword; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_AUTH_PACK_SIZE]; +} MfUltralightAuthPack; + +typedef enum { + MfUltralightMirrorNone, + MfUltralightMirrorUid, + MfUltralightMirrorCounter, + MfUltralightMirrorUidCounter, +} MfUltralightMirrorConf; + +typedef struct __attribute__((packed)) { + union { + uint8_t value; + struct { + uint8_t rfui1 : 2; + bool strg_mod_en : 1; + bool rfui2 : 1; + uint8_t mirror_byte : 2; + MfUltralightMirrorConf mirror_conf : 2; + }; + } mirror; + uint8_t rfui1; + uint8_t mirror_page; + uint8_t auth0; + union { + uint8_t value; + struct { + uint8_t authlim : 3; + bool nfc_cnt_pwd_prot : 1; + bool nfc_cnt_en : 1; + bool nfc_dis_sec1 : 1; // NTAG I2C Plus only + bool cfglck : 1; + bool prot : 1; + }; + } access; + uint8_t vctid; + uint8_t rfui2[2]; + MfUltralightAuthPassword password; + MfUltralightAuthPack pack; + uint8_t rfui3[2]; +} MfUltralightConfigPages; + +typedef struct { + Iso14443_3aData* iso14443_3a_data; + MfUltralightType type; + MfUltralightVersion version; + MfUltralightSignature signature; + MfUltralightCounter counter[MF_ULTRALIGHT_COUNTER_NUM]; + MfUltralightTearingFlag tearing_flag[MF_ULTRALIGHT_TEARING_FLAG_NUM]; + MfUltralightPage page[MF_ULTRALIGHT_MAX_PAGE_NUM]; + uint16_t pages_read; + uint16_t pages_total; + uint32_t auth_attempts; +} MfUltralightData; + +extern const NfcDeviceBase nfc_device_mf_ultralight; + +MfUltralightData* mf_ultralight_alloc(); + +void mf_ultralight_free(MfUltralightData* data); + +void mf_ultralight_reset(MfUltralightData* data); + +void mf_ultralight_copy(MfUltralightData* data, const MfUltralightData* other); + +bool mf_ultralight_verify(MfUltralightData* data, const FuriString* device_type); + +bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t version); + +bool mf_ultralight_save(const MfUltralightData* data, FlipperFormat* ff); + +bool mf_ultralight_is_equal(const MfUltralightData* data, const MfUltralightData* other); + +const char* + mf_ultralight_get_device_name(const MfUltralightData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_len); + +bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data); + +MfUltralightType mf_ultralight_get_type_by_version(MfUltralightVersion* version); + +uint16_t mf_ultralight_get_pages_total(MfUltralightType type); + +uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type); + +uint16_t mf_ultralight_get_config_page_num(MfUltralightType type); + +uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type); + +bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page_num); + +bool mf_ultralight_support_feature(const uint32_t feature_set, const uint32_t features_to_check); + +bool mf_ultralight_get_config_page(const MfUltralightData* data, MfUltralightConfigPages** config); + +bool mf_ultralight_is_all_data_read(const MfUltralightData* data); + +bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data); + +bool mf_ultralight_is_counter_configured(const MfUltralightData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c new file mode 100644 index 0000000000..70c6f6de2a --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -0,0 +1,772 @@ +#include "mf_ultralight_listener_i.h" +#include "mf_ultralight_listener_defs.h" + +#include + +#include + +#define TAG "MfUltralightListener" + +#define MF_ULTRALIGHT_LISTENER_MAX_TX_BUFF_SIZE (256) + +typedef enum { + MfUltralightListenerAccessTypeRead, + MfUltralightListenerAccessTypeWrite, +} MfUltralightListenerAccessType; + +typedef struct { + uint8_t cmd; + size_t cmd_len_bits; + MfUltralightListenerCommandCallback callback; +} MfUltralightListenerCmdHandler; + +static bool mf_ultralight_listener_check_access( + MfUltralightListener* instance, + uint16_t start_page, + MfUltralightListenerAccessType access_type) { + bool access_success = false; + bool is_write_op = (access_type == MfUltralightListenerAccessTypeWrite); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportPasswordAuth)) { + access_success = true; + break; + } + if(instance->auth_state != MfUltralightListenerAuthStateSuccess) { + if((instance->config->auth0 <= start_page) && + (instance->config->access.prot || is_write_op)) { + break; + } + } + if(instance->config->access.cfglck && is_write_op) { + uint16_t config_page_start = instance->data->pages_total - 4; + if((start_page == config_page_start) || (start_page == config_page_start + 1)) { + break; + } + } + + access_success = true; + } while(false); + + return access_success; +} + +static void mf_ultralight_listener_send_short_resp(MfUltralightListener* instance, uint8_t data) { + furi_assert(instance->tx_buffer); + + bit_buffer_set_size(instance->tx_buffer, 4); + bit_buffer_set_byte(instance->tx_buffer, 0, data); + iso14443_3a_listener_tx(instance->iso14443_3a_listener, instance->tx_buffer); +}; + +static void mf_ultralight_listener_perform_read( + MfUltralightPage* pages, + MfUltralightListener* instance, + uint16_t start_page, + uint8_t page_cnt, + bool do_i2c_page_check) { + uint16_t pages_total = instance->data->pages_total; + mf_ultralight_mirror_read_prepare(start_page, instance); + for(uint8_t i = 0, rollover = 0; i < page_cnt; i++) { + uint16_t page = start_page + i; + + bool page_restricted = !mf_ultralight_listener_check_access( + instance, page, MfUltralightListenerAccessTypeRead); + + if(do_i2c_page_check && !mf_ultralight_i2c_validate_pages(page, page, instance)) + memset(pages[i].data, 0, sizeof(MfUltralightPage)); + else if(mf_ultralight_is_page_pwd_or_pack(instance->data->type, page)) + memset(pages[i].data, 0, sizeof(MfUltralightPage)); + else { + if(do_i2c_page_check) + page = mf_ultralight_i2c_provide_page_by_requested(page, instance); + + page = page_restricted ? rollover++ : page % pages_total; + pages[i] = instance->data->page[page]; + + mf_ultralight_mirror_read_handler(page, pages[i].data, instance); + } + } + mf_ultralight_single_counter_try_increase(instance); +} + +static MfUltralightCommand mf_ultralight_listener_perform_write( + MfUltralightListener* instance, + const uint8_t* const rx_data, + uint16_t start_page, + bool do_i2c_check) { + MfUltralightCommand command = MfUltralightCommandProcessedACK; + + if(start_page < 2 && instance->sector == 0) + command = MfUltralightCommandNotProcessedNAK; + else if(start_page == 2 && instance->sector == 0) + mf_ultralight_static_lock_bytes_write(instance->static_lock, *((uint16_t*)&rx_data[2])); + else if(start_page == 3 && instance->sector == 0) + mf_ultralight_capability_container_write(&instance->data->page[start_page], rx_data); + else if(mf_ultralight_is_page_dynamic_lock(instance, start_page)) + mf_ultralight_dynamic_lock_bytes_write(instance->dynamic_lock, *((uint32_t*)rx_data)); + else { + uint16_t page = start_page; + if(do_i2c_check) page = mf_ultralight_i2c_provide_page_by_requested(start_page, instance); + + memcpy(instance->data->page[page].data, rx_data, sizeof(MfUltralightPage)); + } + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_read_page_handler(MfUltralightListener* instance, BitBuffer* buffer) { + uint16_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t pages_total = instance->data->pages_total; + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_READ: %d", start_page); + + do { + bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); + + if(do_i2c_check) { + if(!mf_ultralight_i2c_validate_pages(start_page, start_page, instance)) break; + } else if(pages_total < start_page) { + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeRead)) { + break; + } + + MfUltralightPage pages[4] = {}; + mf_ultralight_listener_perform_read(pages, instance, start_page, 4, do_i2c_check); + + bit_buffer_copy_bytes(instance->tx_buffer, (uint8_t*)pages, sizeof(pages)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_fast_read_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + FURI_LOG_D(TAG, "CMD_FAST_READ"); + + do { + if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastRead)) + break; + uint16_t pages_total = instance->data->pages_total; + uint16_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t end_page = bit_buffer_get_byte(buffer, 2); + bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); + + if(do_i2c_check) { + if(!mf_ultralight_i2c_validate_pages(start_page, end_page, instance)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + } else if(end_page > pages_total - 1) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(end_page < start_page) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeRead) || + !mf_ultralight_listener_check_access( + instance, end_page, MfUltralightListenerAccessTypeRead)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + MfUltralightPage pages[64] = {}; + uint8_t page_cnt = (end_page - start_page) + 1; + mf_ultralight_listener_perform_read(pages, instance, start_page, page_cnt, do_i2c_check); + + bit_buffer_copy_bytes(instance->tx_buffer, (uint8_t*)pages, page_cnt * 4); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_write_page_handler(MfUltralightListener* instance, BitBuffer* buffer) { + uint8_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t pages_total = instance->data->pages_total; + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_WRITE"); + + do { + bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); + + if(do_i2c_check) { + if(!mf_ultralight_i2c_validate_pages(start_page, start_page, instance)) break; + } else if(pages_total < start_page) { + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeWrite)) + break; + + if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page)) break; + if(mf_ultralight_dynamic_lock_check_page(instance, start_page)) break; + + const uint8_t* rx_data = bit_buffer_get_data(buffer); + command = + mf_ultralight_listener_perform_write(instance, &rx_data[2], start_page, do_i2c_check); + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_fast_write_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + FURI_LOG_D(TAG, "CMD_FAST_WRITE"); + + do { + if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastWrite)) + break; + + uint8_t start_page = bit_buffer_get_byte(buffer, 1); + uint8_t end_page = bit_buffer_get_byte(buffer, 2); + if(start_page != 0xF0 || end_page != 0xFF) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + // No SRAM emulation support + + command = MfUltralightCommandProcessedACK; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_read_version_handler(MfUltralightListener* instance, BitBuffer* buffer) { + UNUSED(buffer); + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + + FURI_LOG_D(TAG, "CMD_GET_VERSION"); + + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadVersion)) { + bit_buffer_copy_bytes( + instance->tx_buffer, (uint8_t*)&instance->data->version, sizeof(MfUltralightVersion)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } + + return command; +} + +static MfUltralightCommand mf_ultralight_listener_read_signature_handler( + MfUltralightListener* instance, + BitBuffer* buffer) { + UNUSED(buffer); + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + + FURI_LOG_D(TAG, "CMD_READ_SIG"); + + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadSignature)) { + bit_buffer_copy_bytes( + instance->tx_buffer, instance->data->signature.data, sizeof(MfUltralightSignature)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_read_counter_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_READ_CNT"); + + do { + uint8_t counter_num = bit_buffer_get_byte(buffer, 1); + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportReadCounter)) + break; + + if(mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportSingleCounter)) { + if(instance->config == NULL) break; + + if(!instance->config->access.nfc_cnt_en || counter_num != 2) break; + + if(instance->config->access.nfc_cnt_pwd_prot) { + if(instance->auth_state != MfUltralightListenerAuthStateSuccess) { + break; + } + } + } + + if(counter_num > 2) break; + uint8_t cnt_value[3] = { + (instance->data->counter[counter_num].counter >> 0) & 0xff, + (instance->data->counter[counter_num].counter >> 8) & 0xff, + (instance->data->counter[counter_num].counter >> 16) & 0xff, + }; + bit_buffer_copy_bytes(instance->tx_buffer, cnt_value, sizeof(cnt_value)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand mf_ultralight_listener_increase_counter_handler( + MfUltralightListener* instance, + BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_INCR_CNT"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportIncCounter)) { + command = MfUltralightCommandNotProcessedSilent; + break; + } + + uint8_t counter_num = bit_buffer_get_byte(buffer, 1); + if(counter_num > 2) break; + + if(instance->data->counter[counter_num].counter == MF_ULTRALIGHT_MAX_CNTR_VAL) { + command = MfUltralightCommandProcessed; + break; + } + + MfUltralightCounter buf_counter = {}; + bit_buffer_write_bytes_mid(buffer, buf_counter.data, 2, sizeof(buf_counter.data)); + uint32_t incr_value = buf_counter.counter; + + if(instance->data->counter[counter_num].counter + incr_value > MF_ULTRALIGHT_MAX_CNTR_VAL) + break; + + instance->data->counter[counter_num].counter += incr_value; + command = MfUltralightCommandProcessedACK; + } while(false); + + return command; +} + +static MfUltralightCommand mf_ultralight_listener_check_tearing_handler( + MfUltralightListener* instance, + BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_CHECK_TEARING"); + + do { + uint8_t tearing_flag_num = bit_buffer_get_byte(buffer, 1); + if(!mf_ultralight_support_feature( + instance->features, + MfUltralightFeatureSupportCheckTearingFlag | + MfUltralightFeatureSupportSingleCounter)) { + break; + } + if(mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportSingleCounter) && + (tearing_flag_num != 2)) { + break; + } + if(tearing_flag_num >= MF_ULTRALIGHT_TEARING_FLAG_NUM) { + break; + } + + bit_buffer_set_size_bytes(instance->tx_buffer, 1); + bit_buffer_set_byte( + instance->tx_buffer, 0, instance->data->tearing_flag[tearing_flag_num].data); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_vcsl_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + UNUSED(instance); + UNUSED(buffer); + FURI_LOG_D(TAG, "CMD_VCSL"); + do { + if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportVcsl)) + break; + + MfUltralightConfigPages* config; + if(!mf_ultralight_get_config_page(instance->data, &config)) break; + + bit_buffer_set_size_bytes(instance->tx_buffer, 1); + bit_buffer_set_byte(instance->tx_buffer, 0, config->vctid); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_auth_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_AUTH"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportPasswordAuth)) + break; + + MfUltralightAuthPassword password; + bit_buffer_write_bytes_mid(buffer, password.data, 1, sizeof(password.data)); + + if(instance->callback) { + instance->mfu_event_data.password = password; + instance->mfu_event.type = MfUltralightListenerEventTypeAuth; + instance->callback(instance->generic_event, instance->context); + } + + bool auth_success = + mf_ultralight_auth_check_password(&instance->config->password, &password); + bool card_locked = mf_ultralight_auth_limit_check_and_update(instance, auth_success); + + if(card_locked) { + command = MfUltralightCommandNotProcessedAuthNAK; + break; + } + + if(!auth_success) break; + + bit_buffer_copy_bytes( + instance->tx_buffer, instance->config->pack.data, sizeof(MfUltralightAuthPack)); + instance->auth_state = MfUltralightListenerAuthStateSuccess; + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_comp_write_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + FURI_LOG_D(TAG, "CMD_CM_WR_2"); + + do { + if(bit_buffer_get_size_bytes(buffer) != 16) break; + + const uint8_t* rx_data = bit_buffer_get_data(buffer); + uint8_t start_page = instance->composite_cmd.data; + + command = mf_ultralight_listener_perform_write(instance, rx_data, start_page, false); + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_comp_write_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + + FURI_LOG_D(TAG, "CMD_CM_WR_1"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportCompatibleWrite)) + break; + + uint8_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t last_page = instance->data->pages_total - 1; + + if(start_page < 2 || start_page > last_page) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeWrite)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page) || + mf_ultralight_dynamic_lock_check_page(instance, start_page)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + instance->composite_cmd.data = start_page; + command = MfUltralightCommandProcessedACK; + mf_ultralight_composite_command_set_next(instance, mf_ultralight_comp_write_handler_p2); + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_sector_select_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + UNUSED(instance); + UNUSED(buffer); + FURI_LOG_D(TAG, "CMD_SEC_SEL_2"); + + do { + if(bit_buffer_get_size_bytes(buffer) != 4) break; + uint8_t sector = bit_buffer_get_byte(buffer, 0); + if(sector == 0xFF) break; + + instance->sector = sector; + command = MfUltralightCommandProcessedSilent; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_sector_select_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + UNUSED(buffer); + FURI_LOG_D(TAG, "CMD_SEC_SEL_1"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportSectorSelect) && + bit_buffer_get_byte(buffer, 1) == 0xFF) + break; + + command = MfUltralightCommandProcessedACK; + mf_ultralight_composite_command_set_next(instance, mf_ultralight_sector_select_handler_p2); + } while(false); + + return command; +} + +static const MfUltralightListenerCmdHandler mf_ultralight_command[] = { + { + .cmd = MF_ULTRALIGHT_CMD_READ_PAGE, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_read_page_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_FAST_READ, + .cmd_len_bits = 3 * 8, + .callback = mf_ultralight_listener_fast_read_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_WRITE_PAGE, + .cmd_len_bits = 6 * 8, + .callback = mf_ultralight_listener_write_page_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_FAST_WRITE, + .cmd_len_bits = 67 * 8, + .callback = mf_ultralight_listener_fast_write_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_GET_VERSION, + .cmd_len_bits = 8, + .callback = mf_ultralight_listener_read_version_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_READ_SIG, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_read_signature_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_READ_CNT, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_read_counter_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_CHECK_TEARING, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_check_tearing_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_PWD_AUTH, + .cmd_len_bits = 5 * 8, + .callback = mf_ultralight_listener_auth_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_INCR_CNT, + .cmd_len_bits = 6 * 8, + .callback = mf_ultralight_listener_increase_counter_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_SECTOR_SELECT, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_sector_select_handler_p1, + }, + { + .cmd = MF_ULTRALIGHT_CMD_COMP_WRITE, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_comp_write_handler_p1, + }, + { + .cmd = MF_ULTRALIGHT_CMD_VCSL, + .cmd_len_bits = 21 * 8, + .callback = mf_ultralight_listener_vcsl_handler, + }, +}; + +static void mf_ultralight_listener_prepare_emulation(MfUltralightListener* instance) { + MfUltralightData* data = instance->data; + instance->features = mf_ultralight_get_feature_support_set(data->type); + mf_ultralight_get_config_page(data, &instance->config); + mf_ultralight_mirror_prepare_emulation(instance); + mf_ultralight_static_lock_bytes_prepare(instance); + mf_ultralight_dynamic_lock_bytes_prepare(instance); +} + +static NfcCommand mf_ultralight_command_postprocess( + MfUltralightCommand mfu_command, + MfUltralightListener* instance) { + NfcCommand command = NfcCommandContinue; + + if(mfu_command == MfUltralightCommandProcessedACK) { + mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_ACK); + command = NfcCommandContinue; + } else if(mfu_command == MfUltralightCommandProcessedSilent) { + command = NfcCommandReset; + } else if(mfu_command != MfUltralightCommandProcessed) { + instance->auth_state = MfUltralightListenerAuthStateIdle; + command = NfcCommandSleep; + + if(mfu_command == MfUltralightCommandNotProcessedNAK) { + mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_NACK); + } else if(mfu_command == MfUltralightCommandNotProcessedAuthNAK) { + mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_AUTH_NAK); + } + } + + return command; +} + +static NfcCommand mf_ultralight_reset_listener_state( + MfUltralightListener* instance, + Iso14443_3aListenerEventType event_type) { + mf_ultralight_composite_command_reset(instance); + mf_ultralight_single_counter_try_to_unlock(instance, event_type); + instance->sector = 0; + instance->auth_state = MfUltralightListenerAuthStateIdle; + return NfcCommandSleep; +} + +MfUltralightListener* mf_ultralight_listener_alloc( + Iso14443_3aListener* iso14443_3a_listener, + MfUltralightData* data) { + furi_assert(iso14443_3a_listener); + + MfUltralightListener* instance = malloc(sizeof(MfUltralightListener)); + instance->mirror.ascii_mirror_data = furi_string_alloc(); + instance->iso14443_3a_listener = iso14443_3a_listener; + instance->data = data; + mf_ultralight_static_lock_bytes_prepare(instance); + mf_ultralight_listener_prepare_emulation(instance); + mf_ultralight_composite_command_reset(instance); + instance->sector = 0; + instance->tx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_LISTENER_MAX_TX_BUFF_SIZE); + + instance->mfu_event.data = &instance->mfu_event_data; + instance->generic_event.protocol = NfcProtocolMfUltralight; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->mfu_event; + + return instance; +} + +void mf_ultralight_listener_free(MfUltralightListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + furi_string_free(instance->mirror.ascii_mirror_data); + free(instance); +} + +const MfUltralightData* mf_ultralight_listener_get_data(MfUltralightListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +void mf_ultralight_listener_set_callback( + MfUltralightListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +NfcCommand mf_ultralight_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + + MfUltralightListener* instance = context; + Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; + BitBuffer* rx_buffer = iso14443_3a_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { + MfUltralightCommand mfu_command = MfUltralightCommandNotFound; + size_t size = bit_buffer_get_size(rx_buffer); + uint8_t cmd = bit_buffer_get_byte(rx_buffer, 0); + + if(mf_ultralight_composite_command_in_progress(instance)) { + mfu_command = mf_ultralight_composite_command_run(instance, rx_buffer); + } else { + for(size_t i = 0; i < COUNT_OF(mf_ultralight_command); i++) { + if(size != mf_ultralight_command[i].cmd_len_bits) continue; + if(cmd != mf_ultralight_command[i].cmd) continue; + mfu_command = mf_ultralight_command[i].callback(instance, rx_buffer); + + if(mfu_command != MfUltralightCommandNotFound) break; + } + } + command = mf_ultralight_command_postprocess(mfu_command, instance); + } else if( + iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedData || + iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff || + iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted) { + command = mf_ultralight_reset_listener_state(instance, iso14443_3a_event->type); + } + + return command; +} + +const NfcListenerBase mf_ultralight_listener = { + .alloc = (NfcListenerAlloc)mf_ultralight_listener_alloc, + .free = (NfcListenerFree)mf_ultralight_listener_free, + .get_data = (NfcListenerGetData)mf_ultralight_listener_get_data, + .set_callback = (NfcListenerSetCallback)mf_ultralight_listener_set_callback, + .run = (NfcListenerRun)mf_ultralight_listener_run, +}; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h new file mode 100644 index 0000000000..fa6a6bd7e7 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h @@ -0,0 +1,28 @@ +#pragma once + +#include "mf_ultralight.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfUltralightListener MfUltralightListener; + +typedef enum { + MfUltralightListenerEventTypeAuth, +} MfUltralightListenerEventType; + +typedef struct { + union { + MfUltralightAuthPassword password; + }; +} MfUltralightListenerEventData; + +typedef struct { + MfUltralightListenerEventType type; + MfUltralightListenerEventData* data; +} MfUltralightListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h new file mode 100644 index 0000000000..aa3e11b8c4 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase mf_ultralight_listener; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c new file mode 100644 index 0000000000..3d6b9a94fd --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c @@ -0,0 +1,577 @@ +#include "mf_ultralight_listener_i.h" + +#include + +#define MF_ULTRALIGHT_STATIC_BIT_LOCK_OTP_CC 0 +#define MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_9_4 1 +#define MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_15_10 2 + +#define MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, bit) (((lock_bits) & (1U << (bit))) != 0) +#define MF_ULTRALIGHT_BITS_SET(lock_bits, mask) ((lock_bits) |= (mask)) +#define MF_ULTRALIGHT_BITS_CLR(lock_bits, mask) ((lock_bits) &= ~(mask)) + +#define MF_ULTRALIGHT_PAGE_LOCKED(lock_bits, page) MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, page) + +#define MF_ULTRALIGHT_STATIC_BIT_OTP_CC_LOCKED(lock_bits) \ + MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_OTP_CC) + +#define MF_ULTRALIGHT_STATIC_BITS_9_4_LOCKED(lock_bits) \ + MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_9_4) + +#define MF_ULTRALIGHT_STATIC_BITS_15_10_LOCKED(lock_bits) \ + MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_15_10) + +#define MF_ULTRALIGHT_STATIC_LOCK_L_OTP_CC_MASK (1U << 3) +#define MF_ULTRALIGHT_STATIC_LOCK_L_9_4_MASK \ + ((1U << 9) | (1U << 8) | (1U << 7) | (1U << 6) | (1U << 5) | (1U << 4)) +#define MF_ULTRALIGHT_STATIC_LOCK_L_15_10_MASK \ + ((1U << 15) | (1U << 14) | (1U << 13) | (1U << 12) | (1U << 11) | (1U << 10)) + +#define MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, start, end) (((page) >= (start)) && ((page) <= (end))) + +#define MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(page) \ + MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x00EC, 0x00ED) + +#define MF_ULTRALIGHT_I2C_PAGE_ON_MIRRORED_SESSION_REG(page) \ + MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x00F8, 0x00F9) + +#define MF_ULTRALIGHT_AUTH_RESET_ATTEMPTS(instance) (instance->data->auth_attempts = 0) +#define MF_ULTRALIGHT_AUTH_INCREASE_ATTEMPTS(instance) (instance->data->auth_attempts++) + +static MfUltralightMirrorConf mf_ultralight_mirror_check_mode( + const MfUltralightConfigPages* const config, + const MfUltralightListenerAuthState auth_state) { + MfUltralightMirrorConf mirror_mode = config->mirror.mirror_conf; + + if(mirror_mode == MfUltralightMirrorNone || mirror_mode == MfUltralightMirrorUid) + return mirror_mode; + + if(!config->access.nfc_cnt_en || + (config->access.nfc_cnt_pwd_prot && auth_state != MfUltralightListenerAuthStateSuccess)) { + mirror_mode = mirror_mode == MfUltralightMirrorCounter ? MfUltralightMirrorNone : + MfUltralightMirrorUid; + } + return mirror_mode; +} + +static bool mf_ultralight_mirror_check_boundaries(MfUltralightListener* instance) { + const MfUltralightConfigPages* const conf = instance->config; + + uint8_t last_user_page = mf_ultralight_get_config_page_num(instance->data->type) - 2; + + uint8_t max_page_offset = 0; + uint8_t max_byte_offset = 2; + + MfUltralightMirrorConf mode = mf_ultralight_mirror_check_mode(conf, instance->auth_state); + + bool result = false; + bool again = false; + do { + if(mode == MfUltralightMirrorNone) { + break; + } else if(mode == MfUltralightMirrorUid) { + max_page_offset = 3; + } else if(mode == MfUltralightMirrorCounter) { + max_page_offset = 1; + } else if(mode == MfUltralightMirrorUidCounter) { + max_page_offset = 5; + max_byte_offset = 3; + } + + instance->mirror.actual_mode = mode; + + if(conf->mirror_page <= 3) break; + if(conf->mirror_page < last_user_page - max_page_offset) { + result = true; + break; + } + if(conf->mirror_page == last_user_page - max_page_offset) { + result = (conf->mirror.mirror_byte <= max_byte_offset); + break; + } + + if(conf->mirror_page > last_user_page - max_page_offset && + mode == MfUltralightMirrorUidCounter) { + mode = MfUltralightMirrorUid; + again = true; + } + } while(again); + + return result; +} + +static bool mf_ultralight_mirror_enabled(MfUltralightListener* instance) { + bool mirror_enabled = false; + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportAsciiMirror) && + (instance->config != NULL) && mf_ultralight_mirror_check_boundaries(instance)) { + mirror_enabled = true; + } + instance->mirror.enabled = mirror_enabled; + return instance->mirror.enabled; +} + +static uint8_t mf_ultralight_get_mirror_data_size(MfUltralightMirrorConf mode) { + switch(mode) { + case MfUltralightMirrorUid: + return 14; + case MfUltralightMirrorCounter: + return 6; + case MfUltralightMirrorUidCounter: + return 21; + default: + return 0; + } +} + +static uint8_t mf_ultralight_get_mirror_last_page(MfUltralightListener* instance) { + uint8_t strSize = mf_ultralight_get_mirror_data_size(instance->mirror.actual_mode); + return (instance->config->mirror_page + 1U + strSize / 4); +} + +static uint8_t mf_ultralight_get_ascii_offset(uint8_t start_page, MfUltralightListener* instance) { + uint8_t start_offset = 0; + if(instance->config->mirror.mirror_conf == MfUltralightMirrorCounter) start_offset = 15; + + uint8_t ascii_offset = start_offset; + + if(start_page > instance->config->mirror_page) + ascii_offset = (start_page - instance->config->mirror_page) * 4 - + instance->config->mirror.mirror_byte + start_offset; + + return ascii_offset; +} + +static uint8_t mf_ultralight_get_ascii_end(MfUltralightMirrorConf mode) { + return (mode == MfUltralightMirrorUid) ? 14 : 21; +} + +static uint8_t mf_ultralight_get_byte_offset( + uint8_t current_page, + const MfUltralightConfigPages* const config) { + return (current_page > config->mirror_page) ? 0 : config->mirror.mirror_byte; +} + +static void mf_ultralight_format_mirror_data( + FuriString* str, + const uint8_t* const data, + const uint8_t data_len) { + for(uint8_t i = 0; i < data_len; i++) furi_string_cat_printf(str, "%02X", data[i]); +} + +void mf_ultralight_mirror_read_prepare(uint8_t start_page, MfUltralightListener* instance) { + if(mf_ultralight_mirror_enabled(instance)) { + instance->mirror.ascii_offset = mf_ultralight_get_ascii_offset(start_page, instance); + instance->mirror.ascii_end = mf_ultralight_get_ascii_end(instance->mirror.actual_mode); + + instance->mirror.mirror_last_page = mf_ultralight_get_mirror_last_page(instance); + } +} + +void mf_ultralight_mirror_read_handler( + uint8_t mirror_page_num, + uint8_t* dest, + MfUltralightListener* instance) { + if(instance->mirror.enabled && mirror_page_num >= instance->config->mirror_page && + mirror_page_num <= instance->mirror.mirror_last_page) { + uint8_t byte_offset = mf_ultralight_get_byte_offset(mirror_page_num, instance->config); + + uint8_t ascii_offset = instance->mirror.ascii_offset; + uint8_t ascii_end = instance->mirror.ascii_end; + uint8_t* source = (uint8_t*)furi_string_get_cstr(instance->mirror.ascii_mirror_data); + for(uint8_t j = byte_offset; (j < 4) && (ascii_offset < ascii_end); j++) { + dest[j] = source[ascii_offset]; + ascii_offset++; + } + instance->mirror.ascii_offset = ascii_offset; + } +} + +void mf_ultralight_mirror_prepare_emulation(MfUltralightListener* instance) { + mf_ultralight_format_mirror_data( + instance->mirror.ascii_mirror_data, + instance->data->iso14443_3a_data->uid, + instance->data->iso14443_3a_data->uid_len); + + furi_string_push_back(instance->mirror.ascii_mirror_data, 'x'); + + mf_ultraligt_mirror_format_counter(instance); +} + +void mf_ultraligt_mirror_format_counter(MfUltralightListener* instance) { + furi_string_left( + instance->mirror.ascii_mirror_data, instance->data->iso14443_3a_data->uid_len * 2 + 1); + + uint8_t* c = instance->data->counter[2].data; + furi_string_cat_printf(instance->mirror.ascii_mirror_data, "%02X%02X%02X", c[2], c[1], c[0]); +} + +bool mf_ultralight_composite_command_in_progress(MfUltralightListener* instance) { + return (instance->composite_cmd.callback != NULL); +} + +MfUltralightCommand + mf_ultralight_composite_command_run(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = (instance->composite_cmd.callback)(instance, buffer); + mf_ultralight_composite_command_reset(instance); + return command; +} + +void mf_ultralight_composite_command_reset(MfUltralightListener* instance) { + instance->composite_cmd.callback = NULL; + instance->composite_cmd.data = 0; +} + +void mf_ultralight_composite_command_set_next( + MfUltralightListener* instance, + const MfUltralightListenerCommandCallback handler) { + instance->composite_cmd.callback = handler; +} + +void mf_ultralight_single_counter_try_increase(MfUltralightListener* instance) { + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportSingleCounter) && + instance->config->access.nfc_cnt_en && !instance->single_counter_increased) { + if(instance->data->counter[2].counter < MF_ULTRALIGHT_MAX_CNTR_VAL) { + instance->data->counter[2].counter++; + mf_ultraligt_mirror_format_counter(instance); + } + instance->single_counter_increased = true; + } +} + +void mf_ultralight_single_counter_try_to_unlock( + MfUltralightListener* instance, + Iso14443_3aListenerEventType type) { + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportSingleCounter) && + type == Iso14443_3aListenerEventTypeFieldOff) { + instance->single_counter_increased = false; + } +} + +static bool mf_ultralight_i2c_page_validator_for_sector0( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + UNUSED(type); + bool valid = false; + if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(start_page <= 0xE9 && end_page <= 0xE9) { + valid = true; + } else if( + MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(start_page) && + MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(end_page)) { + valid = true; + } + } else if(type == MfUltralightTypeNTAGI2C1K) { + if((start_page <= 0xE2) || MF_ULTRALIGHT_PAGE_IN_BOUNDS(start_page, 0x00E8, 0x00E9)) { + valid = true; + } + } else if(type == MfUltralightTypeNTAGI2C2K) { + valid = (start_page <= 0xFF && end_page <= 0xFF); + } + + return valid; +} + +static bool mf_ultralight_i2c_page_validator_for_sector1( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + bool valid = false; + if(type == MfUltralightTypeNTAGI2CPlus2K) { + valid = (start_page <= 0xFF && end_page <= 0xFF); + } else if(type == MfUltralightTypeNTAGI2C2K) { + valid = (MF_ULTRALIGHT_PAGE_IN_BOUNDS(start_page, 0x00E8, 0x00E9) || (start_page <= 0xE0)); + } else if(type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2CPlus1K) { + valid = false; + } + + return valid; +} + +static bool mf_ultralight_i2c_page_validator_for_sector2( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + UNUSED(start_page); + UNUSED(end_page); + UNUSED(type); + return false; +} + +static bool mf_ultralight_i2c_page_validator_for_sector3( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + UNUSED(type); + UNUSED(end_page); + return MF_ULTRALIGHT_I2C_PAGE_ON_MIRRORED_SESSION_REG(start_page); +} + +typedef bool ( + *MfUltralightI2CValidator)(uint16_t start_page, uint16_t end_page, MfUltralightType type); + +typedef uint16_t (*MfUltralightI2CPageProvider)(uint16_t page, MfUltralightType type); + +const MfUltralightI2CValidator validation_methods[] = { + mf_ultralight_i2c_page_validator_for_sector0, + mf_ultralight_i2c_page_validator_for_sector1, + mf_ultralight_i2c_page_validator_for_sector2, + mf_ultralight_i2c_page_validator_for_sector3, +}; + +bool mf_ultralight_i2c_validate_pages( + uint16_t start_page, + uint16_t end_page, + MfUltralightListener* instance) { + bool valid = false; + if(instance->sector < COUNT_OF(validation_methods)) { + MfUltralightI2CValidator validate = validation_methods[instance->sector]; + valid = validate(start_page, end_page, instance->data->type); + } + return valid; +} + +bool mf_ultralight_is_i2c_tag(MfUltralightType type) { + return type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2C2K || + type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector0(uint16_t page, MfUltralightType type) { + uint8_t new_page = page; + if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(page == 0x00EC) { + new_page = 234; + } else if(page == 0x00ED) { + new_page = 235; + } + } else if(type == MfUltralightTypeNTAGI2C1K) { + if(page == 0x00E8) { + new_page = 232; + } else if(page == 0x00E9) { + new_page = 233; + } + } else if(type == MfUltralightTypeNTAGI2C2K) { + new_page = page; + } + return new_page; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector1(uint16_t page, MfUltralightType type) { + UNUSED(type); + uint16_t new_page = page; + if(type == MfUltralightTypeNTAGI2CPlus2K) new_page = page + 236; + if(type == MfUltralightTypeNTAGI2C2K) new_page = page + 256; + return new_page; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector2(uint16_t page, MfUltralightType type) { + UNUSED(type); + return page; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector3(uint16_t page, MfUltralightType type) { + uint16_t new_page = page; + if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(page == 0x00F8) + new_page = 234; + else if(page == 0x00F9) + new_page = 235; + } else if(type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2C2K) { + if(page == 0x00F8) + new_page = (type == MfUltralightTypeNTAGI2C1K) ? 227 : 481; + else if(page == 0x00F9) + new_page = (type == MfUltralightTypeNTAGI2C1K) ? 228 : 482; + } + return new_page; +} + +const MfUltralightI2CPageProvider provider_methods[] = { + mf_ultralight_i2c_page_provider_for_sector0, + mf_ultralight_i2c_page_provider_for_sector1, + mf_ultralight_i2c_page_provider_for_sector2, + mf_ultralight_i2c_page_provider_for_sector3, +}; + +uint16_t + mf_ultralight_i2c_provide_page_by_requested(uint16_t page, MfUltralightListener* instance) { + uint16_t result = page; + if(instance->sector < COUNT_OF(provider_methods)) { + MfUltralightI2CPageProvider provider = provider_methods[instance->sector]; + result = provider(page, instance->data->type); + } + return result; +} + +void mf_ultralight_static_lock_bytes_prepare(MfUltralightListener* instance) { + instance->static_lock = (uint16_t*)&instance->data->page[2].data[2]; +} + +void mf_ultralight_static_lock_bytes_write( + MfUltralightStaticLockData* const lock_bits, + uint16_t new_bits) { + uint16_t current_locks = *lock_bits; + + if(MF_ULTRALIGHT_STATIC_BIT_OTP_CC_LOCKED(current_locks)) + MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_OTP_CC_MASK); + + if(MF_ULTRALIGHT_STATIC_BITS_9_4_LOCKED(current_locks)) + MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_9_4_MASK); + + if(MF_ULTRALIGHT_STATIC_BITS_15_10_LOCKED(current_locks)) + MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_15_10_MASK); + + MF_ULTRALIGHT_BITS_SET(current_locks, new_bits); + *lock_bits = current_locks; +} + +bool mf_ultralight_static_lock_check_page( + const MfUltralightStaticLockData* const lock_bits, + uint16_t page) { + bool locked = false; + if(MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x0003, 0x000F)) { + uint16_t current_locks = *lock_bits; + locked = MF_ULTRALIGHT_PAGE_LOCKED(current_locks, page); + } + return locked; +} + +void mf_ultralight_capability_container_write( + MfUltralightPage* const current_page, + const uint8_t* const new_data) { + for(uint8_t i = 0; i < MF_ULTRALIGHT_PAGE_SIZE; i++) { + current_page->data[i] |= new_data[i]; + } +} + +static uint16_t mf_ultralight_dynamic_lock_page_num(const MfUltralightData* data) { + uint16_t lock_page; + if(data->type == MfUltralightTypeNTAGI2C1K) + lock_page = 226; + else if(data->type == MfUltralightTypeNTAGI2C2K) + lock_page = 480; + else + lock_page = mf_ultralight_get_config_page_num(data->type) - 1; + return lock_page; +} + +void mf_ultralight_dynamic_lock_bytes_prepare(MfUltralightListener* instance) { + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock)) { + uint16_t lock_page = mf_ultralight_dynamic_lock_page_num(instance->data); + instance->dynamic_lock = (uint32_t*)instance->data->page[lock_page].data; + } else { + instance->dynamic_lock = NULL; + } +} + +bool mf_ultralight_is_page_dynamic_lock(const MfUltralightListener* instance, uint16_t page) { + bool is_lock = false; + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock)) { + uint16_t linear_page = page + instance->sector * 256; + + uint16_t lock_page = mf_ultralight_dynamic_lock_page_num(instance->data); + is_lock = linear_page == lock_page; + } + return is_lock; +} + +void mf_ultralight_dynamic_lock_bytes_write( + MfUltralightDynamicLockData* const lock_bits, + uint32_t new_bits) { + furi_assert(lock_bits != NULL); + new_bits &= 0x00FFFFFF; + uint32_t current_lock = *lock_bits; + for(uint8_t i = 0; i < 8; i++) { + uint8_t bl_bit = i + 16; + + if(MF_ULTRALIGHT_BIT_ACTIVE(current_lock, bl_bit)) { + uint8_t lock_bit = i * 2; + uint32_t mask = (1U << lock_bit) | (1U << (lock_bit + 1)); + MF_ULTRALIGHT_BITS_CLR(new_bits, mask); + } + } + MF_ULTRALIGHT_BITS_SET(current_lock, new_bits); + *lock_bits = current_lock; +} + +static uint8_t mf_ultralight_dynamic_lock_granularity(MfUltralightType type) { + switch(type) { + case MfUltralightTypeUL21: + case MfUltralightTypeNTAG213: + return 2; + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + return 16; + case MfUltralightTypeNTAGI2C2K: + case MfUltralightTypeNTAGI2CPlus2K: + return 32; + default: + return 1; + } +} + +static uint16_t mf_ultralight_get_upper_page_bound(MfUltralightType type) { + uint16_t upper_page_bound; + + if(type == MfUltralightTypeNTAGI2CPlus2K) + upper_page_bound = 511; + else if(type == MfUltralightTypeNTAGI2C2K) + upper_page_bound = 479; + else { + upper_page_bound = mf_ultralight_get_config_page_num(type) - 2; + } + + return upper_page_bound; +} + +bool mf_ultralight_dynamic_lock_check_page(const MfUltralightListener* instance, uint16_t page) { + UNUSED(page); + bool locked = false; + uint16_t upper_page_bound = mf_ultralight_get_upper_page_bound(instance->data->type); + uint16_t linear_page = page + instance->sector * 256; + + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock) && + MF_ULTRALIGHT_PAGE_IN_BOUNDS(linear_page, 0x0010, upper_page_bound)) { + uint8_t granularity = mf_ultralight_dynamic_lock_granularity(instance->data->type); + uint8_t bit = (linear_page - 16) / granularity; + uint16_t current_locks = *instance->dynamic_lock; + locked = MF_ULTRALIGHT_PAGE_LOCKED(current_locks, bit); + } + return locked; +} + +static bool mf_ultralight_auth_check_attempts(const MfUltralightListener* instance) { + uint8_t authlim = ((instance->data->type == MfUltralightTypeNTAGI2CPlus1K) || + (instance->data->type == MfUltralightTypeNTAGI2CPlus2K)) ? + (1U << instance->config->access.authlim) : + instance->config->access.authlim; + + return (instance->data->auth_attempts >= authlim); +} + +bool mf_ultralight_auth_limit_check_and_update(MfUltralightListener* instance, bool auth_success) { + bool card_locked = false; + + do { + if(instance->config->access.authlim == 0) break; + card_locked = mf_ultralight_auth_check_attempts(instance); + if(card_locked) break; + + if(auth_success) { + MF_ULTRALIGHT_AUTH_RESET_ATTEMPTS(instance); + } else { + MF_ULTRALIGHT_AUTH_INCREASE_ATTEMPTS(instance); + } + + card_locked = mf_ultralight_auth_check_attempts(instance); + } while(false); + + return card_locked; +} + +bool mf_ultralight_auth_check_password( + const MfUltralightAuthPassword* config_pass, + const MfUltralightAuthPassword* auth_pass) { + return memcmp(config_pass->data, auth_pass->data, sizeof(MfUltralightAuthPassword)) == 0; +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h new file mode 100644 index 0000000000..ba448d0879 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h @@ -0,0 +1,123 @@ +#pragma once + +#include "mf_ultralight_listener.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfUltralightListenerAuthStateIdle, + MfUltralightListenerAuthStateSuccess, +} MfUltralightListenerAuthState; + +typedef enum { + MfUltralightCommandNotFound, + MfUltralightCommandProcessed, + MfUltralightCommandProcessedACK, + MfUltralightCommandProcessedSilent, + MfUltralightCommandNotProcessedNAK, + MfUltralightCommandNotProcessedSilent, + MfUltralightCommandNotProcessedAuthNAK, +} MfUltralightCommand; + +typedef MfUltralightCommand ( + *MfUltralightListenerCommandCallback)(MfUltralightListener* instance, BitBuffer* buf); + +typedef uint8_t MfUltralightListenerCompositeCommandData; + +typedef struct { + MfUltralightListenerCompositeCommandData data; + MfUltralightListenerCommandCallback callback; +} MfUltralightListenerCompositeCommandContext; + +typedef struct { + uint8_t enabled; + uint8_t ascii_offset; + uint8_t ascii_end; + uint8_t mirror_last_page; + MfUltralightMirrorConf actual_mode; + FuriString* ascii_mirror_data; +} MfUltralightMirrorMode; + +typedef uint16_t MfUltralightStaticLockData; +typedef uint32_t MfUltralightDynamicLockData; + +struct MfUltralightListener { + Iso14443_3aListener* iso14443_3a_listener; + MfUltralightListenerAuthState auth_state; + MfUltralightData* data; + BitBuffer* tx_buffer; + MfUltralightFeatureSupport features; + MfUltralightConfigPages* config; + MfUltralightStaticLockData* static_lock; + MfUltralightDynamicLockData* dynamic_lock; + + NfcGenericEvent generic_event; + MfUltralightListenerEvent mfu_event; + MfUltralightListenerEventData mfu_event_data; + NfcGenericCallback callback; + uint8_t sector; + bool single_counter_increased; + MfUltralightMirrorMode mirror; + MfUltralightListenerCompositeCommandContext composite_cmd; + void* context; +}; + +void mf_ultralight_single_counter_try_increase(MfUltralightListener* instance); +void mf_ultralight_single_counter_try_to_unlock( + MfUltralightListener* instance, + Iso14443_3aListenerEventType type); + +void mf_ultralight_mirror_prepare_emulation(MfUltralightListener* instance); +void mf_ultraligt_mirror_format_counter(MfUltralightListener* instance); +void mf_ultralight_mirror_read_prepare(uint8_t start_page, MfUltralightListener* instance); +void mf_ultralight_mirror_read_handler( + uint8_t mirror_page_num, + uint8_t* dest, + MfUltralightListener* instance); + +void mf_ultralight_composite_command_set_next( + MfUltralightListener* instance, + const MfUltralightListenerCommandCallback handler); +void mf_ultralight_composite_command_reset(MfUltralightListener* instance); +bool mf_ultralight_composite_command_in_progress(MfUltralightListener* instance); +MfUltralightCommand + mf_ultralight_composite_command_run(MfUltralightListener* instance, BitBuffer* buffer); + +bool mf_ultralight_is_i2c_tag(MfUltralightType type); +bool mf_ultralight_i2c_validate_pages( + uint16_t start_page, + uint16_t end_page, + MfUltralightListener* instance); + +uint16_t + mf_ultralight_i2c_provide_page_by_requested(uint16_t page, MfUltralightListener* instance); + +void mf_ultralight_static_lock_bytes_prepare(MfUltralightListener* instance); +void mf_ultralight_static_lock_bytes_write( + MfUltralightStaticLockData* const lock_bits, + uint16_t new_bits); +bool mf_ultralight_static_lock_check_page( + const MfUltralightStaticLockData* const lock_bits, + uint16_t page); + +void mf_ultralight_capability_container_write( + MfUltralightPage* const current_page, + const uint8_t* const new_data); + +void mf_ultralight_dynamic_lock_bytes_prepare(MfUltralightListener* instance); +bool mf_ultralight_is_page_dynamic_lock(const MfUltralightListener* instance, uint16_t start_page); +void mf_ultralight_dynamic_lock_bytes_write( + MfUltralightDynamicLockData* const lock_bits, + uint32_t new_bits); +bool mf_ultralight_dynamic_lock_check_page(const MfUltralightListener* instance, uint16_t page); +bool mf_ultralight_auth_limit_check_and_update(MfUltralightListener* instance, bool auth_success); +bool mf_ultralight_auth_check_password( + const MfUltralightAuthPassword* config_pass, + const MfUltralightAuthPassword* auth_pass); +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c new file mode 100644 index 0000000000..bf0ced38d0 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -0,0 +1,591 @@ +#include "mf_ultralight_poller_i.h" + +#include + +#include + +#define TAG "MfUltralightPoller" + +typedef NfcCommand (*MfUltralightPollerReadHandler)(MfUltralightPoller* instance); + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 226: sector 0 + // 227 - 228: config registers + // 229 - 230: session registers + + if(lin_addr > 230) { + *pages_left = 0; + } else if(lin_addr >= 229) { + *sector = 3; + *pages_left = 2 - (lin_addr - 229); + *tag = lin_addr - 229 + 248; + tag_calculated = true; + } else if(lin_addr >= 227) { + *sector = 0; + *pages_left = 2 - (lin_addr - 227); + *tag = lin_addr - 227 + 232; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 227 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 255: sector 0 + // 256 - 480: sector 1 + // 481 - 482: config registers + // 483 - 484: session registers + + if(lin_addr > 484) { + *pages_left = 0; + } else if(lin_addr >= 483) { + *sector = 3; + *pages_left = 2 - (lin_addr - 483); + *tag = lin_addr - 483 + 248; + tag_calculated = true; + } else if(lin_addr >= 481) { + *sector = 1; + *pages_left = 2 - (lin_addr - 481); + *tag = lin_addr - 481 + 232; + tag_calculated = true; + } else if(lin_addr >= 256) { + *sector = 1; + *pages_left = 225 - (lin_addr - 256); + *tag = lin_addr - 256; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 256 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + + if(lin_addr > 235) { + *pages_left = 0; + } else if(lin_addr >= 234) { + *sector = 0; + *pages_left = 2 - (lin_addr - 234); + *tag = lin_addr - 234 + 236; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 234 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + // 236 - 491: sector 1 + + if(lin_addr > 491) { + *pages_left = 0; + } else if(lin_addr >= 236) { + *sector = 1; + *pages_left = 256 - (lin_addr - 236); + *tag = lin_addr - 236; + tag_calculated = true; + } else if(lin_addr >= 234) { + *sector = 0; + *pages_left = 2 - (lin_addr - 234); + *tag = lin_addr - 234 + 236; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 234 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( + MfUltralightPoller* instance, + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + furi_assert(instance); + furi_assert(sector); + furi_assert(tag); + furi_assert(pages_left); + + bool tag_calculated = false; + + if(instance->data->type == MfUltralightTypeNTAGI2C1K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k( + lin_addr, sector, tag, pages_left); + } else if(instance->data->type == MfUltralightTypeNTAGI2C2K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k( + lin_addr, sector, tag, pages_left); + } else if(instance->data->type == MfUltralightTypeNTAGI2CPlus1K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k( + lin_addr, sector, tag, pages_left); + } else if(instance->data->type == MfUltralightTypeNTAGI2CPlus2K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k( + lin_addr, sector, tag, pages_left); + } + + return tag_calculated; +} + +MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { + furi_assert(iso14443_3a_poller); + + MfUltralightPoller* instance = malloc(sizeof(MfUltralightPoller)); + instance->iso14443_3a_poller = iso14443_3a_poller; + instance->tx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE); + instance->rx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE); + instance->data = mf_ultralight_alloc(); + + instance->mfu_event.data = &instance->mfu_event_data; + + instance->general_event.protocol = NfcProtocolMfUltralight; + instance->general_event.event_data = &instance->mfu_event; + instance->general_event.instance = instance; + + return instance; +} + +void mf_ultralight_poller_free(MfUltralightPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + mf_ultralight_free(instance->data); + free(instance); +} + +static void mf_ultralight_poller_set_callback( + MfUltralightPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +const MfUltralightData* mf_ultralight_poller_get_data(MfUltralightPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + instance->counters_read = 0; + instance->counters_total = 3; + instance->tearing_flag_read = 0; + instance->tearing_flag_total = 3; + instance->pages_read = 0; + instance->state = MfUltralightPollerStateReadVersion; + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) { + instance->error = mf_ultralight_poller_async_read_version(instance, &instance->data->version); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Read version success"); + instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); + instance->state = MfUltralightPollerStateGetFeatureSet; + } else { + FURI_LOG_D(TAG, "Didn't response. Check Ultralight C"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->state = MfUltralightPollerStateDetectMfulC; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPoller* instance) { + instance->error = mf_ultralight_poller_async_authenticate(instance); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Ultralight C detected"); + instance->data->type = MfUltralightTypeMfulC; + instance->state = MfUltralightPollerStateGetFeatureSet; + } else { + FURI_LOG_D(TAG, "Didn't response. Check NTAG 203"); + instance->state = MfUltralightPollerStateDetectNtag203; + } + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) { + MfUltralightPageReadCommandData data = {}; + instance->error = mf_ultralight_poller_async_read_page(instance, 41, &data); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "NTAG203 detected"); + instance->data->type = MfUltralightTypeNTAG203; + } else { + FURI_LOG_D(TAG, "Original Ultralight detected"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->data->type = MfUltralightTypeUnknown; + } + instance->state = MfUltralightPollerStateGetFeatureSet; + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_get_feature_set(MfUltralightPoller* instance) { + instance->feature_set = mf_ultralight_get_feature_support_set(instance->data->type); + instance->pages_total = mf_ultralight_get_pages_total(instance->data->type); + instance->data->pages_total = instance->pages_total; + FURI_LOG_D( + TAG, + "%s detected. Total pages: %d", + mf_ultralight_get_device_name(instance->data, NfcDeviceNameTypeFull), + instance->pages_total); + + instance->state = MfUltralightPollerStateReadSignature; + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_signature(MfUltralightPoller* instance) { + MfUltralightPollerState next_state = MfUltralightPollerStateAuth; + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportReadSignature)) { + FURI_LOG_D(TAG, "Reading signature"); + instance->error = + mf_ultralight_poller_async_read_signature(instance, &instance->data->signature); + if(instance->error != MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Read signature failed"); + next_state = MfUltralightPollerStateReadFailed; + } + } else { + FURI_LOG_D(TAG, "Skip reading signature"); + } + instance->state = next_state; + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_counters(MfUltralightPoller* instance) { + do { + if(!mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportReadCounter) || + !mf_ultralight_is_counter_configured(instance->data)) { + FURI_LOG_D(TAG, "Skip reading counters"); + instance->state = MfUltralightPollerStateReadTearingFlags; + break; + } + + MfUltralightConfigPages* config = NULL; + mf_ultralight_get_config_page(instance->data, &config); + + if(config->access.nfc_cnt_pwd_prot && !instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Counter reading is protected with password"); + instance->state = MfUltralightPollerStateReadTearingFlags; + break; + } + + if(instance->counters_read == instance->counters_total) { + instance->state = MfUltralightPollerStateReadTearingFlags; + break; + } + + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportSingleCounter)) { + instance->counters_read = 2; + } + + FURI_LOG_D(TAG, "Reading counter %d", instance->counters_read); + instance->error = mf_ultralight_poller_async_read_counter( + instance, instance->counters_read, &instance->data->counter[instance->counters_read]); + if(instance->error != MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Failed to read %d counter", instance->counters_read); + instance->state = MfUltralightPollerStateReadTearingFlags; + } else { + instance->counters_read++; + } + + } while(false); + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_tearing_flags(MfUltralightPoller* instance) { + if(mf_ultralight_support_feature( + instance->feature_set, + MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportSingleCounter)) { + if(instance->tearing_flag_read == instance->tearing_flag_total) { + instance->state = MfUltralightPollerStateTryDefaultPass; + } else { + bool single_counter = mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportSingleCounter); + if(single_counter) instance->tearing_flag_read = 2; + + FURI_LOG_D(TAG, "Reading tearing flag %d", instance->tearing_flag_read); + instance->error = mf_ultralight_poller_async_read_tearing_flag( + instance, + instance->tearing_flag_read, + &instance->data->tearing_flag[instance->tearing_flag_read]); + if((instance->error == MfUltralightErrorProtocol) && single_counter) { + instance->tearing_flag_read++; + } else if(instance->error != MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Reading tearing flag %d failed", instance->tearing_flag_read); + instance->state = MfUltralightPollerStateTryDefaultPass; + } else { + instance->tearing_flag_read++; + } + } + } else { + FURI_LOG_D(TAG, "Skip reading tearing flags"); + instance->state = MfUltralightPollerStateTryDefaultPass; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportPasswordAuth)) { + instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; + + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->auth_context.skip_auth) { + instance->auth_context.password = instance->mfu_event.data->auth_context.password; + uint32_t pass = nfc_util_bytes2num( + instance->auth_context.password.data, sizeof(MfUltralightAuthPassword)); + FURI_LOG_D(TAG, "Trying to authenticate with password %08lX", pass); + instance->error = + mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Auth success"); + instance->auth_context.auth_success = true; + instance->mfu_event.data->auth_context = instance->auth_context; + instance->mfu_event.type = MfUltralightPollerEventTypeAuthSuccess; + command = instance->callback(instance->general_event, instance->context); + } else { + FURI_LOG_D(TAG, "Auth failed"); + instance->auth_context.auth_success = false; + instance->mfu_event.type = MfUltralightPollerEventTypeAuthFailed; + command = instance->callback(instance->general_event, instance->context); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + } + } + instance->state = MfUltralightPollerStateReadPages; + + return command; +} + +static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* instance) { + MfUltralightPageReadCommandData data = {}; + uint16_t start_page = instance->pages_read; + if(MF_ULTRALIGHT_IS_NTAG_I2C(instance->data->type)) { + uint8_t tag = 0; + uint8_t sector = 0; + uint8_t pages_left = 0; + if(mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( + instance, start_page, §or, &tag, &pages_left)) { + instance->error = + mf_ultralight_poller_async_read_page_from_sector(instance, sector, tag, &data); + } else { + FURI_LOG_D(TAG, "Failed to calculate sector and tag from %d page", start_page); + instance->error = MfUltralightErrorProtocol; + } + } else { + instance->error = mf_ultralight_poller_async_read_page(instance, start_page, &data); + } + + if(instance->error == MfUltralightErrorNone) { + for(size_t i = 0; i < 4; i++) { + if(start_page + i < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page + i); + instance->data->page[start_page + i] = data.page[i]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; + } + } + if(instance->pages_read == instance->pages_total) { + instance->state = MfUltralightPollerStateReadCounters; + } + } else { + FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read); + if(instance->pages_read) { + instance->state = MfUltralightPollerStateReadCounters; + } else { + instance->state = MfUltralightPollerStateReadFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoller* instance) { + do { + if(!mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportPasswordAuth)) + break; + + MfUltralightConfigPages* config = NULL; + mf_ultralight_get_config_page(instance->data, &config); + if(instance->auth_context.auth_success) { + config->password = instance->auth_context.password; + config->pack = instance->auth_context.pack; + } else if(config->access.authlim == 0) { + FURI_LOG_D(TAG, "No limits in authentication. Trying default password"); + nfc_util_num2bytes( + MF_ULTRALIGHT_DEFAULT_PASSWORD, + sizeof(MfUltralightAuthPassword), + instance->auth_context.password.data); + instance->error = + mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Default password detected"); + nfc_util_num2bytes( + MF_ULTRALIGHT_DEFAULT_PASSWORD, + sizeof(MfUltralightAuthPassword), + config->password.data); + config->pack = instance->auth_context.pack; + } + } + + if(instance->pages_read != instance->pages_total) { + // Probably password protected, fix AUTH0 and PROT so before AUTH0 + // can be written and since AUTH0 won't be readable, like on the + // original card + config->auth0 = instance->pages_read; + config->access.prot = true; + } + } while(false); + + instance->state = MfUltralightPollerStateReadSuccess; + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = MfUltralightPollerStateIdle; + return command; +} + +static NfcCommand mf_ultralight_poller_handler_read_success(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Read success"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.type = MfUltralightPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const MfUltralightPollerReadHandler + mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { + [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, + [MfUltralightPollerStateReadVersion] = mf_ultralight_poller_handler_read_version, + [MfUltralightPollerStateDetectMfulC] = mf_ultralight_poller_handler_check_ultralight_c, + [MfUltralightPollerStateDetectNtag203] = mf_ultralight_poller_handler_check_ntag_203, + [MfUltralightPollerStateGetFeatureSet] = mf_ultralight_poller_handler_get_feature_set, + [MfUltralightPollerStateReadSignature] = mf_ultralight_poller_handler_read_signature, + [MfUltralightPollerStateReadCounters] = mf_ultralight_poller_handler_read_counters, + [MfUltralightPollerStateReadTearingFlags] = + mf_ultralight_poller_handler_read_tearing_flags, + [MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth, + [MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass, + [MfUltralightPollerStateReadPages] = mf_ultralight_poller_handler_read_pages, + [MfUltralightPollerStateReadFailed] = mf_ultralight_poller_handler_read_fail, + [MfUltralightPollerStateReadSuccess] = mf_ultralight_poller_handler_read_success, + +}; + +static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + MfUltralightPoller* instance = context; + furi_assert(instance->callback); + + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + command = mf_ultralight_poller_read_handler[instance->state](instance); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + instance->mfu_event.type = MfUltralightPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool mf_ultralight_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + bool protocol_detected = false; + MfUltralightPoller* instance = context; + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + MfUltralightPageReadCommandData read_page_cmd_data = {}; + MfUltralightError error = + mf_ultralight_poller_async_read_page(instance, 0, &read_page_cmd_data); + protocol_detected = (error == MfUltralightErrorNone); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + + return protocol_detected; +} + +const NfcPollerBase mf_ultralight_poller = { + .alloc = (NfcPollerAlloc)mf_ultralight_poller_alloc, + .free = (NfcPollerFree)mf_ultralight_poller_free, + .set_callback = (NfcPollerSetCallback)mf_ultralight_poller_set_callback, + .run = (NfcPollerRun)mf_ultralight_poller_run, + .detect = (NfcPollerDetect)mf_ultralight_poller_detect, + .get_data = (NfcPollerGetData)mf_ultralight_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h new file mode 100644 index 0000000000..2d4ef33ea9 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -0,0 +1,41 @@ +#pragma once + +#include "mf_ultralight.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfUltralightPoller MfUltralightPoller; + +typedef enum { + MfUltralightPollerEventTypeAuthRequest, + MfUltralightPollerEventTypeAuthSuccess, + MfUltralightPollerEventTypeAuthFailed, + MfUltralightPollerEventTypeReadSuccess, + MfUltralightPollerEventTypeReadFailed, +} MfUltralightPollerEventType; + +typedef struct { + MfUltralightAuthPassword password; + MfUltralightAuthPack pack; + bool auth_success; + bool skip_auth; +} MfUltralightPollerAuthContext; + +typedef struct { + union { + MfUltralightPollerAuthContext auth_context; + MfUltralightError error; + }; +} MfUltralightPollerEventData; + +typedef struct { + MfUltralightPollerEventType type; + MfUltralightPollerEventData* data; +} MfUltralightPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h new file mode 100644 index 0000000000..80b0d7b6ea --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase mf_ultralight_poller; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c new file mode 100644 index 0000000000..795b03e65f --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -0,0 +1,308 @@ +#include "mf_ultralight_poller_i.h" + +#include + +#define TAG "MfUltralightPoller" + +MfUltralightError mf_ultralight_process_error(Iso14443_3aError error) { + MfUltralightError ret = MfUltralightErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = MfUltralightErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = MfUltralightErrorNotPresent; + break; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + ret = MfUltralightErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = MfUltralightErrorTimeout; + break; + default: + ret = MfUltralightErrorProtocol; + break; + } + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_auth_pwd( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data) { + uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009 + memccpy(&auth_cmd[1], data->password.data, 0, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); + bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd)); + + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + do { + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_AUTH_PACK_SIZE) { + ret = MfUltralightErrorAuth; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance) { + uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd)); + + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + do { + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if((bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_AUTH_RESPONSE_SIZE) && + (bit_buffer_get_byte(instance->rx_buffer, 0) != 0xAF)) { + ret = MfUltralightErrorAuth; + break; + } + //Save encrypted PICC random number RndB here if needed + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_page_from_sector( + MfUltralightPoller* instance, + uint8_t sector, + uint8_t tag, + MfUltralightPageReadCommandData* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + const uint8_t select_sector_cmd[2] = {MF_ULTRALIGHT_CMD_SECTOR_SELECT, 0xff}; + bit_buffer_copy_bytes(instance->tx_buffer, select_sector_cmd, sizeof(select_sector_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + FURI_LOG_D(TAG, "Failed to issue sector select command"); + ret = mf_ultralight_process_error(error); + break; + } + + const uint8_t read_sector_cmd[4] = {sector, 0x00, 0x00, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, read_sector_cmd, sizeof(read_sector_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorTimeout) { + // This is NOT a typo! The tag ACKs by not sending a response within 1ms. + FURI_LOG_D(TAG, "Sector %u select NAK'd", sector); + ret = MfUltralightErrorProtocol; + break; + } + + ret = mf_ultralight_poller_async_read_page(instance, tag, data); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_page( + MfUltralightPoller* instance, + uint8_t start_page, + MfUltralightPageReadCommandData* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t read_page_cmd[2] = {MF_ULTRALIGHT_CMD_READ_PAGE, start_page}; + bit_buffer_copy_bytes(instance->tx_buffer, read_page_cmd, sizeof(read_page_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != + sizeof(MfUltralightPageReadCommandData)) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightPageReadCommandData)); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_write_page( + MfUltralightPoller* instance, + uint8_t page, + MfUltralightPage* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t write_page_cmd[MF_ULTRALIGHT_PAGE_SIZE + 2] = {MF_ULTRALIGHT_CMD_WRITE_PAGE, page}; + memcpy(&write_page_cmd[2], data->data, MF_ULTRALIGHT_PAGE_SIZE); + bit_buffer_copy_bytes(instance->tx_buffer, write_page_cmd, sizeof(write_page_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_buffer) != 4) { + ret = MfUltralightErrorProtocol; + break; + } + if(!bit_buffer_starts_with_byte(instance->rx_buffer, MF_ULTRALIGHT_CMD_ACK)) { + ret = MfUltralightErrorProtocol; + break; + } + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_version( + MfUltralightPoller* instance, + MfUltralightVersion* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + const uint8_t get_version_cmd = MF_ULTRALIGHT_CMD_GET_VERSION; + bit_buffer_copy_bytes(instance->tx_buffer, &get_version_cmd, sizeof(get_version_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightVersion)) { + FURI_LOG_I( + TAG, "Read Version failed: %zu", bit_buffer_get_size_bytes(instance->rx_buffer)); + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightVersion)); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_signature( + MfUltralightPoller* instance, + MfUltralightSignature* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + const uint8_t read_signature_cmd[2] = {MF_ULTRALIGHT_CMD_READ_SIG, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, read_signature_cmd, sizeof(read_signature_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightSignature)) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightSignature)); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_counter( + MfUltralightPoller* instance, + uint8_t counter_num, + MfUltralightCounter* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t read_counter_cmd[2] = {MF_ULTRALIGHT_CMD_READ_CNT, counter_num}; + bit_buffer_copy_bytes(instance->tx_buffer, read_counter_cmd, sizeof(read_counter_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_COUNTER_SIZE) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data->data, MF_ULTRALIGHT_COUNTER_SIZE); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_tearing_flag( + MfUltralightPoller* instance, + uint8_t tearing_falg_num, + MfUltralightTearingFlag* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t check_tearing_cmd[2] = {MF_ULTRALIGHT_CMD_CHECK_TEARING, tearing_falg_num}; + bit_buffer_copy_bytes(instance->tx_buffer, check_tearing_cmd, sizeof(check_tearing_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightTearingFlag)) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightTearingFlag)); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h new file mode 100644 index 0000000000..13490cf1a9 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -0,0 +1,148 @@ +#pragma once + +#include "mf_ultralight_poller.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC (60000) +#define MF_ULTRALIGHT_MAX_BUFF_SIZE (64) + +#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) + +#define MF_ULTRALIGHT_IS_NTAG_I2C(type) \ + (((type) == MfUltralightTypeNTAGI2C1K) || ((type) == MfUltralightTypeNTAGI2C2K) || \ + ((type) == MfUltralightTypeNTAGI2CPlus1K) || ((type) == MfUltralightTypeNTAGI2CPlus2K)) + +typedef struct { + MfUltralightPage page; + uint8_t page_to_write; +} MfUltralightPollerWritePageCommand; + +typedef struct { + MfUltralightPageReadCommandData data; + uint8_t start_page; +} MfUltralightPollerReadPageCommand; + +typedef struct { + MfUltralightCounter data; + uint8_t counter_num; +} MfUltralightPollerReadCounterCommand; + +typedef struct { + MfUltralightTearingFlag data; + uint8_t tearing_flag_num; +} MfUltralightPollerReadTearingFlagCommand; + +typedef union { + MfUltralightPollerWritePageCommand write_cmd; + MfUltralightPollerReadPageCommand read_cmd; + MfUltralightVersion version; + MfUltralightSignature signature; + MfUltralightPollerReadCounterCommand counter_cmd; + MfUltralightPollerReadTearingFlagCommand tearing_flag_cmd; + MfUltralightData* data; +} MfUltralightPollerContextData; + +typedef enum { + MfUltralightPollerStateIdle, + MfUltralightPollerStateReadVersion, + MfUltralightPollerStateDetectMfulC, + MfUltralightPollerStateDetectNtag203, + MfUltralightPollerStateGetFeatureSet, + MfUltralightPollerStateReadSignature, + MfUltralightPollerStateReadCounters, + MfUltralightPollerStateReadTearingFlags, + MfUltralightPollerStateAuth, + MfUltralightPollerStateReadPages, + MfUltralightPollerStateTryDefaultPass, + MfUltralightPollerStateReadFailed, + MfUltralightPollerStateReadSuccess, + + MfUltralightPollerStateNum, +} MfUltralightPollerState; + +struct MfUltralightPoller { + Iso14443_3aPoller* iso14443_3a_poller; + MfUltralightPollerState state; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + MfUltralightData* data; + MfUltralightPollerAuthContext auth_context; + uint32_t feature_set; + uint16_t pages_read; + uint16_t pages_total; + uint8_t counters_read; + uint8_t counters_total; + uint8_t tearing_flag_read; + uint8_t tearing_flag_total; + MfUltralightError error; + + NfcGenericEvent general_event; + MfUltralightPollerEvent mfu_event; + MfUltralightPollerEventData mfu_event_data; + NfcGenericCallback callback; + void* context; +}; + +MfUltralightError mf_ultralight_process_error(Iso14443_3aError error); + +MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller); + +void mf_ultralight_poller_free(MfUltralightPoller* instance); + +const MfUltralightData* mf_ultralight_poller_get_data(MfUltralightPoller* instance); + +bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( + MfUltralightPoller* instance, + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left); + +MfUltralightError mf_ultralight_poller_async_auth_pwd( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data); + +MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance); + +MfUltralightError mf_ultralight_poller_async_read_page( + MfUltralightPoller* instance, + uint8_t start_page, + MfUltralightPageReadCommandData* data); + +MfUltralightError mf_ultralight_poller_async_read_page_from_sector( + MfUltralightPoller* instance, + uint8_t sector, + uint8_t tag, + MfUltralightPageReadCommandData* data); + +MfUltralightError mf_ultralight_poller_async_write_page( + MfUltralightPoller* instance, + uint8_t page, + MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_async_read_version( + MfUltralightPoller* instance, + MfUltralightVersion* data); + +MfUltralightError mf_ultralight_poller_async_read_signature( + MfUltralightPoller* instance, + MfUltralightSignature* data); + +MfUltralightError mf_ultralight_poller_async_read_counter( + MfUltralightPoller* instance, + uint8_t counter_num, + MfUltralightCounter* data); + +MfUltralightError mf_ultralight_poller_async_read_tearing_flag( + MfUltralightPoller* instance, + uint8_t tearing_falg_num, + MfUltralightTearingFlag* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c new file mode 100644 index 0000000000..739df597d2 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c @@ -0,0 +1,287 @@ +#include "mf_ultralight_poller_i.h" + +#include + +#include + +#define MF_ULTRALIGHT_POLLER_COMPLETE_EVENT (1UL << 0) + +typedef enum { + MfUltralightPollerCmdTypeReadPage, + MfUltralightPollerCmdTypeWritePage, + MfUltralightPollerCmdTypeReadVersion, + MfUltralightPollerCmdTypeReadSignature, + MfUltralightPollerCmdTypeReadCounter, + MfUltralightPollerCmdTypeReadTearingFlag, + + MfUltralightPollerCmdTypeNum, +} MfUltralightPollerCmdType; + +typedef struct { + MfUltralightPollerCmdType cmd_type; + FuriThreadId thread_id; + MfUltralightError error; + MfUltralightPollerContextData data; +} MfUltralightPollerContext; + +typedef MfUltralightError (*MfUltralightPollerCmdHandler)( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data); + +MfUltralightError mf_ultralight_poller_read_page_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_page( + poller, data->read_cmd.start_page, &data->read_cmd.data); +} + +MfUltralightError mf_ultralight_poller_write_page_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_write_page( + poller, data->write_cmd.page_to_write, &data->write_cmd.page); +} + +MfUltralightError mf_ultralight_poller_read_version_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_version(poller, &data->version); +} + +MfUltralightError mf_ultralight_poller_read_signature_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_signature(poller, &data->signature); +} + +MfUltralightError mf_ultralight_poller_read_counter_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_counter( + poller, data->counter_cmd.counter_num, &data->counter_cmd.data); +} + +MfUltralightError mf_ultralight_poller_read_tearing_flag_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_tearing_flag( + poller, data->tearing_flag_cmd.tearing_flag_num, &data->tearing_flag_cmd.data); +} + +static const MfUltralightPollerCmdHandler + mf_ultralight_poller_cmd_handlers[MfUltralightPollerCmdTypeNum] = { + [MfUltralightPollerCmdTypeReadPage] = mf_ultralight_poller_read_page_handler, + [MfUltralightPollerCmdTypeWritePage] = mf_ultralight_poller_write_page_handler, + [MfUltralightPollerCmdTypeReadVersion] = mf_ultralight_poller_read_version_handler, + [MfUltralightPollerCmdTypeReadSignature] = mf_ultralight_poller_read_signature_handler, + [MfUltralightPollerCmdTypeReadCounter] = mf_ultralight_poller_read_counter_handler, + [MfUltralightPollerCmdTypeReadTearingFlag] = + mf_ultralight_poller_read_tearing_flag_handler, +}; + +static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEvent event, void* context) { + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(context); + + MfUltralightPollerContext* poller_context = context; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + Iso14443_3aPoller* iso14443_3a_poller = event.instance; + MfUltralightPoller* mfu_poller = mf_ultralight_poller_alloc(iso14443_3a_poller); + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + poller_context->error = mf_ultralight_poller_cmd_handlers[poller_context->cmd_type]( + mfu_poller, &poller_context->data); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + poller_context->error = mf_ultralight_process_error(iso14443_3a_event->data->error); + } + + furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + + mf_ultralight_poller_free(mfu_poller); + + return NfcCommandStop; +} + +static MfUltralightError + mf_ultralight_poller_cmd_execute(Nfc* nfc, MfUltralightPollerContext* poller_ctx) { + furi_assert(poller_ctx->cmd_type < MfUltralightPollerCmdTypeNum); + + poller_ctx->thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + nfc_poller_start(poller, mf_ultralight_poller_cmd_callback, poller_ctx); + furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_ctx->error; +} + +MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadPage, + .data.read_cmd.start_page = page, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.read_cmd.data.page[0]; + } + + return error; +} + +MfUltralightError + mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeWritePage, + .data.write_cmd = + { + .page_to_write = page, + .page = *data, + }, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + return error; +} + +MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadVersion, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.version; + } + + return error; +} + +MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadSignature, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.signature; + } + + return error; +} + +MfUltralightError + mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadCounter, + .data.counter_cmd.counter_num = counter_num, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.counter_cmd.data; + } + + return error; +} + +MfUltralightError mf_ultralight_poller_read_tearing_flag( + Nfc* nfc, + uint8_t flag_num, + MfUltralightTearingFlag* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadTearingFlag, + .data.tearing_flag_cmd.tearing_flag_num = flag_num, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.tearing_flag_cmd.data; + } + + return error; +} + +static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.instance); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + + NfcCommand command = NfcCommandContinue; + MfUltralightPollerContext* poller_context = context; + MfUltralightPoller* mfu_poller = event.instance; + MfUltralightPollerEvent* mfu_event = event.event_data; + + if(mfu_event->type == MfUltralightPollerEventTypeReadSuccess) { + mf_ultralight_copy(poller_context->data.data, mf_ultralight_poller_get_data(mfu_poller)); + poller_context->error = MfUltralightErrorNone; + command = NfcCommandStop; + } else if(mfu_event->type == MfUltralightPollerEventTypeReadFailed) { + poller_context->error = mfu_event->data->error; + command = NfcCommandStop; + } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { + mfu_event->data->auth_context.skip_auth = true; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + } + + return command; +} + +MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = {}; + poller_context.thread_id = furi_thread_get_current_id(); + poller_context.data.data = mf_ultralight_alloc(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight); + nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context); + furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + if(poller_context.error == MfUltralightErrorNone) { + mf_ultralight_copy(data, poller_context.data.data); + } + + mf_ultralight_free(poller_context.data.data); + + return poller_context.error; +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h new file mode 100644 index 0000000000..a0124ae092 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h @@ -0,0 +1,30 @@ +#pragma once + +#include "mf_ultralight.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data); + +MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data); + +MfUltralightError + mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data); + +MfUltralightError mf_ultralight_poller_read_tearing_flag( + Nfc* nfc, + uint8_t flag_num, + MfUltralightTearingFlag* data); + +MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c deleted file mode 100644 index a72d4aac0c..0000000000 --- a/lib/nfc/protocols/mifare_classic.c +++ /dev/null @@ -1,1626 +0,0 @@ -#include "mifare_classic.h" -#include "nfca.h" -#include "nfc_util.h" -#include - -// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git - -#define TAG "MfClassic" - -#define MF_CLASSIC_ACK_CMD 0xAU -#define MF_CLASSIC_NACK_BUF_VALID_CMD 0x0U -#define MF_CLASSIC_NACK_BUF_INVALID_CMD 0x4U -#define MF_CLASSIC_AUTH_KEY_A_CMD 0x60U -#define MF_CLASSIC_AUTH_KEY_B_CMD 0x61U -#define MF_CLASSIC_READ_BLOCK_CMD 0x30U -#define MF_CLASSIC_WRITE_BLOCK_CMD 0xA0U -#define MF_CLASSIC_TRANSFER_CMD 0xB0U -#define MF_CLASSIC_DECREMENT_CMD 0xC0U -#define MF_CLASSIC_INCREMENT_CMD 0xC1U -#define MF_CLASSIC_RESTORE_CMD 0xC2U - -const char* mf_classic_get_type_str(MfClassicType type) { - if(type == MfClassicTypeMini) { - return "MIFARE Mini 0.3K"; - } else if(type == MfClassicType1k) { - return "MIFARE Classic 1K"; - } else if(type == MfClassicType4k) { - return "MIFARE Classic 4K"; - } else { - return "Unknown"; - } -} - -static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { - furi_assert(sector < 40); - if(sector < 32) { - return sector * 4; - } else { - return 32 * 4 + (sector - 32) * 16; - } -} - -uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector) { - furi_assert(sector < 40); - if(sector < 32) { - return sector * 4 + 3; - } else { - return 32 * 4 + (sector - 32) * 16 + 15; - } -} - -uint8_t mf_classic_get_sector_by_block(uint8_t block) { - if(block < 128) { - return (block | 0x03) / 4; - } else { - return 32 + ((block | 0xf) - 32 * 4) / 16; - } -} - -static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { - furi_assert(sector < 40); - return sector < 32 ? 4 : 16; -} - -uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) { - if(block < 128) { - return block | 0x03; - } else { - return block | 0x0f; - } -} - -bool mf_classic_is_sector_trailer(uint8_t block) { - return block == mf_classic_get_sector_trailer_num_by_block(block); -} - -MfClassicSectorTrailer* - mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector) { - furi_assert(data); - uint8_t sec_tr_block_num = mf_classic_get_sector_trailer_block_num_by_sector(sector); - return (MfClassicSectorTrailer*)data->block[sec_tr_block_num].value; -} - -uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { - if(type == MfClassicTypeMini) { - return MF_MINI_TOTAL_SECTORS_NUM; - } else if(type == MfClassicType1k) { - return MF_CLASSIC_1K_TOTAL_SECTORS_NUM; - } else if(type == MfClassicType4k) { - return MF_CLASSIC_4K_TOTAL_SECTORS_NUM; - } else { - return 0; - } -} - -uint16_t mf_classic_get_total_block_num(MfClassicType type) { - if(type == MfClassicTypeMini) { - return 20; - } else if(type == MfClassicType1k) { - return 64; - } else if(type == MfClassicType4k) { - return 256; - } else { - return 0; - } -} - -bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num) { - furi_assert(data); - - return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1); -} - -void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) { - furi_assert(data); - - if(mf_classic_is_sector_trailer(block_num)) { - memcpy(&data->block[block_num].value[6], &block_data->value[6], 4); - } else { - memcpy(data->block[block_num].value, block_data->value, MF_CLASSIC_BLOCK_SIZE); - } - FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); -} - -bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num) { - furi_assert(data); - - uint8_t first_block = mf_classic_get_first_block_num_of_sector(sector_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); - bool data_read = true; - for(size_t i = first_block; i < first_block + total_blocks; i++) { - data_read &= mf_classic_is_block_read(data, i); - } - - return data_read; -} - -void mf_classic_set_sector_data_not_read(MfClassicData* data) { - furi_assert(data); - memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); -} - -bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { - furi_assert(data); - - bool key_found = false; - if(key_type == MfClassicKeyA) { - key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1); - } else if(key_type == MfClassicKeyB) { - key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1); - } - - return key_found; -} - -void mf_classic_set_key_found( - MfClassicData* data, - uint8_t sector_num, - MfClassicKey key_type, - uint64_t key) { - furi_assert(data); - - uint8_t key_arr[6] = {}; - MfClassicSectorTrailer* sec_trailer = - mf_classic_get_sector_trailer_by_sector(data, sector_num); - nfc_util_num2bytes(key, 6, key_arr); - if(key_type == MfClassicKeyA) { - memcpy(sec_trailer->key_a, key_arr, sizeof(sec_trailer->key_a)); - FURI_BIT_SET(data->key_a_mask, sector_num); - } else if(key_type == MfClassicKeyB) { - memcpy(sec_trailer->key_b, key_arr, sizeof(sec_trailer->key_b)); - FURI_BIT_SET(data->key_b_mask, sector_num); - } -} - -void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { - furi_assert(data); - - if(key_type == MfClassicKeyA) { - FURI_BIT_CLEAR(data->key_a_mask, sector_num); - } else if(key_type == MfClassicKeyB) { - FURI_BIT_CLEAR(data->key_b_mask, sector_num); - } -} - -bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) { - furi_assert(data); - - bool sector_read = false; - do { - if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) break; - if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) break; - uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); - uint8_t block_read = true; - for(size_t i = start_block; i < start_block + total_blocks; i++) { - block_read = mf_classic_is_block_read(data, i); - if(!block_read) break; - } - sector_read = block_read; - } while(false); - - return sector_read; -} - -void mf_classic_get_read_sectors_and_keys( - MfClassicData* data, - uint8_t* sectors_read, - uint8_t* keys_found) { - furi_assert(data); - furi_assert(sectors_read); - furi_assert(keys_found); - - *sectors_read = 0; - *keys_found = 0; - uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); - for(size_t i = 0; i < sectors_total; i++) { - if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { - *keys_found += 1; - } - if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { - *keys_found += 1; - } - uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); - uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i); - bool blocks_read = true; - for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) { - blocks_read = mf_classic_is_block_read(data, j); - if(!blocks_read) break; - } - if(blocks_read) { - *sectors_read += 1; - } - } -} - -bool mf_classic_is_card_read(MfClassicData* data) { - furi_assert(data); - - uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); - uint8_t sectors_read = 0; - uint8_t keys_found = 0; - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2); - - return card_read; -} - -bool mf_classic_is_allowed_access_sector_trailer( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action) { - uint8_t* sector_trailer = data->block[block_num].value; - uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | - ((sector_trailer[8] >> 7) & 0x01); - switch(action) { - case MfClassicActionKeyARead: { - return false; - } - case MfClassicActionKeyAWrite: - case MfClassicActionKeyBWrite: { - return ( - (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || - (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); - } - case MfClassicActionKeyBRead: { - return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); - } - case MfClassicActionACRead: { - return ( - (key == MfClassicKeyA) || - (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); - } - case MfClassicActionACWrite: { - return ( - (key == MfClassicKeyA && (AC == 0x01)) || - (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05))); - } - default: - return false; - } - return true; -} - -bool mf_classic_is_allowed_access_data_block( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action) { - uint8_t* sector_trailer = - data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; - - if(block_num == 0 && action == MfClassicActionDataWrite) { - return false; - } - - uint8_t sector_block; - if(block_num <= 128) { - sector_block = block_num & 0x03; - } else { - sector_block = (block_num & 0x0f) / 5; - } - - uint8_t AC; - switch(sector_block) { - case 0x00: { - AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) | - ((sector_trailer[8] >> 4) & 0x01); - break; - } - case 0x01: { - AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) | - ((sector_trailer[8] >> 5) & 0x01); - break; - } - case 0x02: { - AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) | - ((sector_trailer[8] >> 6) & 0x01); - break; - } - default: - return false; - } - - switch(action) { - case MfClassicActionDataRead: { - return ( - (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || - (key == MfClassicKeyB && !(AC == 0x07))); - } - case MfClassicActionDataWrite: { - return ( - (key == MfClassicKeyA && (AC == 0x00)) || - (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); - } - case MfClassicActionDataInc: { - return ( - (key == MfClassicKeyA && (AC == 0x00)) || - (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06))); - } - case MfClassicActionDataDec: { - return ( - (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || - (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); - } - default: - return false; - } - - return false; -} - -static bool mf_classic_is_allowed_access( - MfClassicEmulator* emulator, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action) { - if(mf_classic_is_sector_trailer(block_num)) { - return mf_classic_is_allowed_access_sector_trailer( - &emulator->data, block_num, key, action); - } else { - return mf_classic_is_allowed_access_data_block(&emulator->data, block_num, key, action); - } -} - -bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num) { - // Check if key A can write, if it can, it's transport configuration, not data block - return !mf_classic_is_allowed_access_data_block( - data, block_num, MfClassicKeyA, MfClassicActionDataWrite) && - (mf_classic_is_allowed_access_data_block( - data, block_num, MfClassicKeyB, MfClassicActionDataInc) || - mf_classic_is_allowed_access_data_block( - data, block_num, MfClassicKeyB, MfClassicActionDataDec)); -} - -bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { - return true; - } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { - //skylanders support - return true; - } else if( - ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || - ((ATQA0 == 0x02 || ATQA0 == 0x04 || ATQA0 == 0x08) && (SAK == 0x38))) { - return true; - } else { - return false; - } -} - -MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04)) { - if((SAK == 0x08 || SAK == 0x88)) { - return MfClassicType1k; - } else if((SAK == 0x38)) { - return MfClassicType4k; - } else if(SAK == 0x09) { - return MfClassicTypeMini; - } - } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { - //skylanders support - return MfClassicType1k; - } else if( - ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || - ((ATQA0 == 0x02 || ATQA0 == 0x08) && (SAK == 0x38))) { - return MfClassicType4k; - } - return MfClassicType1k; -} - -void mf_classic_reader_add_sector( - MfClassicReader* reader, - uint8_t sector, - uint64_t key_a, - uint64_t key_b) { - furi_assert(reader); - furi_assert(sector < MF_CLASSIC_SECTORS_MAX); - furi_assert((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)); - - if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX) { - reader->sector_reader[reader->sectors_to_read].key_a = key_a; - reader->sector_reader[reader->sectors_to_read].key_b = key_b; - reader->sector_reader[reader->sectors_to_read].sector_num = sector; - reader->sectors_to_read++; - } -} - -bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr) { - uint32_t v = *(uint32_t*)&block[0]; - uint32_t v_inv = *(uint32_t*)&block[4]; - uint32_t v1 = *(uint32_t*)&block[8]; - - bool val_checks = - ((v == v1) && (v == ~v_inv) && (block[12] == (~block[13] & 0xFF)) && - (block[14] == (~block[15] & 0xFF)) && (block[12] == block[14])); - if(value) { - *value = (int32_t)v; - } - if(addr) { - *addr = block[12]; - } - return val_checks; -} - -void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block) { - uint32_t v_inv = ~((uint32_t)value); - - memcpy(block, &value, 4); //-V1086 - memcpy(block + 4, &v_inv, 4); //-V1086 - memcpy(block + 8, &value, 4); //-V1086 - - block[12] = addr; - block[13] = ~addr & 0xFF; - block[14] = addr; - block[15] = ~addr & 0xFF; -} - -void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) { - furi_assert(auth_ctx); - auth_ctx->sector = sector; - auth_ctx->key_a = MF_CLASSIC_NO_KEY; - auth_ctx->key_b = MF_CLASSIC_NO_KEY; -} - -static bool mf_classic_auth( - FuriHalNfcTxRxContext* tx_rx, - uint32_t block, - uint64_t key, - MfClassicKey key_type, - Crypto1* crypto, - bool skip_activate, - uint32_t cuid) { - bool auth_success = false; - memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); - memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - do { - if(!skip_activate && !furi_hal_nfc_activate_nfca(200, &cuid)) break; - if(key_type == MfClassicKeyA) { - tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD; - } else { - tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_B_CMD; - } - tx_rx->tx_data[1] = block; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc; - tx_rx->tx_bits = 2 * 8; - if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; - - uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4); - crypto1_init(crypto, key); - crypto1_word(crypto, nt ^ cuid, 0); - uint8_t nr[4] = {}; - nfc_util_num2bytes(prng_successor(DWT->CYCCNT, 32), 4, nr); - for(uint8_t i = 0; i < 4; i++) { - tx_rx->tx_data[i] = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; - tx_rx->tx_parity[0] |= - (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01) << (7 - i)); - } - nt = prng_successor(nt, 32); - for(uint8_t i = 4; i < 8; i++) { - nt = prng_successor(nt, 8); - tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ (nt & 0xff); - tx_rx->tx_parity[0] |= - (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt & 0xff)) & 0x01) - << (7 - i)); - } - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - tx_rx->tx_bits = 8 * 8; - if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; - if(tx_rx->rx_bits == 32) { - crypto1_word(crypto, 0, 0); - auth_success = true; - } - } while(false); - - return auth_success; -} - -bool mf_classic_authenticate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type) { - furi_assert(tx_rx); - - Crypto1 crypto = {}; - bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0); - furi_hal_nfc_sleep(); - return key_found; -} - -bool mf_classic_authenticate_skip_activate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type, - bool skip_activate, - uint32_t cuid) { - furi_assert(tx_rx); - - Crypto1 crypto = {}; - bool key_found = - mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, skip_activate, cuid); - furi_hal_nfc_sleep(); - return key_found; -} - -bool mf_classic_auth_attempt( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - MfClassicAuthContext* auth_ctx, - uint64_t key) { - furi_assert(tx_rx); - furi_assert(auth_ctx); - bool found_key = false; - bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) && - (auth_ctx->key_b == MF_CLASSIC_NO_KEY); - - if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) { - // Try AUTH with key A - if(mf_classic_auth( - tx_rx, - mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), - key, - MfClassicKeyA, - crypto, - false, - 0)) { - auth_ctx->key_a = key; - found_key = true; - } - } - - if(need_halt) { - furi_hal_nfc_sleep(); - } - - if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) { - // Try AUTH with key B - if(mf_classic_auth( - tx_rx, - mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), - key, - MfClassicKeyB, - crypto, - false, - 0)) { - auth_ctx->key_b = key; - found_key = true; - } - } - - return found_key; -} - -bool mf_classic_read_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* block) { - furi_assert(tx_rx); - furi_assert(crypto); - furi_assert(block); - - bool read_block_success = false; - uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00}; - nfca_append_crc16(plain_cmd, 2); - - crypto1_encrypt( - crypto, NULL, plain_cmd, sizeof(plain_cmd) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(plain_cmd) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { - uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; - crypto1_decrypt(crypto, tx_rx->rx_data, tx_rx->rx_bits, block_received); - uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); - uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | - block_received[MF_CLASSIC_BLOCK_SIZE]; - if(crc_received != crc_calc) { - FURI_LOG_E( - TAG, - "Incorrect CRC while reading block %d. Expected %04X, Received %04X", - block_num, - crc_received, - crc_calc); - } else { - memcpy(block->value, block_received, MF_CLASSIC_BLOCK_SIZE); - read_block_success = true; - } - } - } - return read_block_success; -} - -void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num) { - furi_assert(tx_rx); - furi_assert(data); - - furi_hal_nfc_sleep(); - bool key_a_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyA); - bool key_b_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyB); - uint8_t start_block = mf_classic_get_first_block_num_of_sector(sec_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); - MfClassicBlock block_tmp = {}; - uint64_t key = 0; - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num); - Crypto1 crypto = {}; - - uint8_t blocks_read = 0; - do { - if(!key_a_found) break; - FURI_LOG_D(TAG, "Try to read blocks with key A"); - key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); - FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); - break; - } - - for(size_t i = start_block; i < start_block + total_blocks; i++) { - if(!mf_classic_is_block_read(data, i)) { - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } else if(i > start_block) { - // Try to re-auth to read block in case prevous block was protected from read - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); - FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); - break; - } - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } - } - } else { - blocks_read++; - } - } - FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); - } while(false); - do { - if(blocks_read == total_blocks) break; - if(!key_b_found) break; - if(key_a_found) { - furi_hal_nfc_sleep(); - } - FURI_LOG_D(TAG, "Try to read blocks with key B"); - key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); - FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); - break; - } - - for(size_t i = start_block; i < start_block + total_blocks; i++) { - if(!mf_classic_is_block_read(data, i)) { - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } else if(i > start_block) { - // Try to re-auth to read block in case prevous block was protected from read - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); - FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); - break; - } - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } - } - } else { - blocks_read++; - } - } - FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); - } while(false); -} - -static bool mf_classic_read_sector_with_reader( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - MfClassicSectorReader* sector_reader, - MfClassicSector* sector) { - furi_assert(tx_rx); - furi_assert(sector_reader); - furi_assert(sector); - - uint64_t key; - MfClassicKey key_type; - uint8_t first_block; - bool sector_read = false; - - furi_hal_nfc_sleep(); - do { - // Activate card - first_block = mf_classic_get_first_block_num_of_sector(sector_reader->sector_num); - if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { - key = sector_reader->key_a; - key_type = MfClassicKeyA; - } else if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { - key = sector_reader->key_b; - key_type = MfClassicKeyB; - } else { - break; - } - - // Auth to first block in sector - if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto, false, 0)) { - // Set key to MF_CLASSIC_NO_KEY to prevent further attempts - if(key_type == MfClassicKeyA) { - sector_reader->key_a = MF_CLASSIC_NO_KEY; - } else { - sector_reader->key_b = MF_CLASSIC_NO_KEY; - } - break; - } - sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num); - - // Read blocks - for(uint8_t i = 0; i < sector->total_blocks; i++) { - if(mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i])) continue; - if(i == 0) continue; - // Try to auth to read next block in case previous is locked - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, first_block + i, key, key_type, crypto, false, 0)) continue; - mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i]); - } - // Save sector keys in last block - if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { - nfc_util_num2bytes( - sector_reader->key_a, 6, §or->block[sector->total_blocks - 1].value[0]); - } - if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { - nfc_util_num2bytes( - sector_reader->key_b, 6, §or->block[sector->total_blocks - 1].value[10]); - } - - sector_read = true; - } while(false); - - return sector_read; -} - -uint8_t mf_classic_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfClassicReader* reader, - MfClassicData* data) { - furi_assert(tx_rx); - furi_assert(reader); - furi_assert(data); - - uint8_t sectors_read = 0; - data->type = reader->type; - data->key_a_mask = 0; - data->key_b_mask = 0; - MfClassicSector temp_sector = {}; - for(uint8_t i = 0; i < reader->sectors_to_read; i++) { - if(mf_classic_read_sector_with_reader( - tx_rx, &reader->crypto, &reader->sector_reader[i], &temp_sector)) { - uint8_t first_block = - mf_classic_get_first_block_num_of_sector(reader->sector_reader[i].sector_num); - for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { - mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); - } - if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) { - mf_classic_set_key_found( - data, - reader->sector_reader[i].sector_num, - MfClassicKeyA, - reader->sector_reader[i].key_a); - } - if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) { - mf_classic_set_key_found( - data, - reader->sector_reader[i].sector_num, - MfClassicKeyB, - reader->sector_reader[i].key_b); - } - sectors_read++; - } - } - - return sectors_read; -} - -uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) { - furi_assert(tx_rx); - furi_assert(data); - - uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); - - for(size_t i = 0; i < total_sectors; i++) { - mf_classic_read_sector(tx_rx, data, i); - } - uint8_t sectors_read = 0; - uint8_t keys_found = 0; - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - FURI_LOG_D(TAG, "Read %d sectors and %d keys", sectors_read, keys_found); - - return sectors_read; -} - -bool mf_classic_emulator( - MfClassicEmulator* emulator, - FuriHalNfcTxRxContext* tx_rx, - bool is_reader_analyzer) { - furi_assert(emulator); - furi_assert(tx_rx); - uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; - MfClassicKey access_key = MfClassicKeyA; - bool need_reset = false; - bool need_nack = false; - bool is_encrypted = false; - uint8_t sector = 0; - - // Used for decrement and increment - copy to block on transfer - uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE]; - bool transfer_buf_valid = false; - - // Process commands - while(!need_reset && !need_nack) { //-V654 - memset(plain_data, 0, MF_CLASSIC_MAX_DATA_SIZE); - if(!is_encrypted) { - crypto1_reset(&emulator->crypto); - memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); - } else { - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { - FURI_LOG_D( - TAG, - "Error in tx rx. Tx: %d bits, Rx: %d bits", - tx_rx->tx_bits, - tx_rx->rx_bits); - need_reset = true; - break; - } - crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); - } - - // After increment, decrement or restore the only allowed command is transfer - uint8_t cmd = plain_data[0]; - if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) { - need_nack = true; - break; - } - - if(cmd == NFCA_CMD_HALT && plain_data[1] == 0x00) { - FURI_LOG_T(TAG, "Halt received"); - need_reset = true; - break; - } - - if(cmd == NFCA_CMD_RATS) { - // Mifare Classic doesn't support ATS, NACK it and start listening again - FURI_LOG_T(TAG, "RATS received"); - need_nack = true; - break; - } - - if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { - uint8_t block = plain_data[1]; - uint64_t key = 0; - uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); - sector = mf_classic_get_sector_by_block(block); - MfClassicSectorTrailer* sector_trailer = - (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; - if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { - if(mf_classic_is_key_found( - &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyA) || - is_reader_analyzer) { - key = nfc_util_bytes2num(sector_trailer->key_a, 6); - access_key = MfClassicKeyA; - } else { - FURI_LOG_D(TAG, "Key not known"); - need_nack = true; - break; - } - } else { - if(mf_classic_is_key_found( - &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyB) || - is_reader_analyzer) { - key = nfc_util_bytes2num(sector_trailer->key_b, 6); - access_key = MfClassicKeyB; - } else { - FURI_LOG_D(TAG, "Key not known"); - need_nack = true; - break; - } - } - - uint32_t nonce = prng_successor(DWT->CYCCNT, 32) ^ 0xAA; - uint8_t nt[4]; - uint8_t nt_keystream[4]; - nfc_util_num2bytes(nonce, 4, nt); - nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream); - crypto1_init(&emulator->crypto, key); - if(!is_encrypted) { - crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0); - memcpy(tx_rx->tx_data, nt, sizeof(nt)); - tx_rx->tx_parity[0] = 0; - nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(nt)); - tx_rx->tx_bits = sizeof(nt) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - } else { - crypto1_encrypt( - &emulator->crypto, - nt_keystream, - nt, - sizeof(nt) * 8, - tx_rx->tx_data, - tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(nt) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - } - - if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { - FURI_LOG_E(TAG, "Error in NT exchange"); - need_reset = true; - break; - } - - if(tx_rx->rx_bits != 64) { - need_reset = true; - break; - } - - uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4); - uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4); - - crypto1_word(&emulator->crypto, nr, 1); - uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); - if(cardRr != prng_successor(nonce, 64)) { - FURI_LOG_T( - TAG, - "Wrong AUTH on block %u! %08lX != %08lX", - block, - cardRr, - prng_successor(nonce, 64)); - // Don't send NACK, as the tag doesn't send it - need_reset = true; - break; - } - - uint32_t ans = prng_successor(nonce, 96); - uint8_t response[4] = {}; - nfc_util_num2bytes(ans, 4, response); - crypto1_encrypt( - &emulator->crypto, - NULL, - response, - sizeof(response) * 8, - tx_rx->tx_data, - tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(response) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - - is_encrypted = true; - continue; - } - - if(!is_encrypted) { - FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd); - need_nack = true; - break; - } - - // Mifare Classic commands always have block number after command - uint8_t block = plain_data[1]; - if(mf_classic_get_sector_by_block(block) != sector) { - // Don't allow access to sectors other than authorized - FURI_LOG_T( - TAG, - "Trying to access block %u from not authorized sector (command: %02X)", - block, - cmd); - need_nack = true; - break; - } - - switch(cmd) { - case MF_CLASSIC_READ_BLOCK_CMD: { - uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; - memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); - if(mf_classic_is_sector_trailer(block)) { - if(!mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionKeyARead)) { - memset(block_data, 0, 6); //-V1086 - } - if(!mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionKeyBRead)) { - memset(&block_data[10], 0, 6); - } - if(!mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionACRead)) { - memset(&block_data[6], 0, 4); - } - } else if( - !mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionDataRead) || - !mf_classic_is_block_read(&emulator->data, block)) { - need_nack = true; - break; - } - - nfca_append_crc16(block_data, 16); - - crypto1_encrypt( - &emulator->crypto, - NULL, - block_data, - sizeof(block_data) * 8, - tx_rx->tx_data, - tx_rx->tx_parity); - tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - break; - } - - case MF_CLASSIC_WRITE_BLOCK_CMD: { - // Send ACK - uint8_t ack = MF_CLASSIC_ACK_CMD; - crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { - need_reset = true; - break; - } - - if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) { - need_reset = true; - break; - } - - crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); - uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {}; - memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); - - if(!mf_classic_is_block_read(&emulator->data, block)) { - // Don't allow writing to the block for which we haven't read data yet - need_nack = true; - break; - } - - if(mf_classic_is_sector_trailer(block)) { - if(mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionKeyAWrite)) { - memcpy(block_data, plain_data, 6); //-V1086 - } - if(mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionKeyBWrite)) { - memcpy(&block_data[10], &plain_data[10], 6); - } - if(mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionACWrite)) { - memcpy(&block_data[6], &plain_data[6], 4); - } - } else { - if(mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionDataWrite)) { - memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); - } else { - need_nack = true; - break; - } - } - - if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { - memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); - emulator->data_changed = true; - } - - // Send ACK - ack = MF_CLASSIC_ACK_CMD; - crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - break; - } - - case MF_CLASSIC_DECREMENT_CMD: - case MF_CLASSIC_INCREMENT_CMD: - case MF_CLASSIC_RESTORE_CMD: { - MfClassicAction action = (cmd == MF_CLASSIC_INCREMENT_CMD) ? MfClassicActionDataInc : - MfClassicActionDataDec; - - if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) { - need_nack = true; - break; - } - - int32_t prev_value; - uint8_t addr; - if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) { - need_nack = true; - break; - } - - // Send ACK - uint8_t ack = MF_CLASSIC_ACK_CMD; - crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { - need_reset = true; - break; - } - - if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) { - need_reset = true; - break; - } - - crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); - int32_t value = *(int32_t*)&plain_data[0]; - if(value < 0) { - value = -value; - } - if(cmd == MF_CLASSIC_DECREMENT_CMD) { - value = -value; - } else if(cmd == MF_CLASSIC_RESTORE_CMD) { - value = 0; - } - - mf_classic_value_to_block(prev_value + value, addr, transfer_buf); - transfer_buf_valid = true; - // Commands do not ACK - tx_rx->tx_bits = 0; - break; - } - - case MF_CLASSIC_TRANSFER_CMD: { - if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) { - need_nack = true; - break; - } - if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != - 0) { - memcpy(emulator->data.block[block].value, transfer_buf, MF_CLASSIC_BLOCK_SIZE); - emulator->data_changed = true; - } - transfer_buf_valid = false; - - uint8_t ack = MF_CLASSIC_ACK_CMD; - crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - break; - } - - default: - FURI_LOG_T(TAG, "Unknown command: %02X", cmd); - need_nack = true; - break; - } - } - - if(need_nack && !need_reset) { - // Send NACK - uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD : - MF_CLASSIC_NACK_BUF_INVALID_CMD; - if(is_encrypted) { - crypto1_encrypt(&emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); - } else { - tx_rx->tx_data[0] = nack; - } - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - furi_hal_nfc_tx_rx(tx_rx, 300); - need_reset = true; - } - - return !need_reset; -} - -void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { - furi_assert(tx_rx); - - uint8_t plain_data[4] = {NFCA_CMD_HALT, 0x00, 0x00, 0x00}; - - nfca_append_crc16(plain_data, 2); - if(crypto) { - crypto1_encrypt( - crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); - } else { - memcpy(tx_rx->tx_data, plain_data, sizeof(plain_data)); - nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(plain_data)); - } - - tx_rx->tx_bits = sizeof(plain_data) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - furi_hal_nfc_tx_rx(tx_rx, 50); -} - -bool mf_classic_write_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* src_block) { - furi_assert(tx_rx); - furi_assert(crypto); - furi_assert(src_block); - - bool write_success = false; - uint8_t plain_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; - uint8_t resp; - - do { - // Send write command - plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD; - plain_data[1] = block_num; - nfca_append_crc16(plain_data, 2); - crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = 4 * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send write cmd"); - break; - } - - // Send data - memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE); - nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE); - crypto1_encrypt( - crypto, - NULL, - plain_data, - (MF_CLASSIC_BLOCK_SIZE + 2) * 8, - tx_rx->tx_data, - tx_rx->tx_parity); - tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != MF_CLASSIC_ACK_CMD) { - FURI_LOG_D(TAG, "NACK received on sending data"); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send data"); - break; - } - - write_success = true; - } while(false); - - return write_success; -} - -bool mf_classic_auth_write_block( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key) { - furi_assert(tx_rx); - furi_assert(src_block); - - Crypto1 crypto = {}; - bool write_success = false; - - do { - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { - FURI_LOG_D(TAG, "Auth fail"); - break; - } - - if(!mf_classic_write_block(tx_rx, &crypto, block_num, src_block)) { - FURI_LOG_D(TAG, "Write fail"); - break; - } - write_success = true; - - mf_classic_halt(tx_rx, &crypto); - } while(false); - - return write_success; -} - -bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num) { - furi_assert(tx_rx); - furi_assert(crypto); - - // Send transfer command - uint8_t plain_data[4] = {MF_CLASSIC_TRANSFER_CMD, block_num, 0, 0}; - uint8_t resp = 0; - bool transfer_success = false; - - nfca_append_crc16(plain_data, 2); - crypto1_encrypt( - crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(plain_data) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - do { - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send transfer cmd"); - break; - } - - transfer_success = true; - } while(false); - - return transfer_success; -} - -bool mf_classic_value_cmd( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - uint8_t cmd, - int32_t d_value) { - furi_assert(tx_rx); - furi_assert(crypto); - furi_assert( - cmd == MF_CLASSIC_INCREMENT_CMD || cmd == MF_CLASSIC_DECREMENT_CMD || - cmd == MF_CLASSIC_RESTORE_CMD); - furi_assert(d_value >= 0); - - uint8_t plain_data[sizeof(d_value) + 2] = {}; - uint8_t resp = 0; - bool success = false; - - do { - // Send cmd - plain_data[0] = cmd; - plain_data[1] = block_num; - nfca_append_crc16(plain_data, 2); - crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = 4 * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send write cmd"); - break; - } - - // Send data - memcpy(plain_data, &d_value, sizeof(d_value)); - nfca_append_crc16(plain_data, sizeof(d_value)); - crypto1_encrypt( - crypto, NULL, plain_data, (sizeof(d_value) + 2) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = (sizeof(d_value) + 2) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - // inc, dec, restore do not ACK, but they do NACK - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not NACK received"); - break; - } - } - - success = true; - - } while(false); - - return success; -} - -bool mf_classic_value_cmd_full( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key, - int32_t d_value) { - furi_assert(tx_rx); - furi_assert(src_block); - - Crypto1 crypto = {}; - uint8_t cmd; - bool success = false; - - if(d_value > 0) { - cmd = MF_CLASSIC_INCREMENT_CMD; - } else if(d_value < 0) { - cmd = MF_CLASSIC_DECREMENT_CMD; - d_value = -d_value; - } else { - cmd = MF_CLASSIC_RESTORE_CMD; - } - - do { - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { - FURI_LOG_D(TAG, "Value cmd auth fail"); - break; - } - if(!mf_classic_value_cmd(tx_rx, &crypto, block_num, cmd, d_value)) { - FURI_LOG_D(TAG, "Value cmd inc/dec/res fail"); - break; - } - - if(!mf_classic_transfer(tx_rx, &crypto, block_num)) { - FURI_LOG_D(TAG, "Value cmd transfer fail"); - break; - } - - success = true; - - // Send Halt - mf_classic_halt(tx_rx, &crypto); - } while(false); - - return success; -} - -bool mf_classic_write_sector( - FuriHalNfcTxRxContext* tx_rx, - MfClassicData* dest_data, - MfClassicData* src_data, - uint8_t sec_num) { - furi_assert(tx_rx); - furi_assert(dest_data); - furi_assert(src_data); - - uint8_t first_block = mf_classic_get_first_block_num_of_sector(sec_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(dest_data, sec_num); - bool key_a_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyA); - bool key_b_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyB); - - bool write_success = true; - for(size_t i = first_block; i < first_block + total_blocks; i++) { - // Compare blocks - if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE) != - 0) { - if(mf_classic_is_value_block(dest_data, i)) { - bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyA, MfClassicActionDataInc); - bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyB, MfClassicActionDataInc); - bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyA, MfClassicActionDataDec); - bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyB, MfClassicActionDataDec); - - int32_t src_value, dst_value; - - mf_classic_block_to_value(src_data->block[i].value, &src_value, NULL); - mf_classic_block_to_value(dest_data->block[i].value, &dst_value, NULL); - - int32_t diff = src_value - dst_value; - - if(diff > 0) { - if(key_a_found && key_a_inc_allowed) { - FURI_LOG_I(TAG, "Incrementing block %d with key A by %ld", i, diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { - FURI_LOG_E(TAG, "Failed to increment block %d", i); - write_success = false; - break; - } - } else if(key_b_found && key_b_inc_allowed) { - FURI_LOG_I(TAG, "Incrementing block %d with key B by %ld", i, diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { - FURI_LOG_E(TAG, "Failed to increment block %d", i); - write_success = false; - break; - } - } else { - FURI_LOG_E(TAG, "Failed to increment block %d", i); - } - } else if(diff < 0) { - if(key_a_found && key_a_dec_allowed) { - FURI_LOG_I(TAG, "Decrementing block %d with key A by %ld", i, -diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { - FURI_LOG_E(TAG, "Failed to decrement block %d", i); - write_success = false; - break; - } - } else if(key_b_found && key_b_dec_allowed) { - FURI_LOG_I(TAG, "Decrementing block %d with key B by %ld", i, diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { - FURI_LOG_E(TAG, "Failed to decrement block %d", i); - write_success = false; - break; - } - } else { - FURI_LOG_E(TAG, "Failed to decrement block %d", i); - } - } else { - FURI_LOG_E(TAG, "Value block %d address changed, cannot write it", i); - } - } else { - bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); - bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyB, MfClassicActionDataWrite); - - if(key_a_found && key_a_write_allowed) { - FURI_LOG_I(TAG, "Writing block %d with key A", i); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(!mf_classic_auth_write_block( - tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) { - FURI_LOG_E(TAG, "Failed to write block %d", i); - write_success = false; - break; - } - } else if(key_b_found && key_b_write_allowed) { - FURI_LOG_I(TAG, "Writing block %d with key A", i); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); - if(!mf_classic_auth_write_block( - tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) { - FURI_LOG_E(TAG, "Failed to write block %d", i); - write_success = false; - break; - } - } else { - FURI_LOG_E(TAG, "Failed to find key with write access"); - write_success = false; - break; - } - } - } else { - FURI_LOG_D(TAG, "Blocks %d are equal", i); - } - } - - return write_success; -} diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h deleted file mode 100644 index 7efe81b8c5..0000000000 --- a/lib/nfc/protocols/mifare_classic.h +++ /dev/null @@ -1,251 +0,0 @@ -#pragma once - -#include - -#include "crypto1.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define MF_CLASSIC_BLOCK_SIZE (16) -#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) -#define MF_MINI_TOTAL_SECTORS_NUM (5) -#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16) -#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40) - -#define MF_CLASSIC_SECTORS_MAX (40) -#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16) - -#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF) -#define MF_CLASSIC_MAX_DATA_SIZE (16) -#define MF_CLASSIC_KEY_SIZE (6) -#define MF_CLASSIC_ACCESS_BYTES_SIZE (4) - -typedef enum { - MfClassicType1k, - MfClassicType4k, - MfClassicTypeMini, -} MfClassicType; - -typedef enum { - MfClassicKeyA, - MfClassicKeyB, -} MfClassicKey; - -typedef enum { - MfClassicActionDataRead, - MfClassicActionDataWrite, - MfClassicActionDataInc, - MfClassicActionDataDec, - - MfClassicActionKeyARead, - MfClassicActionKeyAWrite, - MfClassicActionKeyBRead, - MfClassicActionKeyBWrite, - MfClassicActionACRead, - MfClassicActionACWrite, -} MfClassicAction; - -typedef struct { - uint8_t value[MF_CLASSIC_BLOCK_SIZE]; -} MfClassicBlock; - -typedef struct { - uint8_t key_a[MF_CLASSIC_KEY_SIZE]; - uint8_t access_bits[MF_CLASSIC_ACCESS_BYTES_SIZE]; - uint8_t key_b[MF_CLASSIC_KEY_SIZE]; -} MfClassicSectorTrailer; - -typedef struct { - uint8_t total_blocks; - MfClassicBlock block[MF_CLASSIC_BLOCKS_IN_SECTOR_MAX]; -} MfClassicSector; - -typedef struct { - MfClassicType type; - uint32_t block_read_mask[MF_CLASSIC_TOTAL_BLOCKS_MAX / 32]; - uint64_t key_a_mask; - uint64_t key_b_mask; - MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; -} MfClassicData; - -typedef struct { - uint8_t sector; - uint64_t key_a; - uint64_t key_b; -} MfClassicAuthContext; - -typedef struct { - uint8_t sector_num; - uint64_t key_a; - uint64_t key_b; -} MfClassicSectorReader; - -typedef struct { - MfClassicType type; - Crypto1 crypto; - uint8_t sectors_to_read; - MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX]; -} MfClassicReader; - -typedef struct { - uint32_t cuid; - Crypto1 crypto; - MfClassicData data; - bool data_changed; -} MfClassicEmulator; - -const char* mf_classic_get_type_str(MfClassicType type); - -bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -uint8_t mf_classic_get_total_sectors_num(MfClassicType type); - -uint16_t mf_classic_get_total_block_num(MfClassicType type); - -uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector); - -bool mf_classic_is_sector_trailer(uint8_t block); - -uint8_t mf_classic_get_sector_by_block(uint8_t block); - -bool mf_classic_is_allowed_access_sector_trailer( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action); - -bool mf_classic_is_allowed_access_data_block( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action); - -bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num); - -bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr); - -void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block); - -bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); - -void mf_classic_set_key_found( - MfClassicData* data, - uint8_t sector_num, - MfClassicKey key_type, - uint64_t key); - -void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); - -bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); - -void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); - -bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num); - -void mf_classic_set_sector_data_not_read(MfClassicData* data); - -bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); - -bool mf_classic_is_card_read(MfClassicData* data); - -void mf_classic_get_read_sectors_and_keys( - MfClassicData* data, - uint8_t* sectors_read, - uint8_t* keys_found); - -MfClassicSectorTrailer* - mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector); - -void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector); - -bool mf_classic_authenticate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type); - -bool mf_classic_authenticate_skip_activate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type, - bool skip_activate, - uint32_t cuid); - -bool mf_classic_auth_attempt( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - MfClassicAuthContext* auth_ctx, - uint64_t key); - -void mf_classic_reader_add_sector( - MfClassicReader* reader, - uint8_t sector, - uint64_t key_a, - uint64_t key_b); - -bool mf_classic_read_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* block); - -void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num); - -uint8_t mf_classic_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfClassicReader* reader, - MfClassicData* data); - -uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); - -bool mf_classic_emulator( - MfClassicEmulator* emulator, - FuriHalNfcTxRxContext* tx_rx, - bool is_reader_analyzer); - -void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto); - -bool mf_classic_write_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* src_block); - -bool mf_classic_auth_write_block( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key); - -bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num); - -bool mf_classic_value_cmd( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - uint8_t cmd, - int32_t d_value); - -bool mf_classic_value_cmd_full( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key, - int32_t d_value); - -bool mf_classic_write_sector( - FuriHalNfcTxRxContext* tx_rx, - MfClassicData* dest_data, - MfClassicData* src_data, - uint8_t sec_num); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c deleted file mode 100644 index 90b57e1f03..0000000000 --- a/lib/nfc/protocols/mifare_common.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "mifare_common.h" - -MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - MifareType type = MifareTypeUnknown; - - if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { - type = MifareTypeUltralight; - } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || - ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || - ((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01))) { - type = MifareTypeClassic; - } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { - type = MifareTypeDesfire; - } - - return type; -} diff --git a/lib/nfc/protocols/mifare_common.h b/lib/nfc/protocols/mifare_common.h deleted file mode 100644 index 2b694d9068..0000000000 --- a/lib/nfc/protocols/mifare_common.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -typedef enum { - MifareTypeUnknown, - MifareTypeUltralight, - MifareTypeClassic, - MifareTypeDesfire, -} MifareType; - -MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c deleted file mode 100644 index e0ead737f8..0000000000 --- a/lib/nfc/protocols/mifare_desfire.c +++ /dev/null @@ -1,665 +0,0 @@ -#include "mifare_desfire.h" -#include -#include - -#define TAG "MifareDESFire" - -void mf_df_clear(MifareDesfireData* data) { - free(data->free_memory); - if(data->master_key_settings) { - MifareDesfireKeyVersion* key_version = data->master_key_settings->key_version_head; - while(key_version) { - MifareDesfireKeyVersion* next_key_version = key_version->next; - free(key_version); - key_version = next_key_version; - } - } - free(data->master_key_settings); - MifareDesfireApplication* app = data->app_head; - while(app) { - MifareDesfireApplication* next_app = app->next; - if(app->key_settings) { - MifareDesfireKeyVersion* key_version = app->key_settings->key_version_head; - while(key_version) { - MifareDesfireKeyVersion* next_key_version = key_version->next; - free(key_version); - key_version = next_key_version; - } - } - free(app->key_settings); - MifareDesfireFile* file = app->file_head; - while(file) { - MifareDesfireFile* next_file = file->next; - free(file->contents); - free(file); - file = next_file; - } - free(app); - app = next_app; - } - data->free_memory = NULL; - data->master_key_settings = NULL; - data->app_head = NULL; -} - -MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]) { - if(!data) { - return NULL; - } - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - if(memcmp(aid, app->id, 3) == 0) { - return app; - } - } - return NULL; -} - -MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id) { - if(!app) { - return NULL; - } - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - if(file->id == id) { - return file; - } - } - return NULL; -} - -void mf_df_cat_data(MifareDesfireData* data, FuriString* out) { - mf_df_cat_card_info(data, out); - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - mf_df_cat_application(app, out); - } -} - -void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out) { - mf_df_cat_version(&data->version, out); - if(data->free_memory) { - mf_df_cat_free_mem(data->free_memory, out); - } - if(data->master_key_settings) { - mf_df_cat_key_settings(data->master_key_settings, out); - } -} - -void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out) { - furi_string_cat_printf( - out, - "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", - version->uid[0], - version->uid[1], - version->uid[2], - version->uid[3], - version->uid[4], - version->uid[5], - version->uid[6]); - furi_string_cat_printf( - out, - "hw %02x type %02x sub %02x\n" - " maj %02x min %02x\n" - " size %02x proto %02x\n", - version->hw_vendor, - version->hw_type, - version->hw_subtype, - version->hw_major, - version->hw_minor, - version->hw_storage, - version->hw_proto); - furi_string_cat_printf( - out, - "sw %02x type %02x sub %02x\n" - " maj %02x min %02x\n" - " size %02x proto %02x\n", - version->sw_vendor, - version->sw_type, - version->sw_subtype, - version->sw_major, - version->sw_minor, - version->sw_storage, - version->sw_proto); - furi_string_cat_printf( - out, - "batch %02x:%02x:%02x:%02x:%02x\n" - "week %d year %d\n", - version->batch[0], - version->batch[1], - version->batch[2], - version->batch[3], - version->batch[4], - version->prod_week, - version->prod_year); -} - -void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out) { - furi_string_cat_printf(out, "freeMem %lu\n", free_mem->bytes); -} - -void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out) { - furi_string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); - furi_string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); - furi_string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); - furi_string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); - furi_string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); - if(ks->flags) { - furi_string_cat_printf(out, "flags %d\n", ks->flags); - } - furi_string_cat_printf(out, "maxKeys %d\n", ks->max_keys); - for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { - furi_string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); - } -} - -void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out) { - furi_string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]); - if(app->key_settings) { - mf_df_cat_key_settings(app->key_settings, out); - } -} - -void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out) { - mf_df_cat_application_info(app, out); - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - mf_df_cat_file(file, out); - } -} - -void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { - char* type = "unknown"; - switch(file->type) { - case MifareDesfireFileTypeStandard: - type = "standard"; - break; - case MifareDesfireFileTypeBackup: - type = "backup"; - break; - case MifareDesfireFileTypeValue: - type = "value"; - break; - case MifareDesfireFileTypeLinearRecord: - type = "linear"; - break; - case MifareDesfireFileTypeCyclicRecord: - type = "cyclic"; - break; - } - char* comm = "unknown"; - switch(file->comm) { - case MifareDesfireFileCommunicationSettingsPlaintext: - comm = "plain"; - break; - case MifareDesfireFileCommunicationSettingsAuthenticated: - comm = "auth"; - break; - case MifareDesfireFileCommunicationSettingsEnciphered: - comm = "enciphered"; - break; - } - furi_string_cat_printf(out, "File %d\n", file->id); - furi_string_cat_printf(out, "%s %s\n", type, comm); - furi_string_cat_printf( - out, - "r %d w %d rw %d c %d\n", - file->access_rights >> 12 & 0xF, - file->access_rights >> 8 & 0xF, - file->access_rights >> 4 & 0xF, - file->access_rights & 0xF); - uint16_t size = 0; - uint16_t num = 1; - switch(file->type) { - case MifareDesfireFileTypeStandard: - case MifareDesfireFileTypeBackup: - size = file->settings.data.size; - furi_string_cat_printf(out, "size %d\n", size); - break; - case MifareDesfireFileTypeValue: - size = 4; - furi_string_cat_printf( - out, "lo %lu hi %lu\n", file->settings.value.lo_limit, file->settings.value.hi_limit); - furi_string_cat_printf( - out, - "limit %lu enabled %d\n", - file->settings.value.limited_credit_value, - file->settings.value.limited_credit_enabled); - break; - case MifareDesfireFileTypeLinearRecord: - case MifareDesfireFileTypeCyclicRecord: - size = file->settings.record.size; - num = file->settings.record.cur; - furi_string_cat_printf(out, "size %d\n", size); - furi_string_cat_printf(out, "num %d max %lu\n", num, file->settings.record.max); - break; - } - uint8_t* data = file->contents; - if(data) { - for(int rec = 0; rec < num; rec++) { - furi_string_cat_printf(out, "record %d\n", rec); - for(int ch = 0; ch < size; ch += 4) { - furi_string_cat_printf(out, "%03x|", ch); - for(int i = 0; i < 4; i++) { - if(ch + i < size) { - furi_string_cat_printf(out, "%02x ", data[rec * size + ch + i]); - } else { - furi_string_cat_printf(out, " "); - } - } - for(int i = 0; i < 4 && ch + i < size; i++) { - const size_t data_index = rec * size + ch + i; - if(isprint(data[data_index])) { - furi_string_cat_printf(out, "%c", data[data_index]); - } else { - furi_string_cat_printf(out, "."); - } - } - furi_string_cat_printf(out, "\n"); - } - furi_string_cat_printf(out, " \n"); - } - } -} - -bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - return ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20; -} - -uint16_t mf_df_prepare_get_version(uint8_t* dest) { - dest[0] = MF_DF_GET_VERSION; - return 1; -} - -bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len < sizeof(MifareDesfireVersion)) { - return false; - } - memcpy(out, buf, sizeof(MifareDesfireVersion)); - return true; -} - -uint16_t mf_df_prepare_get_free_memory(uint8_t* dest) { - dest[0] = MF_DF_GET_FREE_MEMORY; - return 1; -} - -bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len != 3) { - return false; - } - out->bytes = buf[0] | (buf[1] << 8) | (buf[2] << 16); - return true; -} - -uint16_t mf_df_prepare_get_key_settings(uint8_t* dest) { - dest[0] = MF_DF_GET_KEY_SETTINGS; - return 1; -} - -bool mf_df_parse_get_key_settings_response( - uint8_t* buf, - uint16_t len, - MifareDesfireKeySettings* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len < 2) { - return false; - } - out->change_key_id = buf[0] >> 4; - out->config_changeable = (buf[0] & 0x8) != 0; - out->free_create_delete = (buf[0] & 0x4) != 0; - out->free_directory_list = (buf[0] & 0x2) != 0; - out->master_key_changeable = (buf[0] & 0x1) != 0; - out->flags = buf[1] >> 4; - out->max_keys = buf[1] & 0xF; - return true; -} - -uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id) { - dest[0] = MF_DF_GET_KEY_VERSION; - dest[1] = key_id; - return 2; -} - -bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out) { - if(len != 2 || *buf) { - return false; - } - out->version = buf[1]; - return true; -} - -uint16_t mf_df_prepare_get_application_ids(uint8_t* dest) { - dest[0] = MF_DF_GET_APPLICATION_IDS; - return 1; -} - -bool mf_df_parse_get_application_ids_response( - uint8_t* buf, - uint16_t len, - MifareDesfireApplication** app_head) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len % 3 != 0) { - return false; - } - while(len) { - MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); - memset(app, 0, sizeof(MifareDesfireApplication)); - memcpy(app->id, buf, 3); - len -= 3; - buf += 3; - *app_head = app; - app_head = &app->next; - } - return true; -} - -uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]) { - dest[0] = MF_DF_SELECT_APPLICATION; - dest[1] = id[0]; - dest[2] = id[1]; - dest[3] = id[2]; - return 4; -} - -bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len) { - return len == 1 && !*buf; -} - -uint16_t mf_df_prepare_get_file_ids(uint8_t* dest) { - dest[0] = MF_DF_GET_FILE_IDS; - return 1; -} - -bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - while(len) { - MifareDesfireFile* file = malloc(sizeof(MifareDesfireFile)); - memset(file, 0, sizeof(MifareDesfireFile)); - file->id = *buf; - len--; - buf++; - *file_head = file; - file_head = &file->next; - } - return true; -} - -uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id) { - dest[0] = MF_DF_GET_FILE_SETTINGS; - dest[1] = file_id; - return 2; -} - -bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { - if(len < 5 || *buf) { - return false; - } - len--; - buf++; - out->type = buf[0]; - out->comm = buf[1]; - out->access_rights = buf[2] | (buf[3] << 8); - switch(out->type) { - case MifareDesfireFileTypeStandard: - case MifareDesfireFileTypeBackup: - if(len != 7) { - return false; - } - out->settings.data.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); - break; - case MifareDesfireFileTypeValue: - if(len != 17) { - return false; - } - out->settings.value.lo_limit = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); - out->settings.value.hi_limit = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24); - out->settings.value.limited_credit_value = buf[12] | (buf[13] << 8) | (buf[14] << 16) | - (buf[15] << 24); - out->settings.value.limited_credit_enabled = buf[16]; - break; - case MifareDesfireFileTypeLinearRecord: - case MifareDesfireFileTypeCyclicRecord: - if(len != 13) { - return false; - } - out->settings.record.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); - out->settings.record.max = buf[7] | (buf[8] << 8) | (buf[9] << 16); - out->settings.record.cur = buf[10] | (buf[11] << 8) | (buf[12] << 16); - break; - default: - return false; - } - return true; -} - -uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { - dest[0] = MF_DF_READ_DATA; - dest[1] = file_id; - dest[2] = offset; - dest[3] = offset >> 8; - dest[4] = offset >> 16; - dest[5] = len; - dest[6] = len >> 8; - dest[7] = len >> 16; - return 8; -} - -uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id) { - dest[0] = MF_DF_GET_VALUE; - dest[1] = file_id; - return 2; -} - -uint16_t - mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { - dest[0] = MF_DF_READ_RECORDS; - dest[1] = file_id; - dest[2] = offset; - dest[3] = offset >> 8; - dest[4] = offset >> 16; - dest[5] = len; - dest[6] = len >> 8; - dest[7] = len >> 16; - return 8; -} - -bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - out->contents = malloc(len); - memcpy(out->contents, buf, len); - return true; -} - -bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) { - furi_assert(tx_rx); - furi_assert(data); - - bool card_read = false; - do { - // Get version - tx_rx->tx_bits = 8 * mf_df_prepare_get_version(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting version"); - break; - } - if(!mf_df_parse_get_version_response(tx_rx->rx_data, tx_rx->rx_bits / 8, &data->version)) { - FURI_LOG_W(TAG, "Bad DESFire GET_VERSION responce"); - } - - // Get free memory - tx_rx->tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx->tx_data); - if(furi_hal_nfc_tx_rx_full(tx_rx)) { - data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); - if(!mf_df_parse_get_free_memory_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, data->free_memory)) { - FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)"); - free(data->free_memory); - data->free_memory = NULL; - } - } - - // Get key settings - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_D(TAG, "Bad exchange getting key settings"); - } else { - data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); - if(!mf_df_parse_get_key_settings_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, data->master_key_settings)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); - free(data->master_key_settings); - data->master_key_settings = NULL; - } else { - MifareDesfireKeyVersion** key_version_head = - &data->master_key_settings->key_version_head; - for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) { - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting key version"); - continue; - } - MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); - memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); - key_version->id = key_id; - if(!mf_df_parse_get_key_version_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); - free(key_version); - continue; - } - *key_version_head = key_version; - key_version_head = &key_version->next; - } - } - } - - // Get application IDs - tx_rx->tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting application IDs"); - break; - } else { - if(!mf_df_parse_get_application_ids_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, &data->app_head)) { - FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response"); - break; - } - } - - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id); - if(!furi_hal_nfc_tx_rx_full(tx_rx) || - !mf_df_parse_select_application_response( - tx_rx->rx_data, tx_rx->rx_bits / 8)) { //-V1051 - FURI_LOG_W(TAG, "Bad exchange selecting application"); - continue; - } - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting key settings"); - } else { - app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); - memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!mf_df_parse_get_key_settings_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, app->key_settings)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); - free(app->key_settings); - app->key_settings = NULL; - continue; - } - - MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; - for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) { - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting key version"); - continue; - } - MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); - memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); - key_version->id = key_id; - if(!mf_df_parse_get_key_version_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); - free(key_version); - continue; - } - *key_version_head = key_version; - key_version_head = &key_version->next; - } - } - - tx_rx->tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting file IDs"); - } else { - if(!mf_df_parse_get_file_ids_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, &app->file_head)) { - FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response"); - } - } - - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - tx_rx->tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx->tx_data, file->id); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting file settings"); - continue; - } - if(!mf_df_parse_get_file_settings_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { - FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response"); - continue; - } - switch(file->type) { - case MifareDesfireFileTypeStandard: - case MifareDesfireFileTypeBackup: - tx_rx->tx_bits = 8 * mf_df_prepare_read_data(tx_rx->tx_data, file->id, 0, 0); - break; - case MifareDesfireFileTypeValue: - tx_rx->tx_bits = 8 * mf_df_prepare_get_value(tx_rx->tx_data, file->id); - break; - case MifareDesfireFileTypeLinearRecord: - case MifareDesfireFileTypeCyclicRecord: - tx_rx->tx_bits = - 8 * mf_df_prepare_read_records(tx_rx->tx_data, file->id, 0, 0); - break; - } - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id); - continue; - } - if(!mf_df_parse_read_data_response(tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { - FURI_LOG_W(TAG, "Bad response reading file %d", file->id); - continue; - } - } - } - - card_read = true; - } while(false); - - return card_read; -} diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h deleted file mode 100644 index 8faa98ec24..0000000000 --- a/lib/nfc/protocols/mifare_desfire.h +++ /dev/null @@ -1,179 +0,0 @@ -#pragma once - -#include -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define MF_DF_GET_VERSION (0x60) -#define MF_DF_GET_FREE_MEMORY (0x6E) -#define MF_DF_GET_KEY_SETTINGS (0x45) -#define MF_DF_GET_KEY_VERSION (0x64) -#define MF_DF_GET_APPLICATION_IDS (0x6A) -#define MF_DF_SELECT_APPLICATION (0x5A) -#define MF_DF_GET_FILE_IDS (0x6F) -#define MF_DF_GET_FILE_SETTINGS (0xF5) - -#define MF_DF_READ_DATA (0xBD) -#define MF_DF_GET_VALUE (0x6C) -#define MF_DF_READ_RECORDS (0xBB) - -typedef struct { - uint8_t hw_vendor; - uint8_t hw_type; - uint8_t hw_subtype; - uint8_t hw_major; - uint8_t hw_minor; - uint8_t hw_storage; - uint8_t hw_proto; - - uint8_t sw_vendor; - uint8_t sw_type; - uint8_t sw_subtype; - uint8_t sw_major; - uint8_t sw_minor; - uint8_t sw_storage; - uint8_t sw_proto; - - uint8_t uid[7]; - uint8_t batch[5]; - uint8_t prod_week; - uint8_t prod_year; -} MifareDesfireVersion; - -typedef struct { - uint32_t bytes; -} MifareDesfireFreeMemory; // EV1+ only - -typedef struct MifareDesfireKeyVersion { - uint8_t id; - uint8_t version; - struct MifareDesfireKeyVersion* next; -} MifareDesfireKeyVersion; - -typedef struct { - uint8_t change_key_id; - bool config_changeable; - bool free_create_delete; - bool free_directory_list; - bool master_key_changeable; - uint8_t flags; - uint8_t max_keys; - MifareDesfireKeyVersion* key_version_head; -} MifareDesfireKeySettings; - -typedef enum { - MifareDesfireFileTypeStandard = 0, - MifareDesfireFileTypeBackup = 1, - MifareDesfireFileTypeValue = 2, - MifareDesfireFileTypeLinearRecord = 3, - MifareDesfireFileTypeCyclicRecord = 4, -} MifareDesfireFileType; - -typedef enum { - MifareDesfireFileCommunicationSettingsPlaintext = 0, - MifareDesfireFileCommunicationSettingsAuthenticated = 1, - MifareDesfireFileCommunicationSettingsEnciphered = 3, -} MifareDesfireFileCommunicationSettings; - -typedef struct MifareDesfireFile { - uint8_t id; - MifareDesfireFileType type; - MifareDesfireFileCommunicationSettings comm; - uint16_t access_rights; - union { - struct { - uint32_t size; - } data; - struct { - uint32_t lo_limit; - uint32_t hi_limit; - uint32_t limited_credit_value; - bool limited_credit_enabled; - } value; - struct { - uint32_t size; - uint32_t max; - uint32_t cur; - } record; - } settings; - uint8_t* contents; - - struct MifareDesfireFile* next; -} MifareDesfireFile; - -typedef struct MifareDesfireApplication { - uint8_t id[3]; - MifareDesfireKeySettings* key_settings; - MifareDesfireFile* file_head; - - struct MifareDesfireApplication* next; -} MifareDesfireApplication; - -typedef struct { - MifareDesfireVersion version; - MifareDesfireFreeMemory* free_memory; - MifareDesfireKeySettings* master_key_settings; - MifareDesfireApplication* app_head; -} MifareDesfireData; - -void mf_df_clear(MifareDesfireData* data); - -void mf_df_cat_data(MifareDesfireData* data, FuriString* out); -void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out); -void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out); -void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out); -void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out); -void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out); -void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out); -void mf_df_cat_file(MifareDesfireFile* file, FuriString* out); - -bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]); -MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id); - -uint16_t mf_df_prepare_get_version(uint8_t* dest); -bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out); - -uint16_t mf_df_prepare_get_free_memory(uint8_t* dest); -bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out); - -uint16_t mf_df_prepare_get_key_settings(uint8_t* dest); -bool mf_df_parse_get_key_settings_response( - uint8_t* buf, - uint16_t len, - MifareDesfireKeySettings* out); - -uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id); -bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out); - -uint16_t mf_df_prepare_get_application_ids(uint8_t* dest); -bool mf_df_parse_get_application_ids_response( - uint8_t* buf, - uint16_t len, - MifareDesfireApplication** app_head); - -uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]); -bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len); - -uint16_t mf_df_prepare_get_file_ids(uint8_t* dest); -bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head); - -uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id); -bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); - -uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); -uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id); -uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); -bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); - -bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c deleted file mode 100644 index 266b6e2e2e..0000000000 --- a/lib/nfc/protocols/mifare_ultralight.c +++ /dev/null @@ -1,1946 +0,0 @@ -#include -#include -#include "mifare_ultralight.h" -#include "nfc_util.h" -#include -#include - -#define TAG "MfUltralight" - -// Algorithms from: https://github.com/RfidResearchGroup/proxmark3/blob/0f6061c16f072372b7d4d381911f1542afbc3a69/common/generator.c#L110 -uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data) { - uint8_t hash[20]; - mbedtls_sha1(data->uid, data->uid_len, hash); - - uint32_t pwd = 0; - pwd |= (hash[hash[0] % 20]) << 24; - pwd |= (hash[(hash[0] + 5) % 20]) << 16; - pwd |= (hash[(hash[0] + 13) % 20]) << 8; - pwd |= (hash[(hash[0] + 17) % 20]); - - return pwd; -} - -uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data) { - uint8_t* uid = data->uid; - - uint32_t pwd = 0; - pwd |= (uid[1] ^ uid[3] ^ 0xAA) << 24; - pwd |= (uid[2] ^ uid[4] ^ 0x55) << 16; - pwd |= (uid[3] ^ uid[5] ^ 0xAA) << 8; - pwd |= uid[4] ^ uid[6] ^ 0x55; - - return pwd; -} - -bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { - return true; - } - return false; -} - -void mf_ul_reset(MfUltralightData* data) { - furi_assert(data); - data->type = MfUltralightTypeUnknown; - memset(&data->version, 0, sizeof(MfUltralightVersion)); - memset(data->signature, 0, sizeof(data->signature)); - memset(data->counter, 0, sizeof(data->counter)); - memset(data->tearing, 0, sizeof(data->tearing)); - memset(data->data, 0, sizeof(data->data)); - data->data_size = 0; - data->data_read = 0; - data->curr_authlim = 0; - data->auth_success = false; -} - -static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) { - switch(type) { - case MfUltralightTypeUL11: - case MfUltralightTypeUL21: - return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | - MfUltralightSupportReadCounter | MfUltralightSupportIncrCounter | - MfUltralightSupportAuth | MfUltralightSupportSignature | - MfUltralightSupportTearingFlags | MfUltralightSupportVcsl; - case MfUltralightTypeNTAG213: - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | - MfUltralightSupportReadCounter | MfUltralightSupportAuth | - MfUltralightSupportSignature | MfUltralightSupportSingleCounter | - MfUltralightSupportAsciiMirror; - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2C2K: - return MfUltralightSupportFastRead | MfUltralightSupportSectorSelect; - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - return MfUltralightSupportFastRead | MfUltralightSupportAuth | - MfUltralightSupportFastWrite | MfUltralightSupportSignature | - MfUltralightSupportSectorSelect; - case MfUltralightTypeNTAG203: - return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory; - case MfUltralightTypeULC: - return MfUltralightSupportCompatWrite | MfUltralightSupport3DesAuth; - default: - // Assumed original MFUL 512-bit - return MfUltralightSupportCompatWrite; - } -} - -static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightData* data) { - data->type = MfUltralightTypeUnknown; - reader->pages_to_read = 16; -} - -static void mf_ul_set_version_ntag203(MfUltralightReader* reader, MfUltralightData* data) { - data->type = MfUltralightTypeNTAG203; - reader->pages_to_read = 42; -} - -static void mf_ul_set_version_ulc(MfUltralightReader* reader, MfUltralightData* data) { - data->type = MfUltralightTypeULC; - reader->pages_to_read = 48; -} - -bool mf_ultralight_read_version( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data) { - bool version_read = false; - - do { - FURI_LOG_D(TAG, "Reading version"); - tx_rx->tx_data[0] = MF_UL_GET_VERSION_CMD; - tx_rx->tx_bits = 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits != 64) { - FURI_LOG_D(TAG, "Failed reading version"); - mf_ul_set_default_version(reader, data); - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - break; - } - MfUltralightVersion* version = (MfUltralightVersion*)tx_rx->rx_data; - data->version = *version; - if(version->storage_size == 0x0B || version->storage_size == 0x00) { - data->type = MfUltralightTypeUL11; - reader->pages_to_read = 20; - } else if(version->storage_size == 0x0E) { - data->type = MfUltralightTypeUL21; - reader->pages_to_read = 41; - } else if(version->storage_size == 0x0F) { - data->type = MfUltralightTypeNTAG213; - reader->pages_to_read = 45; - } else if(version->storage_size == 0x11) { - data->type = MfUltralightTypeNTAG215; - reader->pages_to_read = 135; - } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) { - // NTAG I2C - bool known = false; - if(version->prod_ver_minor == 1) { - if(version->storage_size == 0x13) { - data->type = MfUltralightTypeNTAGI2C1K; - reader->pages_to_read = 231; - known = true; - } else if(version->storage_size == 0x15) { - data->type = MfUltralightTypeNTAGI2C2K; - reader->pages_to_read = 485; - known = true; - } - } else if(version->prod_ver_minor == 2) { - if(version->storage_size == 0x13) { - data->type = MfUltralightTypeNTAGI2CPlus1K; - reader->pages_to_read = 236; - known = true; - } else if(version->storage_size == 0x15) { - data->type = MfUltralightTypeNTAGI2CPlus2K; - reader->pages_to_read = 492; - known = true; - } - } - - if(!known) { - mf_ul_set_default_version(reader, data); - } - } else if(version->storage_size == 0x13) { - data->type = MfUltralightTypeNTAG216; - reader->pages_to_read = 231; - } else { - mf_ul_set_default_version(reader, data); - break; - } - version_read = true; - } while(false); - - reader->supported_features = mf_ul_get_features(data->type); - return version_read; -} - -bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack) { - furi_assert(pack); - bool authenticated = false; - - do { - FURI_LOG_D(TAG, "Authenticating"); - tx_rx->tx_data[0] = MF_UL_PWD_AUTH; - nfc_util_num2bytes(key, 4, &tx_rx->tx_data[1]); - tx_rx->tx_bits = 40; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { - FURI_LOG_D(TAG, "Tag did not respond to authentication"); - break; - } - - // PACK - if(tx_rx->rx_bits < 2 * 8) { - FURI_LOG_D(TAG, "Authentication failed"); - break; - } - - *pack = (tx_rx->rx_data[1] << 8) | tx_rx->rx_data[0]; - - FURI_LOG_I(TAG, "Auth success. Password: %08lX. PACK: %04X", key, *pack); - authenticated = true; - } while(false); - - return authenticated; -} - -static int16_t mf_ultralight_page_addr_to_tag_addr(uint8_t sector, uint8_t page) { - return sector * 256 + page; -} - -static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_1k( - int16_t linear_address, - uint8_t* sector, - int16_t* valid_pages) { - // 0 - 226: sector 0 - // 227 - 228: config registers - // 229 - 230: session registers - - if(linear_address > 230) { - *valid_pages = 0; - return -1; - } else if(linear_address >= 229) { - *sector = 3; - *valid_pages = 2 - (linear_address - 229); - return linear_address - 229 + 248; - } else if(linear_address >= 227) { - *sector = 0; - *valid_pages = 2 - (linear_address - 227); - return linear_address - 227 + 232; - } else { - *sector = 0; - *valid_pages = 227 - linear_address; - return linear_address; - } -} - -static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_2k( - int16_t linear_address, - uint8_t* sector, - int16_t* valid_pages) { - // 0 - 255: sector 0 - // 256 - 480: sector 1 - // 481 - 482: config registers - // 483 - 484: session registers - - if(linear_address > 484) { - *valid_pages = 0; - return -1; - } else if(linear_address >= 483) { - *sector = 3; - *valid_pages = 2 - (linear_address - 483); - return linear_address - 483 + 248; - } else if(linear_address >= 481) { - *sector = 1; - *valid_pages = 2 - (linear_address - 481); - return linear_address - 481 + 232; - } else if(linear_address >= 256) { - *sector = 1; - *valid_pages = 225 - (linear_address - 256); - return linear_address - 256; - } else { - *sector = 0; - *valid_pages = 256 - linear_address; - return linear_address; - } -} - -static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k( - int16_t linear_address, - uint8_t* sector, - int16_t* valid_pages) { - // 0 - 233: sector 0 + registers - // 234 - 235: session registers - - if(linear_address > 235) { - *valid_pages = 0; - return -1; - } else if(linear_address >= 234) { - *sector = 0; - *valid_pages = 2 - (linear_address - 234); - return linear_address - 234 + 236; - } else { - *sector = 0; - *valid_pages = 234 - linear_address; - return linear_address; - } -} - -static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k( - int16_t linear_address, - uint8_t* sector, - int16_t* valid_pages) { - // 0 - 233: sector 0 + registers - // 234 - 235: session registers - // 236 - 491: sector 1 - - if(linear_address > 491) { - *valid_pages = 0; - return -1; - } else if(linear_address >= 236) { - *sector = 1; - *valid_pages = 256 - (linear_address - 236); - return linear_address - 236; - } else if(linear_address >= 234) { - *sector = 0; - *valid_pages = 2 - (linear_address - 234); - return linear_address - 234 + 236; - } else { - *sector = 0; - *valid_pages = 234 - linear_address; - return linear_address; - } -} - -static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag( - MfUltralightData* data, - MfUltralightReader* reader, - int16_t linear_address, - uint8_t* sector, - int16_t* valid_pages) { - switch(data->type) { - case MfUltralightTypeNTAGI2C1K: - return mf_ultralight_ntag_i2c_addr_lin_to_tag_1k(linear_address, sector, valid_pages); - - case MfUltralightTypeNTAGI2C2K: - return mf_ultralight_ntag_i2c_addr_lin_to_tag_2k(linear_address, sector, valid_pages); - - case MfUltralightTypeNTAGI2CPlus1K: - return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k(linear_address, sector, valid_pages); - - case MfUltralightTypeNTAGI2CPlus2K: - return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k(linear_address, sector, valid_pages); - - default: - *sector = 0xff; - *valid_pages = reader->pages_to_read - linear_address; - return linear_address; - } -} - -static int16_t - mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { - bool valid = false; - int16_t translated_page; - if(sector == 0) { - if(page <= 226) { - *valid_pages = 227 - page; - translated_page = page; - valid = true; - } else if(page >= 232 && page <= 233) { - *valid_pages = 2 - (page - 232); - translated_page = page - 232 + 227; - valid = true; - } - } else if(sector == 3) { - if(page >= 248 && page <= 249) { - *valid_pages = 2 - (page - 248); - translated_page = page - 248 + 229; - valid = true; - } - } - - if(!valid) { - *valid_pages = 0; - translated_page = -1; - } - return translated_page; -} - -static int16_t - mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { - bool valid = false; - int16_t translated_page; - if(sector == 0) { - *valid_pages = 256 - page; - translated_page = page; - valid = true; - } else if(sector == 1) { - if(page <= 224) { - *valid_pages = 225 - page; - translated_page = 256 + page; - valid = true; - } else if(page >= 232 && page <= 233) { - *valid_pages = 2 - (page - 232); - translated_page = page - 232 + 481; - valid = true; - } - } else if(sector == 3) { - if(page >= 248 && page <= 249) { - *valid_pages = 2 - (page - 248); - translated_page = page - 248 + 483; - valid = true; - } - } - - if(!valid) { - *valid_pages = 0; - translated_page = -1; - } - return translated_page; -} - -static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k( - uint8_t page, - uint8_t sector, - uint16_t* valid_pages) { - bool valid = false; - int16_t translated_page; - if(sector == 0) { - if(page <= 233) { - *valid_pages = 234 - page; - translated_page = page; - valid = true; - } else if(page >= 236 && page <= 237) { - *valid_pages = 2 - (page - 236); - translated_page = page - 236 + 234; - valid = true; - } - } else if(sector == 3) { - if(page >= 248 && page <= 249) { - *valid_pages = 2 - (page - 248); - translated_page = page - 248 + 234; - valid = true; - } - } - - if(!valid) { - *valid_pages = 0; - translated_page = -1; - } - return translated_page; -} - -static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k( - uint8_t page, - uint8_t sector, - uint16_t* valid_pages) { - bool valid = false; - int16_t translated_page; - if(sector == 0) { - if(page <= 233) { - *valid_pages = 234 - page; - translated_page = page; - valid = true; - } else if(page >= 236 && page <= 237) { - *valid_pages = 2 - (page - 236); - translated_page = page - 236 + 234; - valid = true; - } - } else if(sector == 1) { - *valid_pages = 256 - page; - translated_page = page + 236; - valid = true; - } else if(sector == 3) { - if(page >= 248 && page <= 249) { - *valid_pages = 2 - (page - 248); - translated_page = page - 248 + 234; - valid = true; - } - } - - if(!valid) { - *valid_pages = 0; - translated_page = -1; - } - return translated_page; -} - -static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin( - MfUltralightData* data, - uint8_t page, - uint8_t sector, - uint16_t* valid_pages) { - switch(data->type) { - case MfUltralightTypeNTAGI2C1K: - return mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(page, sector, valid_pages); - - case MfUltralightTypeNTAGI2C2K: - return mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(page, sector, valid_pages); - - case MfUltralightTypeNTAGI2CPlus1K: - return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k(page, sector, valid_pages); - - case MfUltralightTypeNTAGI2CPlus2K: - return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k(page, sector, valid_pages); - - default: - *valid_pages = data->data_size / 4 - page; - return page; - } -} - -MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) { - if(data->type >= MfUltralightTypeUL11 && data->type <= MfUltralightTypeNTAG216) { - return (MfUltralightConfigPages*)&data->data[data->data_size - 4 * 4]; - } else if( - data->type >= MfUltralightTypeNTAGI2CPlus1K && - data->type <= MfUltralightTypeNTAGI2CPlus2K) { - return (MfUltralightConfigPages*)&data->data[0xe3 * 4]; //-V641 - } else { - return NULL; - } -} - -static uint16_t mf_ultralight_calc_auth_count(MfUltralightData* data) { - if(mf_ul_get_features(data->type) & MfUltralightSupportAuth) { - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - uint16_t scaled_authlim = config->access.authlim; - // NTAG I2C Plus uses 2^AUTHLIM attempts rather than the direct number - if(scaled_authlim > 0 && data->type >= MfUltralightTypeNTAGI2CPlus1K && - data->type <= MfUltralightTypeNTAGI2CPlus2K) { - scaled_authlim = 1 << scaled_authlim; - } - return scaled_authlim; - } - - return 0; -} - -// NTAG21x will NAK if NFC_CNT_EN unset, so preempt -static bool mf_ultralight_should_read_counters(MfUltralightData* data) { - if(data->type < MfUltralightTypeNTAG213 || data->type > MfUltralightTypeNTAG216) return true; - - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - return config->access.nfc_cnt_en; -} - -static bool mf_ultralight_sector_select(FuriHalNfcTxRxContext* tx_rx, uint8_t sector) { - FURI_LOG_D(TAG, "Selecting sector %u", sector); - tx_rx->tx_data[0] = MF_UL_SECTOR_SELECT; - tx_rx->tx_data[1] = 0xff; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { - FURI_LOG_D(TAG, "Failed to issue sector select command"); - return false; - } - - tx_rx->tx_data[0] = sector; - tx_rx->tx_data[1] = 0x00; - tx_rx->tx_data[2] = 0x00; - tx_rx->tx_data[3] = 0x00; - tx_rx->tx_bits = 32; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - // This is NOT a typo! The tag ACKs by not sending a response within 1ms. - if(furi_hal_nfc_tx_rx(tx_rx, 20)) { - // TODO: what gets returned when an actual NAK is received? - FURI_LOG_D(TAG, "Sector %u select NAK'd", sector); - return false; - } - - return true; -} - -bool mf_ultralight_read_pages_direct( - FuriHalNfcTxRxContext* tx_rx, - uint8_t start_index, - uint8_t* data) { - FURI_LOG_D(TAG, "Reading pages %d - %d", start_index, start_index + 3); - tx_rx->tx_data[0] = MF_UL_READ_CMD; - tx_rx->tx_data[1] = start_index; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) { - FURI_LOG_D(TAG, "Failed to read pages %d - %d", start_index, start_index + 3); - return false; - } - memcpy(data, tx_rx->rx_data, 16); //-V1086 - return true; -} - -bool mf_ultralight_read_pages( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data) { - uint8_t pages_read_cnt = 0; - uint8_t curr_sector_index = 0xff; - reader->pages_read = 0; - for(size_t i = 0; i < reader->pages_to_read; i += pages_read_cnt) { - uint8_t tag_sector; - int16_t valid_pages; - int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( - data, reader, (int16_t)i, &tag_sector, &valid_pages); - - furi_assert(tag_page != -1); - if(curr_sector_index != tag_sector) { - if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; - curr_sector_index = tag_sector; - } - - FURI_LOG_D( - TAG, "Reading pages %zu - %zu", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1U); - tx_rx->tx_data[0] = MF_UL_READ_CMD; - tx_rx->tx_data[1] = tag_page; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) { - FURI_LOG_D( - TAG, - "Failed to read pages %zu - %zu", - i, - i + (valid_pages > 4 ? 4 : valid_pages) - 1U); - break; - } - - if(valid_pages > 4) { - pages_read_cnt = 4; - } else { - pages_read_cnt = valid_pages; - } - reader->pages_read += pages_read_cnt; - memcpy(&data->data[i * 4], tx_rx->rx_data, pages_read_cnt * 4); - } - data->data_size = reader->pages_to_read * 4; - data->data_read = reader->pages_read * 4; - - return reader->pages_read > 0; -} - -bool mf_ultralight_fast_read_pages( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data) { - uint8_t curr_sector_index = 0xff; - reader->pages_read = 0; - while(reader->pages_read < reader->pages_to_read) { - uint8_t tag_sector; - int16_t valid_pages; - int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( - data, reader, reader->pages_read, &tag_sector, &valid_pages); - - furi_assert(tag_page != -1); - if(curr_sector_index != tag_sector) { - if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; - curr_sector_index = tag_sector; - } - - FURI_LOG_D( - TAG, "Reading pages %d - %d", reader->pages_read, reader->pages_read + valid_pages - 1); - tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD; - tx_rx->tx_data[1] = tag_page; - tx_rx->tx_data[2] = valid_pages - 1; - tx_rx->tx_bits = 24; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - memcpy(&data->data[reader->pages_read * 4], tx_rx->rx_data, valid_pages * 4); - reader->pages_read += valid_pages; - data->data_size = reader->pages_read * 4; - } else { - FURI_LOG_D( - TAG, - "Failed to read pages %d - %d", - reader->pages_read, - reader->pages_read + valid_pages - 1); - break; - } - } - - return reader->pages_read == reader->pages_to_read; -} - -bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { - bool signature_read = false; - - FURI_LOG_D(TAG, "Reading signature"); - tx_rx->tx_data[0] = MF_UL_READ_SIG; - tx_rx->tx_data[1] = 0; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - memcpy(data->signature, tx_rx->rx_data, sizeof(data->signature)); - signature_read = true; - } else { - FURI_LOG_D(TAG, "Failed redaing signature"); - } - - return signature_read; -} - -bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { - uint8_t counter_read = 0; - - FURI_LOG_D(TAG, "Reading counters"); - bool is_single_counter = (mf_ul_get_features(data->type) & MfUltralightSupportSingleCounter) != - 0; - for(size_t i = is_single_counter ? 2 : 0; i < 3; i++) { - tx_rx->tx_data[0] = MF_UL_READ_CNT; - tx_rx->tx_data[1] = i; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { - FURI_LOG_D(TAG, "Failed to read %d counter", i); - break; - } - data->counter[i] = (tx_rx->rx_data[2] << 16) | (tx_rx->rx_data[1] << 8) | - tx_rx->rx_data[0]; - counter_read++; - } - - return counter_read == (is_single_counter ? 1 : 3); -} - -bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { - uint8_t flag_read = 0; - - FURI_LOG_D(TAG, "Reading tearing flags"); - for(size_t i = 0; i < 3; i++) { - tx_rx->tx_data[0] = MF_UL_CHECK_TEARING; - tx_rx->tx_data[1] = i; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { - FURI_LOG_D(TAG, "Failed to read %d tearing flag", i); - break; - } - data->tearing[i] = tx_rx->rx_data[0]; - flag_read++; - } - - return flag_read == 2; -} - -static bool mf_ul_probe_3des_auth(FuriHalNfcTxRxContext* tx_rx) { - tx_rx->tx_data[0] = MF_UL_AUTHENTICATE_1; - tx_rx->tx_data[1] = 0; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - bool rc = furi_hal_nfc_tx_rx(tx_rx, 50) && tx_rx->rx_bits == 9 * 8 && - tx_rx->rx_data[0] == 0xAF; - - // Reset just in case, we're not going to finish authenticating and need to if tag doesn't support auth - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - - return rc; -} - -bool mf_ul_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data) { - furi_assert(tx_rx); - furi_assert(reader); - furi_assert(data); - - bool card_read = false; - - // Read Mifare Ultralight version - if(mf_ultralight_read_version(tx_rx, reader, data)) { - if(reader->supported_features & MfUltralightSupportSignature) { - // Read Signature - mf_ultralight_read_signature(tx_rx, data); - } - } else { - uint8_t dummy[16]; - // No GET_VERSION command, check if AUTHENTICATE command available (detect UL C). - if(mf_ul_probe_3des_auth(tx_rx)) { - mf_ul_set_version_ulc(reader, data); - } else if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) { - // No AUTHENTICATE, check for NTAG203 by reading last page (41) - mf_ul_set_version_ntag203(reader, data); - } else { - // We're really an original Mifare Ultralight, reset tag for safety - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - } - - reader->supported_features = mf_ul_get_features(data->type); - } - - card_read = mf_ultralight_read_pages(tx_rx, reader, data); - - if(card_read) { - if(reader->supported_features & MfUltralightSupportReadCounter && - mf_ultralight_should_read_counters(data)) { - mf_ultralight_read_counters(tx_rx, data); - } - if(reader->supported_features & MfUltralightSupportTearingFlags) { - mf_ultralight_read_tearing_flags(tx_rx, data); - } - data->curr_authlim = 0; - - if(reader->pages_read == reader->pages_to_read && - reader->supported_features & MfUltralightSupportAuth && !data->auth_success) { - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - if(config->access.authlim == 0) { - // Attempt to auth with default PWD - uint16_t pack; - data->auth_success = mf_ultralight_authenticate(tx_rx, MF_UL_DEFAULT_PWD, &pack); - if(data->auth_success) { - config->auth_data.pwd.value = MF_UL_DEFAULT_PWD; - config->auth_data.pack.value = pack; - } else { - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - } - } - } - } - - if(reader->pages_read != reader->pages_to_read) { - if(reader->supported_features & MfUltralightSupportAuth) { - // Probably password protected, fix AUTH0 and PROT so before AUTH0 - // can be written and since AUTH0 won't be readable, like on the - // original card - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - config->auth0 = reader->pages_read; - config->access.prot = true; - } - } - - return card_read; -} - -static void mf_ul_protect_auth_data_on_read_command_i2c( - uint8_t* tx_buff, - uint8_t start_page, - uint8_t end_page, - MfUltralightEmulator* emulator) { - if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { - // Blank out PWD and PACK - if(start_page <= 229 && end_page >= 229) { - uint16_t offset = (229 - start_page) * 4; - uint8_t count = 4; - if(end_page >= 230) count += 2; - memset(&tx_buff[offset], 0, count); - } - - // Handle AUTH0 for sector 0 - if(!emulator->auth_success) { - if(emulator->config_cache.access.prot) { - uint8_t auth0 = emulator->config_cache.auth0; - if(auth0 < end_page) { - // start_page is always < auth0; otherwise is NAK'd already - uint8_t page_offset = auth0 - start_page; - uint8_t page_count = end_page - auth0; - memset(&tx_buff[page_offset * 4], 0, page_count * 4); - } - } - } - } -} - -static void mf_ul_ntag_i2c_fill_cross_area_read( - uint8_t* tx_buff, - uint8_t start_page, - uint8_t end_page, - MfUltralightEmulator* emulator) { - // For copying config or session registers in fast read - int16_t tx_page_offset; - int16_t data_page_offset; - uint8_t page_length; - bool apply = false; - MfUltralightType type = emulator->data.type; - if(emulator->curr_sector == 0) { - if(type == MfUltralightTypeNTAGI2C1K) { - if(start_page <= 233 && end_page >= 232) { - tx_page_offset = start_page - 232; - data_page_offset = 227; - page_length = 2; - apply = true; - } - } else if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { - if(start_page <= 237 && end_page >= 236) { - tx_page_offset = start_page - 236; - data_page_offset = 234; - page_length = 2; - apply = true; - } - } - } else if(emulator->curr_sector == 1) { - if(type == MfUltralightTypeNTAGI2C2K) { - if(start_page <= 233 && end_page >= 232) { - tx_page_offset = start_page - 232; - data_page_offset = 483; - page_length = 2; - apply = true; - } - } - } - - if(apply) { - while(tx_page_offset < 0 && page_length > 0) { //-V614 - ++tx_page_offset; - ++data_page_offset; - --page_length; - } - memcpy( - &tx_buff[tx_page_offset * 4], - &emulator->data.data[data_page_offset * 4], - page_length * 4); - } -} - -static bool mf_ul_check_auth(MfUltralightEmulator* emulator, uint8_t start_page, bool is_write) { - if(!emulator->auth_success) { - if(start_page >= emulator->config_cache.auth0 && - (emulator->config_cache.access.prot || is_write)) - return false; - } - - if(is_write && emulator->config_cache.access.cfglck) { - uint16_t config_start_page = emulator->page_num - 4; - if(start_page == config_start_page || start_page == config_start_page + 1) return false; - } - - return true; -} - -static bool mf_ul_ntag_i2c_plus_check_auth( - MfUltralightEmulator* emulator, - uint8_t start_page, - bool is_write) { - if(!emulator->auth_success) { - // Check NFC_PROT - if(emulator->curr_sector == 0 && (emulator->config_cache.access.prot || is_write)) { - if(start_page >= emulator->config_cache.auth0) return false; - } else if(emulator->curr_sector == 1) { - // We don't have to specifically check for type because this is done - // by address translator - uint8_t pt_i2c = emulator->data.data[231 * 4]; - // Check 2K_PROT - if(pt_i2c & 0x08) return false; - } - } - - if(emulator->curr_sector == 1) { - // Check NFC_DIS_SEC1 - if(emulator->config_cache.access.nfc_dis_sec1) return false; - } - - return true; -} - -static int16_t mf_ul_get_dynamic_lock_page_addr(MfUltralightData* data) { - switch(data->type) { - case MfUltralightTypeNTAG203: - return 0x28; - case MfUltralightTypeUL21: - case MfUltralightTypeNTAG213: - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - return data->data_size / 4 - 5; - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - return 0xe2; - case MfUltralightTypeNTAGI2C2K: - return 0x1e0; - default: - return -1; // No dynamic lock bytes - } -} - -// Returns true if page not locked -// write_page is tag address -static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) { - if(write_page < 2) return false; // Page 0-1 is always locked - if(write_page == 2) return true; // Page 2 does not have a lock flag - - // Check static lock bytes - if(write_page <= 15) { - uint16_t static_lock_bytes = emulator->data.data[10] | (emulator->data.data[11] << 8); - return (static_lock_bytes & (1 << write_page)) == 0; - } - - // Check dynamic lock bytes - - // Check max page - switch(emulator->data.type) { - case MfUltralightTypeNTAG203: - // Counter page can be locked and is after dynamic locks - if(write_page == 40) return true; - break; - case MfUltralightTypeUL21: - case MfUltralightTypeNTAG213: - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - if(write_page >= emulator->page_num - 5) return true; - break; - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - if(write_page > 225) return true; - break; - case MfUltralightTypeNTAGI2C2K: - if(write_page > 479) return true; - break; - case MfUltralightTypeNTAGI2CPlus2K: - if(write_page >= 226 && write_page <= 255) return true; - if(write_page >= 512) return true; - break; - default: - furi_crash("Unknown MFUL"); - return true; - } - - int16_t dynamic_lock_index = mf_ul_get_dynamic_lock_page_addr(&emulator->data); - if(dynamic_lock_index == -1) return true; - // Run address through converter because NTAG I2C 2K is special - uint16_t valid_pages; // unused - dynamic_lock_index = - mf_ultralight_ntag_i2c_addr_tag_to_lin( - &emulator->data, dynamic_lock_index & 0xff, dynamic_lock_index >> 8, &valid_pages) * - 4; - - uint16_t dynamic_lock_bytes = emulator->data.data[dynamic_lock_index] | - (emulator->data.data[dynamic_lock_index + 1] << 8); - uint8_t shift; - - switch(emulator->data.type) { - // low byte LSB range, MSB range - case MfUltralightTypeNTAG203: - if(write_page >= 16 && write_page <= 27) //-V560 - shift = (write_page - 16) / 4 + 1; - else if(write_page >= 28 && write_page <= 39) //-V560 - shift = (write_page - 28) / 4 + 5; - else if(write_page == 41) - shift = 12; - else { - furi_crash("Unknown MFUL"); - } - - break; - case MfUltralightTypeUL21: - case MfUltralightTypeNTAG213: - // 16-17, 30-31 - shift = (write_page - 16) / 2; - break; - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - // 16-31, 128-129 - // 16-31, 128-143 - shift = (write_page - 16) / 16; - break; - case MfUltralightTypeNTAGI2C2K: - // 16-47, 240-271 - shift = (write_page - 16) / 32; - break; - case MfUltralightTypeNTAGI2CPlus2K: - // 16-47, 256-271 - if(write_page >= 208 && write_page <= 225) - shift = 6; - else if(write_page >= 256 && write_page <= 271) - shift = 7; - else - shift = (write_page - 16) / 32; - break; - default: - furi_crash("Unknown MFUL"); - break; - } - - return (dynamic_lock_bytes & (1 << shift)) == 0; -} - -static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, FuriString* str) { - // Locals to improve readability - uint8_t mirror_page = emulator->config->mirror_page; - uint8_t mirror_byte = emulator->config->mirror.mirror_byte; - MfUltralightMirrorConf mirror_conf = emulator->config_cache.mirror.mirror_conf; - uint16_t last_user_page_index = emulator->page_num - 6; - bool uid_printed = false; - - if(mirror_conf == MfUltralightMirrorUid || mirror_conf == MfUltralightMirrorUidCounter) { - // UID range check - if(mirror_page < 4 || mirror_page > last_user_page_index - 3 || - (mirror_page == last_user_page_index - 3 && mirror_byte > 2)) { - if(mirror_conf == MfUltralightMirrorUid) return; - // NTAG21x has the peculiar behavior when UID+counter selected, if UID does not fit but - // counter will fit, it will actually mirror the counter - furi_string_cat(str, " "); - } else { - for(int i = 0; i < 3; ++i) { - furi_string_cat_printf(str, "%02X", emulator->data.data[i]); - } - // Skip BCC0 - for(int i = 4; i < 8; ++i) { - furi_string_cat_printf(str, "%02X", emulator->data.data[i]); - } - uid_printed = true; - } - - uint16_t next_byte_offset = mirror_page * 4 + mirror_byte + 14; - if(mirror_conf == MfUltralightMirrorUidCounter) ++next_byte_offset; - mirror_page = next_byte_offset / 4; - mirror_byte = next_byte_offset % 4; - } - - if(mirror_conf == MfUltralightMirrorCounter || mirror_conf == MfUltralightMirrorUidCounter) { - // Counter is only printed if counter enabled - if(emulator->config_cache.access.nfc_cnt_en) { - // Counter protection check - if(emulator->config_cache.access.nfc_cnt_pwd_prot && !emulator->auth_success) return; - // Counter range check - if(mirror_page < 4) return; - if(mirror_page > last_user_page_index - 1) return; - if(mirror_page == last_user_page_index - 1 && mirror_byte > 2) return; - - if(mirror_conf == MfUltralightMirrorUidCounter) - furi_string_cat(str, uid_printed ? "x" : " "); - - furi_string_cat_printf(str, "%06lX", emulator->data.counter[2]); - } - } -} - -static void mf_ul_increment_single_counter(MfUltralightEmulator* emulator) { - if(!emulator->read_counter_incremented && emulator->config_cache.access.nfc_cnt_en) { - if(emulator->data.counter[2] < 0xFFFFFF) { - ++emulator->data.counter[2]; - emulator->data_changed = true; - } - emulator->read_counter_incremented = true; - } -} - -static bool - mf_ul_emulate_ntag203_counter_write(MfUltralightEmulator* emulator, uint8_t* page_buff) { - // We'll reuse the existing counters for other NTAGs as staging - // Counter 0 stores original value, data is new value - uint32_t counter_value; - if(emulator->data.tearing[0] == MF_UL_TEARING_FLAG_DEFAULT) { - counter_value = emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | - (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); - } else { - // We've had a reset here, so load from original value - counter_value = emulator->data.counter[0]; - } - // Although the datasheet says increment by 0 is always possible, this is not the case on - // an actual tag. If the counter is at 0xFFFF, any writes are locked out. - if(counter_value == 0xFFFF) return false; - uint32_t increment = page_buff[0] | (page_buff[1] << 8); - if(counter_value == 0) { - counter_value = increment; - } else { - // Per datasheet specifying > 0x000F is supposed to NAK, but actual tag doesn't - increment &= 0x000F; - if(counter_value + increment > 0xFFFF) return false; - counter_value += increment; - } - // Commit to new value counter - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = (uint8_t)counter_value; - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = (uint8_t)(counter_value >> 8); - emulator->data.tearing[0] = MF_UL_TEARING_FLAG_DEFAULT; - if(counter_value == 0xFFFF) { - // Tag will lock out counter if final number is 0xFFFF, even if you try to roll it back - emulator->data.counter[1] = 0xFFFF; - } - emulator->data_changed = true; - return true; -} - -static void mf_ul_emulate_write( - MfUltralightEmulator* emulator, - int16_t tag_addr, - int16_t write_page, - uint8_t* page_buff) { - // Assumption: all access checks have been completed - - if(tag_addr == 2) { - // Handle static locks - uint16_t orig_static_locks = emulator->data.data[write_page * 4 + 2] | - (emulator->data.data[write_page * 4 + 3] << 8); - uint16_t new_static_locks = page_buff[2] | (page_buff[3] << 8); - if(orig_static_locks & 1) new_static_locks &= ~0x08; - if(orig_static_locks & 2) new_static_locks &= ~0xF0; - if(orig_static_locks & 4) new_static_locks &= 0xFF; - new_static_locks |= orig_static_locks; - page_buff[0] = emulator->data.data[write_page * 4]; - page_buff[1] = emulator->data.data[write_page * 4 + 1]; - page_buff[2] = new_static_locks & 0xff; - page_buff[3] = new_static_locks >> 8; - } else if(tag_addr == 3) { - // Handle OTP/capability container - *(uint32_t*)page_buff |= *(uint32_t*)&emulator->data.data[write_page * 4]; - } else if(tag_addr == mf_ul_get_dynamic_lock_page_addr(&emulator->data)) { - // Handle dynamic locks - if(emulator->data.type == MfUltralightTypeNTAG203) { - // NTAG203 lock bytes are a bit different from the others - uint8_t orig_page_lock_byte = emulator->data.data[write_page * 4]; - uint8_t orig_cnt_lock_byte = emulator->data.data[write_page * 4 + 1]; - uint8_t new_page_lock_byte = page_buff[0]; - uint8_t new_cnt_lock_byte = page_buff[1]; - - if(orig_page_lock_byte & 0x01) // Block lock bits 1-3 - new_page_lock_byte &= ~0x0E; - if(orig_page_lock_byte & 0x10) // Block lock bits 5-7 - new_page_lock_byte &= ~0xE0; - for(uint8_t i = 0; i < 4; ++i) { - if(orig_cnt_lock_byte & (1 << i)) // Block lock counter bit - new_cnt_lock_byte &= ~(1 << (4 + i)); - } - - new_page_lock_byte |= orig_page_lock_byte; - new_cnt_lock_byte |= orig_cnt_lock_byte; - page_buff[0] = new_page_lock_byte; - page_buff[1] = new_cnt_lock_byte; - } else { - uint16_t orig_locks = emulator->data.data[write_page * 4] | - (emulator->data.data[write_page * 4 + 1] << 8); - uint8_t orig_block_locks = emulator->data.data[write_page * 4 + 2]; - uint16_t new_locks = page_buff[0] | (page_buff[1] << 8); - uint8_t new_block_locks = page_buff[2]; - - int block_lock_count; - switch(emulator->data.type) { - case MfUltralightTypeUL21: - block_lock_count = 5; - break; - case MfUltralightTypeNTAG213: - block_lock_count = 6; - break; - case MfUltralightTypeNTAG215: - block_lock_count = 4; - break; - case MfUltralightTypeNTAG216: - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - block_lock_count = 7; - break; - case MfUltralightTypeNTAGI2C2K: - case MfUltralightTypeNTAGI2CPlus2K: - block_lock_count = 8; - break; - default: - furi_crash("Unknown MFUL"); - break; - } - - for(int i = 0; i < block_lock_count; ++i) { - if(orig_block_locks & (1 << i)) new_locks &= ~(3 << (2 * i)); - } - - new_locks |= orig_locks; - new_block_locks |= orig_block_locks; - - page_buff[0] = new_locks & 0xff; - page_buff[1] = new_locks >> 8; - page_buff[2] = new_block_locks; - if(emulator->data.type >= MfUltralightTypeUL21 && //-V1016 - emulator->data.type <= MfUltralightTypeNTAG216) - page_buff[3] = MF_UL_TEARING_FLAG_DEFAULT; - else - page_buff[3] = 0; - } - } - - memcpy(&emulator->data.data[write_page * 4], page_buff, 4); - emulator->data_changed = true; -} - -bool mf_ul_emulation_supported(MfUltralightData* data) { - return data->type != MfUltralightTypeULC; -} - -void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) { - emulator->comp_write_cmd_started = false; - emulator->sector_select_cmd_started = false; - emulator->curr_sector = 0; - emulator->ntag_i2c_plus_sector3_lockout = false; - emulator->auth_success = false; - if(is_power_cycle) { - if(emulator->config != NULL) emulator->config_cache = *emulator->config; - - if(emulator->supported_features & MfUltralightSupportSingleCounter) { - emulator->read_counter_incremented = false; - } - - if(emulator->data.type == MfUltralightTypeNTAG203) { - // Apply lockout if counter ever reached 0xFFFF - if(emulator->data.counter[1] == 0xFFFF) { - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = 0xFF; - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = 0xFF; - } - // Copy original counter value from data - emulator->data.counter[0] = - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | - (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); - } - } else { - if(emulator->config != NULL) { - // ACCESS (less CFGLCK) and AUTH0 are updated when reactivated - // MIRROR_CONF is not; don't know about STRG_MOD_EN, but we're not using that anyway - emulator->config_cache.access.value = (emulator->config->access.value & 0xBF) | - (emulator->config_cache.access.value & 0x40); - emulator->config_cache.auth0 = emulator->config->auth0; - } - } - if(emulator->data.type == MfUltralightTypeNTAG203) { - // Mark counter as dirty - emulator->data.tearing[0] = 0; - } -} - -void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data) { - FURI_LOG_D(TAG, "Prepare emulation"); - emulator->data = *data; - emulator->supported_features = mf_ul_get_features(data->type); - emulator->config = mf_ultralight_get_config_pages(&emulator->data); - emulator->page_num = emulator->data.data_size / 4; - emulator->data_changed = false; - memset(&emulator->auth_attempt, 0, sizeof(MfUltralightAuth)); - mf_ul_reset_emulation(emulator, true); -} - -bool mf_ul_prepare_emulation_response( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len, - uint32_t* data_type, - void* context) { - furi_assert(context); - MfUltralightEmulator* emulator = context; - uint16_t tx_bytes = 0; - uint16_t tx_bits = 0; - bool command_parsed = false; - bool send_ack = false; - bool respond_nothing = false; - bool reset_idle = false; - -#ifdef FURI_DEBUG - FuriString* debug_buf; - debug_buf = furi_string_alloc(); - for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) { - furi_string_cat_printf(debug_buf, "%02x ", buff_rx[i]); - } - furi_string_trim(debug_buf); - FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, furi_string_get_cstr(debug_buf)); - furi_string_reset(debug_buf); -#endif - - // Check composite commands - if(emulator->comp_write_cmd_started) { - if(buff_rx_len == 16 * 8) { - if(emulator->data.type == MfUltralightTypeNTAG203 && - emulator->comp_write_page_addr == MF_UL_NTAG203_COUNTER_PAGE) { - send_ack = mf_ul_emulate_ntag203_counter_write(emulator, buff_rx); - command_parsed = send_ack; - } else { - mf_ul_emulate_write( - emulator, - emulator->comp_write_page_addr, - emulator->comp_write_page_addr, - buff_rx); - send_ack = true; - command_parsed = true; - } - } - emulator->comp_write_cmd_started = false; - } else if(emulator->sector_select_cmd_started) { - if(buff_rx_len == 4 * 8) { - if(buff_rx[0] <= 0xFE) { - emulator->curr_sector = buff_rx[0] > 3 ? 0 : buff_rx[0]; - emulator->ntag_i2c_plus_sector3_lockout = false; - command_parsed = true; - respond_nothing = true; - FURI_LOG_D(TAG, "Changing sector to %d", emulator->curr_sector); - } - } - emulator->sector_select_cmd_started = false; - } else if(buff_rx_len >= 8) { - uint8_t cmd = buff_rx[0]; - if(cmd == MF_UL_GET_VERSION_CMD) { - if(emulator->data.type >= MfUltralightTypeUL11) { - if(buff_rx_len == 1 * 8) { - tx_bytes = sizeof(emulator->data.version); - memcpy(buff_tx, &emulator->data.version, tx_bytes); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } - } else if(cmd == MF_UL_READ_CMD) { - if(buff_rx_len == (1 + 1) * 8) { - int16_t start_page = buff_rx[1]; - tx_bytes = 16; - if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { - if(start_page < emulator->page_num) { - do { - uint8_t copied_pages = 0; - uint8_t src_page = start_page; - uint8_t last_page_plus_one = start_page + 4; - uint8_t pwd_page = emulator->page_num - 2; - FuriString* ascii_mirror = NULL; - size_t ascii_mirror_len = 0; - const char* ascii_mirror_cptr = NULL; - uint8_t ascii_mirror_curr_page = 0; - uint8_t ascii_mirror_curr_byte = 0; - if(last_page_plus_one > emulator->page_num) - last_page_plus_one = emulator->page_num; - if(emulator->supported_features & MfUltralightSupportAuth) { - if(!mf_ul_check_auth(emulator, start_page, false)) break; - if(!emulator->auth_success && emulator->config_cache.access.prot && - emulator->config_cache.auth0 < last_page_plus_one) - last_page_plus_one = emulator->config_cache.auth0; - } - if(emulator->supported_features & MfUltralightSupportSingleCounter) - mf_ul_increment_single_counter(emulator); - if(emulator->supported_features & MfUltralightSupportAsciiMirror && - emulator->config_cache.mirror.mirror_conf != - MfUltralightMirrorNone) { - ascii_mirror_curr_byte = emulator->config->mirror.mirror_byte; - ascii_mirror_curr_page = emulator->config->mirror_page; - // Try to avoid wasting time making mirror if we won't copy it - // Conservatively check with UID+counter mirror size - if(last_page_plus_one > ascii_mirror_curr_page && - start_page + 3 >= ascii_mirror_curr_page && - start_page <= ascii_mirror_curr_page + 6) { - ascii_mirror = furi_string_alloc(); - mf_ul_make_ascii_mirror(emulator, ascii_mirror); - ascii_mirror_len = furi_string_utf8_length(ascii_mirror); - ascii_mirror_cptr = furi_string_get_cstr(ascii_mirror); - // Move pointer to where it should be to start copying - if(ascii_mirror_len > 0 && - ascii_mirror_curr_page < start_page && - ascii_mirror_curr_byte != 0) { - uint8_t diff = 4 - ascii_mirror_curr_byte; - ascii_mirror_len -= diff; - ascii_mirror_cptr += diff; - ascii_mirror_curr_byte = 0; - ++ascii_mirror_curr_page; - } - while(ascii_mirror_len > 0 && - ascii_mirror_curr_page < start_page) { - uint8_t diff = ascii_mirror_len > 4 ? 4 : ascii_mirror_len; - ascii_mirror_len -= diff; - ascii_mirror_cptr += diff; - ++ascii_mirror_curr_page; - } - } - } - - uint8_t* dest_ptr = buff_tx; - while(copied_pages < 4) { - // Copy page - memcpy(dest_ptr, &emulator->data.data[src_page * 4], 4); - - // Note: don't have to worry about roll-over with ASCII mirror because - // lowest valid page for it is 4, while roll-over will at best read - // pages 0-2 - if(ascii_mirror_len > 0 && src_page == ascii_mirror_curr_page) { - // Copy ASCII mirror - size_t copy_len = 4 - ascii_mirror_curr_byte; - if(copy_len > ascii_mirror_len) copy_len = ascii_mirror_len; - for(size_t i = 0; i < copy_len; ++i) { - if(*ascii_mirror_cptr != ' ') - dest_ptr[ascii_mirror_curr_byte] = - (uint8_t)*ascii_mirror_cptr; - ++ascii_mirror_curr_byte; - ++ascii_mirror_cptr; - } - ascii_mirror_len -= copy_len; - // Don't care if this is inaccurate after ascii_mirror_len = 0 - ascii_mirror_curr_byte = 0; - ++ascii_mirror_curr_page; - } - - if(emulator->supported_features & MfUltralightSupportAuth) { - if(src_page == pwd_page || src_page == pwd_page + 1) { - // Blank out PWD and PACK pages - memset(dest_ptr, 0, 4); - } - } - - dest_ptr += 4; - ++copied_pages; - ++src_page; - if(src_page >= last_page_plus_one) src_page = 0; - } - if(ascii_mirror != NULL) { - furi_string_free(ascii_mirror); - } - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } while(false); - } - } else { - uint16_t valid_pages; - start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( - &emulator->data, start_page, emulator->curr_sector, &valid_pages); - if(start_page != -1) { - if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || - mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { - if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && - emulator->curr_sector == 3 && valid_pages == 1) { - // Rewind back a sector to match behavior on a real tag - --start_page; - ++valid_pages; - } - - uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4; - FURI_LOG_D( - TAG, - "NTAG I2C Emu: page valid, %02x:%02x -> %d, %d", - emulator->curr_sector, - buff_rx[1], - start_page, - valid_pages); - memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count); - // For NTAG I2C, there's no roll-over; remainder is filled by null bytes - if(copy_count < tx_bytes) - memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); - // Special case: NTAG I2C Plus sector 0 page 233 read crosses into page 236 - if(start_page == 233) - memcpy( - &buff_tx[12], &emulator->data.data[(start_page + 1) * 4], 4); - mf_ul_protect_auth_data_on_read_command_i2c( - buff_tx, start_page, start_page + copy_count / 4 - 1, emulator); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } else { - FURI_LOG_D( - TAG, - "NTAG I2C Emu: page invalid, %02x:%02x", - emulator->curr_sector, - buff_rx[1]); - if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && - emulator->curr_sector == 3 && - !emulator->ntag_i2c_plus_sector3_lockout) { - // NTAG I2C Plus has a weird behavior where if you read sector 3 - // at an invalid address, it responds with zeroes then locks - // the read out, while if you read the mirrored session registers, - // it returns both session registers on either pages - memset(buff_tx, 0, tx_bytes); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - emulator->ntag_i2c_plus_sector3_lockout = true; - } - } - } - if(!command_parsed) tx_bytes = 0; - } - } else if(cmd == MF_UL_FAST_READ_CMD) { - if(emulator->supported_features & MfUltralightSupportFastRead) { - if(buff_rx_len == (1 + 2) * 8) { - int16_t start_page = buff_rx[1]; - uint8_t end_page = buff_rx[2]; - if(start_page <= end_page) { - tx_bytes = ((end_page + 1) - start_page) * 4; - if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { - if((start_page < emulator->page_num) && - (end_page < emulator->page_num)) { - do { - if(emulator->supported_features & MfUltralightSupportAuth) { - // NAK if not authenticated and requested pages cross over AUTH0 - if(!emulator->auth_success && - emulator->config_cache.access.prot && - (start_page >= emulator->config_cache.auth0 || - end_page >= emulator->config_cache.auth0)) - break; - } - if(emulator->supported_features & - MfUltralightSupportSingleCounter) - mf_ul_increment_single_counter(emulator); - - // Copy requested pages - memcpy( - buff_tx, &emulator->data.data[start_page * 4], tx_bytes); - - if(emulator->supported_features & - MfUltralightSupportAsciiMirror && - emulator->config_cache.mirror.mirror_conf != - MfUltralightMirrorNone) { - // Copy ASCII mirror - // Less stringent check here, because expecting FAST_READ to - // only be issued once rather than repeatedly - FuriString* ascii_mirror; - ascii_mirror = furi_string_alloc(); - mf_ul_make_ascii_mirror(emulator, ascii_mirror); - size_t ascii_mirror_len = - furi_string_utf8_length(ascii_mirror); - const char* ascii_mirror_cptr = - furi_string_get_cstr(ascii_mirror); - int16_t mirror_start_offset = - (emulator->config->mirror_page - start_page) * 4 + - emulator->config->mirror.mirror_byte; - if(mirror_start_offset < 0) { - if(mirror_start_offset < -(int16_t)ascii_mirror_len) { - // Past ASCII mirror, don't copy - ascii_mirror_len = 0; - } else { - ascii_mirror_cptr += -mirror_start_offset; - ascii_mirror_len -= -mirror_start_offset; - mirror_start_offset = 0; - } - } - if(ascii_mirror_len > 0) { - int16_t mirror_end_offset = - mirror_start_offset + ascii_mirror_len; - if(mirror_end_offset > (end_page + 1) * 4) { - mirror_end_offset = (end_page + 1) * 4; - ascii_mirror_len = - mirror_end_offset - mirror_start_offset; - } - for(size_t i = 0; i < ascii_mirror_len; ++i) { - if(*ascii_mirror_cptr != ' ') - buff_tx[mirror_start_offset] = - (uint8_t)*ascii_mirror_cptr; - ++mirror_start_offset; - ++ascii_mirror_cptr; - } - } - furi_string_free(ascii_mirror); - } - - if(emulator->supported_features & MfUltralightSupportAuth) { - // Clear PWD and PACK pages - uint8_t pwd_page = emulator->page_num - 2; - int16_t pwd_page_offset = pwd_page - start_page; - // PWD page - if(pwd_page_offset >= 0 && pwd_page <= end_page) { - memset(&buff_tx[pwd_page_offset * 4], 0, 4); - // PACK page - if(pwd_page + 1 <= end_page) - memset(&buff_tx[(pwd_page_offset + 1) * 4], 0, 4); - } - } - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } while(false); - } - } else { - uint16_t valid_pages; - start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( - &emulator->data, start_page, emulator->curr_sector, &valid_pages); - if(start_page != -1) { - if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || - mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { - uint16_t copy_count = tx_bytes; - if(copy_count > valid_pages * 4) copy_count = valid_pages * 4; - memcpy( - buff_tx, &emulator->data.data[start_page * 4], copy_count); - if(copy_count < tx_bytes) - memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); - mf_ul_ntag_i2c_fill_cross_area_read( - buff_tx, buff_rx[1], buff_rx[2], emulator); - mf_ul_protect_auth_data_on_read_command_i2c( - buff_tx, - start_page, - start_page + copy_count / 4 - 1, - emulator); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } - } - if(!command_parsed) tx_bytes = 0; - } - } - } - } else if(cmd == MF_UL_WRITE) { - if(buff_rx_len == (1 + 5) * 8) { - do { - uint8_t orig_write_page = buff_rx[1]; - int16_t write_page = orig_write_page; - uint16_t valid_pages; // unused - write_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( - &emulator->data, write_page, emulator->curr_sector, &valid_pages); - if(write_page == -1) // NTAG I2C range check - break; - else if(write_page < 2 || write_page >= emulator->page_num) // Other MFUL/NTAG range check - break; - - if(emulator->supported_features & MfUltralightSupportAuth) { - if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { - if(!mf_ul_ntag_i2c_plus_check_auth(emulator, orig_write_page, true)) - break; - } else { - if(!mf_ul_check_auth(emulator, orig_write_page, true)) break; - } - } - int16_t tag_addr = mf_ultralight_page_addr_to_tag_addr( - emulator->curr_sector, orig_write_page); - if(!mf_ul_check_lock(emulator, tag_addr)) break; - if(emulator->data.type == MfUltralightTypeNTAG203 && - orig_write_page == MF_UL_NTAG203_COUNTER_PAGE) { - send_ack = mf_ul_emulate_ntag203_counter_write(emulator, &buff_rx[2]); - command_parsed = send_ack; - } else { - mf_ul_emulate_write(emulator, tag_addr, write_page, &buff_rx[2]); - send_ack = true; - command_parsed = true; - } - } while(false); - } - } else if(cmd == MF_UL_FAST_WRITE) { - if(emulator->supported_features & MfUltralightSupportFastWrite) { - if(buff_rx_len == (1 + 66) * 8) { - if(buff_rx[1] == 0xF0 && buff_rx[2] == 0xFF) { - // TODO: update when SRAM emulation implemented - send_ack = true; - command_parsed = true; - } - } - } - } else if(cmd == MF_UL_COMP_WRITE) { - if(emulator->supported_features & MfUltralightSupportCompatWrite) { - if(buff_rx_len == (1 + 1) * 8) { - uint8_t write_page = buff_rx[1]; - do { - if(write_page < 2 || write_page >= emulator->page_num) break; - if(emulator->supported_features & MfUltralightSupportAuth && - !mf_ul_check_auth(emulator, write_page, true)) - break; - // Note we don't convert to tag addr here because there's only one sector - if(!mf_ul_check_lock(emulator, write_page)) break; - - emulator->comp_write_cmd_started = true; - emulator->comp_write_page_addr = write_page; - send_ack = true; - command_parsed = true; - } while(false); - } - } - } else if(cmd == MF_UL_READ_CNT) { - if(emulator->supported_features & MfUltralightSupportReadCounter) { - if(buff_rx_len == (1 + 1) * 8) { - do { - uint8_t cnt_num = buff_rx[1]; - - // NTAG21x checks - if(emulator->supported_features & MfUltralightSupportSingleCounter) { - if(cnt_num != 2) break; // Only counter 2 is available - if(!emulator->config_cache.access.nfc_cnt_en) - break; // NAK if counter not enabled - if(emulator->config_cache.access.nfc_cnt_pwd_prot && - !emulator->auth_success) - break; - } - - if(cnt_num < 3) { - buff_tx[0] = emulator->data.counter[cnt_num] & 0xFF; - buff_tx[1] = (emulator->data.counter[cnt_num] >> 8) & 0xFF; - buff_tx[2] = (emulator->data.counter[cnt_num] >> 16) & 0xFF; - tx_bytes = 3; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } while(false); - } - } - } else if(cmd == MF_UL_INC_CNT) { - if(emulator->supported_features & MfUltralightSupportIncrCounter) { - if(buff_rx_len == (1 + 5) * 8) { - uint8_t cnt_num = buff_rx[1]; - uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); - // TODO: can you increment by 0 when counter is at 0xffffff? - if((cnt_num < 3) && (emulator->data.counter[cnt_num] != 0x00FFFFFF) && - (emulator->data.counter[cnt_num] + inc <= 0x00FFFFFF)) { - emulator->data.counter[cnt_num] += inc; - // We're RAM-backed, so tearing never happens - emulator->data.tearing[cnt_num] = MF_UL_TEARING_FLAG_DEFAULT; - emulator->data_changed = true; - send_ack = true; - command_parsed = true; - } - } - } - } else if(cmd == MF_UL_PWD_AUTH) { - if(emulator->supported_features & MfUltralightSupportAuth) { - if(buff_rx_len == (1 + 4) * 8) { - // Record password sent by PCD - memcpy( - emulator->auth_attempt.pwd.raw, - &buff_rx[1], - sizeof(emulator->auth_attempt.pwd.raw)); - emulator->auth_attempted = true; - if(emulator->auth_received_callback) { - emulator->auth_received_callback( - emulator->auth_attempt, emulator->context); - } - - uint16_t scaled_authlim = mf_ultralight_calc_auth_count(&emulator->data); - if(scaled_authlim != 0 && emulator->data.curr_authlim >= scaled_authlim) { - if(emulator->data.curr_authlim != UINT16_MAX) { - // Handle case where AUTHLIM has been lowered or changed from 0 - emulator->data.curr_authlim = UINT16_MAX; - emulator->data_changed = true; - } - // AUTHLIM reached, always fail - buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - mf_ul_reset_emulation(emulator, false); - command_parsed = true; - } else { - if(memcmp(&buff_rx[1], emulator->config->auth_data.pwd.raw, 4) == 0) { - // Correct password - buff_tx[0] = emulator->config->auth_data.pack.raw[0]; - buff_tx[1] = emulator->config->auth_data.pack.raw[1]; - tx_bytes = 2; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - emulator->auth_success = true; - command_parsed = true; - if(emulator->data.curr_authlim != 0) { - // Reset current AUTHLIM - emulator->data.curr_authlim = 0; - emulator->data_changed = true; - } - } else if(!emulator->config->auth_data.pwd.value) { - // Unknown password, pretend to be an Amiibo - buff_tx[0] = 0x80; - buff_tx[1] = 0x80; - tx_bytes = 2; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - emulator->auth_success = true; - command_parsed = true; - } else { - // Wrong password, increase negative verification count - if(emulator->data.curr_authlim < UINT16_MAX) { - ++emulator->data.curr_authlim; - emulator->data_changed = true; - } - if(scaled_authlim != 0 && - emulator->data.curr_authlim >= scaled_authlim) { - emulator->data.curr_authlim = UINT16_MAX; - buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - mf_ul_reset_emulation(emulator, false); - command_parsed = true; - } else { - // Should delay here to slow brute forcing - } - } - } - } - } - } else if(cmd == MF_UL_READ_SIG) { - if(emulator->supported_features & MfUltralightSupportSignature) { - // Check 2nd byte = 0x00 - RFU - if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0x00) { - tx_bytes = sizeof(emulator->data.signature); - memcpy(buff_tx, emulator->data.signature, tx_bytes); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } - } else if(cmd == MF_UL_CHECK_TEARING) { - if(emulator->supported_features & MfUltralightSupportTearingFlags) { - if(buff_rx_len == (1 + 1) * 8) { - uint8_t cnt_num = buff_rx[1]; - if(cnt_num < 3) { - buff_tx[0] = emulator->data.tearing[cnt_num]; - tx_bytes = 1; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } - } - } else if(cmd == MF_UL_HALT_START) { - reset_idle = true; - FURI_LOG_D(TAG, "Received HLTA"); - } else if(cmd == MF_UL_SECTOR_SELECT) { - if(emulator->supported_features & MfUltralightSupportSectorSelect) { - if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0xFF) { - // Send ACK - emulator->sector_select_cmd_started = true; - send_ack = true; - command_parsed = true; - } - } - } else if(cmd == MF_UL_READ_VCSL) { - if(emulator->supported_features & MfUltralightSupportVcsl) { - if(buff_rx_len == (1 + 20) * 8) { - buff_tx[0] = emulator->config_cache.vctid; - tx_bytes = 1; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } - } else { - // NTAG203 appears to NAK instead of just falling off on invalid commands - if(emulator->data.type != MfUltralightTypeNTAG203) reset_idle = true; - FURI_LOG_D(TAG, "Received invalid command"); - } - } else { - reset_idle = true; - FURI_LOG_D(TAG, "Received invalid buffer less than 8 bits in length"); - } - - if(reset_idle) { - mf_ul_reset_emulation(emulator, false); - tx_bits = 0; - command_parsed = true; - } - - if(!command_parsed) { - // Send NACK - buff_tx[0] = MF_UL_NAK_INVALID_ARGUMENT; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - // Every NAK should cause reset to IDLE - mf_ul_reset_emulation(emulator, false); - } else if(send_ack) { - buff_tx[0] = MF_UL_ACK; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - } - - if(respond_nothing) { - *buff_tx_len = UINT16_MAX; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - } else { - // Return tx buffer size in bits - if(tx_bytes) { - tx_bits = tx_bytes * 8; - } - *buff_tx_len = tx_bits; - } - -#ifdef FURI_DEBUG - if(*buff_tx_len == UINT16_MAX) { - FURI_LOG_T(TAG, "Emu TX: no reply"); - } else if(*buff_tx_len > 0) { - int count = (*buff_tx_len + 7) / 8; - for(int i = 0; i < count; ++i) { - furi_string_cat_printf(debug_buf, "%02x ", buff_tx[i]); - } - furi_string_trim(debug_buf); - FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, furi_string_get_cstr(debug_buf)); - furi_string_free(debug_buf); - } else { - FURI_LOG_T(TAG, "Emu TX: HALT"); - } -#endif - - return tx_bits > 0; -} - -bool mf_ul_is_full_capture(MfUltralightData* data) { - if(data->data_read != data->data_size) return false; - - // Having read all the pages doesn't mean that we've got everything. - // By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000, - // so a default read on an auth-supported NTAG is never complete. - if(!(mf_ul_get_features(data->type) & MfUltralightSupportAuth)) return true; - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - return config->auth_data.pwd.value != 0 || config->auth_data.pack.value != 0; -} diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h deleted file mode 100644 index 9cb7ca5356..0000000000 --- a/lib/nfc/protocols/mifare_ultralight.h +++ /dev/null @@ -1,269 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM -#define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4) - -#define MF_UL_TEARING_FLAG_DEFAULT (0xBD) - -#define MF_UL_HALT_START (0x50) -#define MF_UL_GET_VERSION_CMD (0x60) -#define MF_UL_READ_CMD (0x30) -#define MF_UL_FAST_READ_CMD (0x3A) -#define MF_UL_WRITE (0xA2) -#define MF_UL_FAST_WRITE (0xA6) -#define MF_UL_COMP_WRITE (0xA0) -#define MF_UL_READ_CNT (0x39) -#define MF_UL_INC_CNT (0xA5) -#define MF_UL_AUTHENTICATE_1 (0x1A) -#define MF_UL_PWD_AUTH (0x1B) -#define MF_UL_READ_SIG (0x3C) -#define MF_UL_CHECK_TEARING (0x3E) -#define MF_UL_READ_VCSL (0x4B) -#define MF_UL_SECTOR_SELECT (0xC2) - -#define MF_UL_ACK (0xa) -#define MF_UL_NAK_INVALID_ARGUMENT (0x0) -#define MF_UL_NAK_AUTHLIM_REACHED (0x4) - -#define MF_UL_NTAG203_COUNTER_PAGE (41) - -#define MF_UL_DEFAULT_PWD (0xFFFFFFFF) - -typedef enum { - MfUltralightAuthMethodManual, - MfUltralightAuthMethodAmeebo, - MfUltralightAuthMethodXiaomi, - MfUltralightAuthMethodAuto, -} MfUltralightAuthMethod; - -// Important: order matters; some features are based on positioning in this enum -typedef enum { - MfUltralightTypeUnknown, - MfUltralightTypeNTAG203, - MfUltralightTypeULC, - // Below have config pages and GET_VERSION support - MfUltralightTypeUL11, - MfUltralightTypeUL21, - MfUltralightTypeNTAG213, - MfUltralightTypeNTAG215, - MfUltralightTypeNTAG216, - // Below also have sector select - // NTAG I2C's *does not* have regular config pages, so it's a bit of an odd duck - MfUltralightTypeNTAGI2C1K, - MfUltralightTypeNTAGI2C2K, - // NTAG I2C Plus has stucture expected from NTAG21x - MfUltralightTypeNTAGI2CPlus1K, - MfUltralightTypeNTAGI2CPlus2K, - - // Keep last for number of types calculation - MfUltralightTypeNum, -} MfUltralightType; - -typedef enum { - MfUltralightSupportNone = 0, - MfUltralightSupportFastRead = 1 << 0, - MfUltralightSupportTearingFlags = 1 << 1, - MfUltralightSupportReadCounter = 1 << 2, - MfUltralightSupportIncrCounter = 1 << 3, - MfUltralightSupportSignature = 1 << 4, - MfUltralightSupportFastWrite = 1 << 5, - MfUltralightSupportCompatWrite = 1 << 6, - MfUltralightSupportAuth = 1 << 7, - MfUltralightSupportVcsl = 1 << 8, - MfUltralightSupportSectorSelect = 1 << 9, - // NTAG21x only has counter 2 - MfUltralightSupportSingleCounter = 1 << 10, - // ASCII mirror is not a command, but handy to have as a flag - MfUltralightSupportAsciiMirror = 1 << 11, - // NTAG203 counter that's in memory rather than through a command - MfUltralightSupportCounterInMemory = 1 << 12, - MfUltralightSupport3DesAuth = 1 << 13, -} MfUltralightFeatures; - -typedef enum { - MfUltralightMirrorNone, - MfUltralightMirrorUid, - MfUltralightMirrorCounter, - MfUltralightMirrorUidCounter, -} MfUltralightMirrorConf; - -typedef struct { - uint8_t header; - uint8_t vendor_id; - uint8_t prod_type; - uint8_t prod_subtype; - uint8_t prod_ver_major; - uint8_t prod_ver_minor; - uint8_t storage_size; - uint8_t protocol_type; -} MfUltralightVersion; - -typedef struct { - uint8_t sn0[3]; - uint8_t btBCC0; - uint8_t sn1[4]; - uint8_t btBCC1; - uint8_t internal; - uint8_t lock[2]; - uint8_t otp[4]; -} MfUltralightManufacturerBlock; - -typedef struct { - MfUltralightType type; - MfUltralightVersion version; - uint8_t signature[32]; - uint32_t counter[3]; - uint8_t tearing[3]; - MfUltralightAuthMethod auth_method; - uint8_t auth_key[4]; - bool auth_success; - uint16_t curr_authlim; - uint16_t data_size; - uint8_t data[MF_UL_MAX_DUMP_SIZE]; - uint16_t data_read; -} MfUltralightData; - -typedef struct __attribute__((packed)) { - union { - uint8_t raw[4]; - uint32_t value; - } pwd; - union { - uint8_t raw[2]; - uint16_t value; - } pack; -} MfUltralightAuth; - -// Common configuration pages for MFUL EV1, NTAG21x, and NTAG I2C Plus -typedef struct __attribute__((packed)) { - union { - uint8_t value; - struct { - uint8_t rfui1 : 2; - bool strg_mod_en : 1; - bool rfui2 : 1; - uint8_t mirror_byte : 2; - MfUltralightMirrorConf mirror_conf : 2; - }; - } mirror; - uint8_t rfui1; - uint8_t mirror_page; - uint8_t auth0; - union { - uint8_t value; - struct { - uint8_t authlim : 3; - bool nfc_cnt_pwd_prot : 1; - bool nfc_cnt_en : 1; - bool nfc_dis_sec1 : 1; // NTAG I2C Plus only - bool cfglck : 1; - bool prot : 1; - }; - } access; - uint8_t vctid; - uint8_t rfui2[2]; - MfUltralightAuth auth_data; - uint8_t rfui3[2]; -} MfUltralightConfigPages; - -typedef struct { - uint16_t pages_to_read; - int16_t pages_read; - MfUltralightFeatures supported_features; -} MfUltralightReader; - -// TODO rework with reader analyzer -typedef void (*MfUltralightAuthReceivedCallback)(MfUltralightAuth auth, void* context); - -typedef struct { - MfUltralightData data; - MfUltralightConfigPages* config; - // Most config values don't apply until power cycle, so cache config pages - // for correct behavior - MfUltralightConfigPages config_cache; - MfUltralightFeatures supported_features; - uint16_t page_num; - bool data_changed; - bool comp_write_cmd_started; - uint8_t comp_write_page_addr; - bool auth_success; - uint8_t curr_sector; - bool sector_select_cmd_started; - bool ntag_i2c_plus_sector3_lockout; - bool read_counter_incremented; - bool auth_attempted; - MfUltralightAuth auth_attempt; - - // TODO rework with reader analyzer - MfUltralightAuthReceivedCallback auth_received_callback; - void* context; -} MfUltralightEmulator; - -void mf_ul_reset(MfUltralightData* data); - -bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -bool mf_ultralight_read_version( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ultralight_read_pages_direct( - FuriHalNfcTxRxContext* tx_rx, - uint8_t start_index, - uint8_t* data); - -bool mf_ultralight_read_pages( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ultralight_fast_read_pages( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); - -bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); - -bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); - -bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack); - -MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data); - -bool mf_ul_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ul_emulation_supported(MfUltralightData* data); - -void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle); - -void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data); - -bool mf_ul_prepare_emulation_response( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len, - uint32_t* data_type, - void* context); - -uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data); - -uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data); - -bool mf_ul_is_full_capture(MfUltralightData* data); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_device_base.h b/lib/nfc/protocols/nfc_device_base.h new file mode 100644 index 0000000000..4f3480d455 --- /dev/null +++ b/lib/nfc/protocols/nfc_device_base.h @@ -0,0 +1,26 @@ +/** + * @file nfc_device_base.h + * @brief Common top-level types for the NFC protocol stack. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Verbosity level of the displayed NFC device name. + */ +typedef enum { + NfcDeviceNameTypeFull, /**< Display full(verbose) name. */ + NfcDeviceNameTypeShort, /**< Display shortened name. */ +} NfcDeviceNameType; + +/** + * @brief Generic opaque type for protocol-specific NFC device data. + */ +typedef void NfcDeviceData; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_device_base_i.h b/lib/nfc/protocols/nfc_device_base_i.h new file mode 100644 index 0000000000..946ae76dcc --- /dev/null +++ b/lib/nfc/protocols/nfc_device_base_i.h @@ -0,0 +1,161 @@ +/** + * @file nfc_device_base_i.h + * @brief Abstract interface definitions for the NFC device system. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#pragma once + +#include "nfc_device_base.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Allocate the protocol-specific NFC device data instance. + * + * @returns pointer to the allocated instance. + */ +typedef NfcDeviceData* (*NfcDeviceAlloc)(); + +/** + * @brief Delete the protocol-specific NFC device data instance. + * + * @param[in,out] data pointer to the instance to be deleted. + */ +typedef void (*NfcDeviceFree)(NfcDeviceData* data); + +/** + * @brief Reset the NFC device data instance. + * + * The behaviour is protocol-specific. Usually, required fields are zeroed or + * set to their initial values. + * + * @param[in,out] data pointer to the instance to be reset. + */ +typedef void (*NfcDeviceReset)(NfcDeviceData* data); + +/** + * @brief Copy source instance's data into the destination so that they become equal. + * + * @param[in,out] data pointer to the destination instance. + * @param[in] other pointer to the source instance. + */ +typedef void (*NfcDeviceCopy)(NfcDeviceData* data, const NfcDeviceData* other); + +/** + * @brief Deprecated. Do not use in new protocols. + * @deprecated do not use in new protocols. + * + * @param[in,out] data pointer to the instance to be tested. + * @param[in] device_type pointer to a FuriString containing a device type identifier. + * @returns true if data was verified, false otherwise. + */ +typedef bool (*NfcDeviceVerify)(NfcDeviceData* data, const FuriString* device_type); + +/** + * @brief Load NFC device data from a FlipperFormat file. + * + * The FlipperFormat file structure must be initialised and open by the calling code. + * + * @param[in,out] data pointer to the instance to be loaded into. + * @param[in] ff pointer to the FlipperFormat file instance. + * @param[in] version file format version to use when loading. + * @returns true if loaded successfully, false otherwise. + */ +typedef bool (*NfcDeviceLoad)(NfcDeviceData* data, FlipperFormat* ff, uint32_t version); + +/** + * @brief Save NFC device data to a FlipperFormat file. + * + * The FlipperFormat file structure must be initialised and open by the calling code. + * + * @param[in] data pointer to the instance to be saved. + * @param[in] ff pointer to the FlipperFormat file instance. + * @returns true if saved successfully, false otherwise. + */ +typedef bool (*NfcDeviceSave)(const NfcDeviceData* data, FlipperFormat* ff); + +/** + * @brief Compare two NFC device data instances. + * + * @param[in] data pointer to the first instance to be compared. + * @param[in] other pointer to the second instance to be compared. + * @returns true if instances are equal, false otherwise. + */ +typedef bool (*NfcDeviceEqual)(const NfcDeviceData* data, const NfcDeviceData* other); + +/** + * @brief Get a protocol-specific stateful NFC device name. + * + * The return value may change depending on the instance's internal state and the name_type parameter. + * + * @param[in] data pointer to the instance to be queried. + * @param[in] name_type type of the name to be displayed. + * @returns pointer to a statically allocated character string containing the appropriate name. + */ +typedef const char* (*NfcDeviceGetName)(const NfcDeviceData* data, NfcDeviceNameType name_type); + +/** + * @brief Get the NFC device's unique identifier (UID). + * + * The UID length is protocol-dependent. Additionally, a particular protocol might support + * several UID lengths. + * + * @param[in] data pointer to the instance to be queried. + * @param[out] uid_len pointer to the variable to contain the UID length. + * @returns pointer to the byte array containing the device's UID. + */ +typedef const uint8_t* (*NfcDeviceGetUid)(const NfcDeviceData* data, size_t* uid_len); + +/** + * @brief Set the NFC device's unique identifier (UID). + * + * The UID length must be supported by the protocol in question. + * + * @param[in,out] data pointer to the instance to be modified. + * @param[in] uid pointer to the byte array containing the new UID. + * @param[in] uid_len length of the UID. + * @return true if the UID was valid and set, false otherwise. + */ +typedef bool (*NfcDeviceSetUid)(NfcDeviceData* data, const uint8_t* uid, size_t uid_len); + +/** + * @brief Get the NFC device data associated with the parent protocol. + * + * The protocol the instance's data is associated with must have a parent. + * + * @param[in] data pointer to the instance to be queried. + * @returns pointer to the data instance associated with the parent protocol. + */ +typedef NfcDeviceData* (*NfcDeviceGetBaseData)(const NfcDeviceData* data); + +/** + * @brief Generic NFC device interface. + * + * Each protocol must fill this structure with its own function implementations. + */ +typedef struct { + const char* + protocol_name; /**< Pointer to a statically-allocated string with the protocol name. */ + NfcDeviceAlloc alloc; /**< Pointer to the alloc() function. */ + NfcDeviceFree free; /**< Pointer to the free() function. */ + NfcDeviceReset reset; /**< Pointer to the reset() function. */ + NfcDeviceCopy copy; /**< Pointer to the copy() function. */ + NfcDeviceVerify verify; /**< Deprecated. Set to NULL in new protocols. */ + NfcDeviceLoad load; /**< Pointer to the load() function. */ + NfcDeviceSave save; /**< Pointer to the save() function. */ + NfcDeviceEqual is_equal; /**< Pointer to the is_equal() function. */ + NfcDeviceGetName get_name; /**< Pointer to the get_name() function. */ + NfcDeviceGetUid get_uid; /**< Pointer to the get_uid() function. */ + NfcDeviceSetUid set_uid; /**< Pointer to the set_uid() function. */ + NfcDeviceGetBaseData get_base_data; /**< Pointer to the get_base_data() function. */ +} NfcDeviceBase; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c new file mode 100644 index 0000000000..870bcafd9e --- /dev/null +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -0,0 +1,46 @@ +/** + * @file nfc_device_defs.c + * @brief Main NFC device implementation definitions. + * + * All NFC device implementations must be registered here in order to be used + * by the NfcDevice library. + * + * @see nfc_device.h + * + * This file is to be modified upon adding a new protocol (see below). + */ +#include "nfc_device_base_i.h" +#include "nfc_protocol.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief List of registered NFC device implementations. + * + * When implementing a new protocol, add its implementation + * here under its own index defined in nfc_protocol.h. + */ +const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_device_iso14443_3a, + [NfcProtocolIso14443_3b] = &nfc_device_iso14443_3b, + [NfcProtocolIso14443_4a] = &nfc_device_iso14443_4a, + [NfcProtocolIso14443_4b] = &nfc_device_iso14443_4b, + [NfcProtocolIso15693_3] = &nfc_device_iso15693_3, + [NfcProtocolFelica] = &nfc_device_felica, + [NfcProtocolMfUltralight] = &nfc_device_mf_ultralight, + [NfcProtocolMfClassic] = &nfc_device_mf_classic, + [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, + [NfcProtocolSlix] = &nfc_device_slix, + [NfcProtocolSt25tb] = &nfc_device_st25tb, + /* Add new protocols here */ +}; diff --git a/lib/nfc/protocols/nfc_device_defs.h b/lib/nfc/protocols/nfc_device_defs.h new file mode 100644 index 0000000000..f5ba2563f4 --- /dev/null +++ b/lib/nfc/protocols/nfc_device_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include "nfc_device_base_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcDeviceBase* nfc_devices[]; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_generic_event.h b/lib/nfc/protocols/nfc_generic_event.h new file mode 100644 index 0000000000..ec3bd68dc5 --- /dev/null +++ b/lib/nfc/protocols/nfc_generic_event.h @@ -0,0 +1,79 @@ +/** + * @file nfc_generic_event.h + * @brief Generic Nfc stack event definitions. + * + * Events are the main way of passing information about, well, various events + * that occur across the Nfc protocol stack. + * + * In order to subscribe to events from a certain instance, the user code must call + * its corresponding start() function while providing the appropriate callback. + * During this call, an additional context pointer can be provided, which will be passed + * to the context parameter at the time of the callback execution. + * + * For additional information on how events are passed around and processed, see protocol-specific + * poller and listener implementations found in the respectively named subfolders. + * + */ +#pragma once + +#include "nfc_protocol.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Generic Nfc instance type. + * + * Must be cast to a concrete type before use. + * Depending on the context, a pointer of this type + * may point to an object of the following types: + * - Nfc type, + * - Concrete poller type, + * - Concrete listener type. + */ +typedef void NfcGenericInstance; + +/** + * @brief Generic Nfc event data type. + * + * Must be cast to a concrete type before use. + * Usually, it will be the protocol-specific event type. + */ +typedef void NfcGenericEventData; + +/** + * @brief Generic Nfc event type. + * + * A generic Nfc event contains a protocol identifier, can be used to determine + * the remaing fields' type. + * + * If the value of the protocol field is NfcProtocolInvalid, then it means that + * the event was emitted from an Nfc instance, otherwise it originated from + * a concrete poller or listener instance. + * + * The event_data field is protocol-specific and should be cast to the appropriate type before use. + */ +typedef struct { + NfcProtocol protocol; /**< Protocol identifier of the instance that produced the event. */ + NfcGenericInstance* + instance; /**< Pointer to the protocol-specific instance that produced the event. */ + NfcGenericEventData* event_data; /**< Pointer to the protocol-specific event. */ +} NfcGenericEvent; + +/** + * @brief Generic Nfc event callback type. + * + * A function of this type must be passed as the callback parameter upon start + * of a poller, listener or Nfc instance. + * + * @param [in] event Nfc generic event, passed by value, complete with protocol type and data. + * @param [in,out] context pointer to the user-specific context (set when starting a poller/listener instance). + * @returns the command which the event producer must execute. + */ +typedef NfcCommand (*NfcGenericCallback)(NfcGenericEvent event, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_listener_base.h b/lib/nfc/protocols/nfc_listener_base.h new file mode 100644 index 0000000000..6d0cdcd094 --- /dev/null +++ b/lib/nfc/protocols/nfc_listener_base.h @@ -0,0 +1,98 @@ +/** + * @file nfc_listener_base.h + * @brief Abstract interface definitions for the NFC listener system. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + * + * @see nfc_listener.h + * + */ +#pragma once + +#include "nfc_generic_event.h" +#include "nfc_device_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Allocate a protocol-specific listener instance. + * + * For base listeners pass a pointer to an instance of type Nfc + * as the base_listener parameter, otherwise it must be a pointer to another listener instance + * (compare iso14443_3a/iso14443_3a_listener.c and iso14443_4a/iso14443_4a_listener.c). + * + * @see nfc_protocol.c + * + * The NFC device data passed as the data parameter is copied to the instance and may + * change during the emulation in response to reader commands. + * + * To retrieve the modified data, NfcListenerGetData gets called by the NfcListener + * implementation when the user code calls nfc_listener_get_data(). + * + * @param[in] base_listener pointer to the parent listener instance. + * @param[in] data pointer to the protocol-specific data to use during emulation. + * @returns pointer to the allocated listener instance. + */ +typedef NfcGenericInstance* ( + *NfcListenerAlloc)(NfcGenericInstance* base_listener, NfcDeviceData* data); + +/** + * @brief Delete a protocol-specific listener instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +typedef void (*NfcListenerFree)(NfcGenericInstance* instance); + +/** + * @brief Set the callback function to handle events emitted by the listener instance. + * + * @see nfc_generic_event.h + * + * @param[in,out] listener + * @param[in] callback pointer to the user-defined callback function which will receive events. + * @param[in] context pointer to the user-specific context (will be passed to the callback). + */ +typedef void (*NfcListenerSetCallback)( + NfcGenericInstance* listener, + NfcGenericCallback callback, + void* context); + +/** + * @brief Emulate a supported NFC card with given device data. + * + * @param[in] event protocol-specific event passed by the parent listener instance. + * @param[in,out] context pointer to the protocol-specific listener instance. + * @returns command to be executed by the parent listener instance. + */ +typedef NfcCommand (*NfcListenerRun)(NfcGenericEvent event, void* context); + +/** + * @brief Get the protocol-specific data that was that was provided for emulation. + * + * @param[in] instance pointer to the protocol-specific listener instance. + * @returns pointer to the NFC device data. + */ +typedef const NfcDeviceData* (*NfcListenerGetData)(const NfcGenericInstance* instance); + +/** + * @brief Generic NFC listener interface. + * + * Each protocol must fill this structure with its own function implementations. + * See above for the function signatures and descriptions. + * + * Additionally, see ${PROTOCOL_NAME}/${PROTOCOL_NAME}_listener.c for usage examples. + */ +typedef struct { + NfcListenerAlloc alloc; /**< Pointer to the alloc() function. */ + NfcListenerFree free; /**< Pointer to the free() function. */ + NfcListenerSetCallback set_callback; /**< Pointer to the set_callback() function. */ + NfcListenerRun run; /**< Pointer to the run() function. */ + NfcListenerGetData get_data; /**< Pointer to the get_data() function. */ +} NfcListenerBase; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c new file mode 100644 index 0000000000..31f9bc16c6 --- /dev/null +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -0,0 +1,21 @@ +#include "nfc_listener_defs.h" + +#include +#include +#include +#include +#include +#include + +const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, + [NfcProtocolIso14443_3b] = NULL, + [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, + [NfcProtocolIso14443_4b] = NULL, + [NfcProtocolIso15693_3] = &nfc_listener_iso15693_3, + [NfcProtocolMfUltralight] = &mf_ultralight_listener, + [NfcProtocolMfClassic] = &mf_classic_listener, + [NfcProtocolMfDesfire] = NULL, + [NfcProtocolSlix] = &nfc_listener_slix, + [NfcProtocolSt25tb] = NULL, +}; diff --git a/lib/nfc/protocols/nfc_listener_defs.h b/lib/nfc/protocols/nfc_listener_defs.h new file mode 100644 index 0000000000..4d88cc0983 --- /dev/null +++ b/lib/nfc/protocols/nfc_listener_defs.h @@ -0,0 +1,14 @@ +#pragma once + +#include "nfc_listener_base.h" +#include "nfc_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase* nfc_listeners_api[NfcProtocolNum]; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_poller_base.h b/lib/nfc/protocols/nfc_poller_base.h new file mode 100644 index 0000000000..9c4a2b6520 --- /dev/null +++ b/lib/nfc/protocols/nfc_poller_base.h @@ -0,0 +1,132 @@ +/** + * @file nfc_poller_base.h + * @brief Abstract interface definitions for the NFC poller system. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + * + * @see nfc_poller.h + * + */ +#pragma once + +#include "nfc_generic_event.h" +#include "nfc_device_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Allocate a protocol-specific poller instance. + * + * For base pollers pass a pointer to an instance of type Nfc + * as the base_poller parameter, otherwise it must be a pointer to another poller instance + * (compare iso14443_3a/iso14443_3a_poller.c and iso14443_4a/iso14443_4a_poller.c). + * + * @see nfc_protocol.c + * + * @param[in] base_poller pointer to the parent poller instance. + * @returns pointer to the allocated poller instance. + */ +typedef NfcGenericInstance* (*NfcPollerAlloc)(NfcGenericInstance* base_poller); + +/** + * @brief Delete a protocol-specific poller instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +typedef void (*NfcPollerFree)(NfcGenericInstance* instance); + +/** + * @brief Set the callback function to handle events emitted by the poller instance. + * + * @see nfc_generic_event.h + * + * @param[in,out] poller pointer to the protocol-specific poller instance. + * @param[in] callback pointer to the user-defined callback function which will receive events. + * @param[in] context pointer to the user-specific context (will be passed to the callback). + */ +typedef void ( + *NfcPollerSetCallback)(NfcGenericInstance* poller, NfcGenericCallback callback, void* context); + +/** + * @brief Activate and read a supported NFC card. + * + * Ths function is passed to the parent poller's ${POLLER_NAME}_set_callback function as + * the callback parameter. This is done automatically by the NfcPoller implementation based + * on the protocol hierarchy defined in nfc_protocol.c, so there is no need to call it explicitly. + * + * Thus, it will be called each time the parent poller emits an event. Usually it happens + * only after the parent poller has successfully completed its job. + * + * Example for an application reading a card with a compound (non-base) protocol (simplified): + * + * ``` + * start() <-- set_callback() <-- set_callback() <-- nfc_poller_start() + * | | | + * Nfc | Base Poller | Child Poller | Application + * | | | + * worker() --> run() --> run() ---> handle_event() + * ``` + * + * The base poller receives events directly from an Nfc instance, from which they are + * propagated as needed to however many other pollers there are in the current hierarchy. + * + * This function can be thought of as the poller's "main loop" function. Depending + * on the particular poller implementation, it may perform actions such as reading + * and writing to an NFC card, state changes and control of the parent poller. + * + * @see nfc_generic_event.h + * + * @param[in] event protocol-specific event passed by the parent poller instance. + * @param[in,out] context pointer to the protocol-specific poller instance. + * @returns command to be executed by the parent poller instance. + */ +typedef NfcCommand (*NfcPollerRun)(NfcGenericEvent event, void* context); + +/** + * @brief Determine whether there is a supported card in the vicinity. + * + * The behaviour is mostly the same as of NfcPollerRun, with the difference in the + * procedure and return value. + * The procedure implemented in this function must do whatever it needs to unambigiously + * determine whether a supported and valid NFC card is in the vicinity. + * + * Like the previously described NfcPollerRun, it is called automatically by the NfcPoller + * implementation, so there is no need to call it explicitly. + * + * @param[in] event protocol-specific event passed by the parent poller instance. + * @param[in,out] context pointer to the protocol-specific poller instance. + * @returns true if a supported card was detected, false otherwise. + */ +typedef bool (*NfcPollerDetect)(NfcGenericEvent event, void* context); + +/** + * @brief Get the data that was that was gathered during the reading process. + * + * @param[in] instance pointer to the protocol-specific poller instance. + * @returns pointer to the NFC device data. + */ +typedef const NfcDeviceData* (*NfcPollerGetData)(const NfcGenericInstance* instance); + +/** + * @brief Generic NFC poller interface. + * + * Each protocol must fill this structure with its own function implementations. + * See above for the function signatures and descriptions. + * + * Additionally, see ${PROTOCOL_NAME}/${PROTOCOL_NAME}_poller.c for usage examples. + */ +typedef struct { + NfcPollerAlloc alloc; /**< Pointer to the alloc() function. */ + NfcPollerFree free; /**< Pointer to the free() function. */ + NfcPollerSetCallback set_callback; /**< Pointer to the set_callback() function. */ + NfcPollerRun run; /**< Pointer to the run() function. */ + NfcPollerDetect detect; /**< Pointer to the detect() function. */ + NfcPollerGetData get_data; /**< Pointer to the get_data() function. */ +} NfcPollerBase; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c new file mode 100644 index 0000000000..7553c74de1 --- /dev/null +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -0,0 +1,28 @@ +#include "nfc_poller_defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, + [NfcProtocolIso14443_3b] = &nfc_poller_iso14443_3b, + [NfcProtocolIso14443_4a] = &nfc_poller_iso14443_4a, + [NfcProtocolIso14443_4b] = &nfc_poller_iso14443_4b, + [NfcProtocolIso15693_3] = &nfc_poller_iso15693_3, + [NfcProtocolFelica] = &nfc_poller_felica, + [NfcProtocolMfUltralight] = &mf_ultralight_poller, + [NfcProtocolMfClassic] = &mf_classic_poller, + [NfcProtocolMfDesfire] = &mf_desfire_poller, + [NfcProtocolSlix] = &nfc_poller_slix, + /* Add new pollers here */ + [NfcProtocolSt25tb] = &nfc_poller_st25tb, +}; diff --git a/lib/nfc/protocols/nfc_poller_defs.h b/lib/nfc/protocols/nfc_poller_defs.h new file mode 100644 index 0000000000..a406a5f08b --- /dev/null +++ b/lib/nfc/protocols/nfc_poller_defs.h @@ -0,0 +1,14 @@ +#pragma once + +#include "nfc_poller_base.h" +#include "nfc_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase* nfc_pollers_api[NfcProtocolNum]; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c new file mode 100644 index 0000000000..2ea9b39820 --- /dev/null +++ b/lib/nfc/protocols/nfc_protocol.c @@ -0,0 +1,174 @@ +/** + * @file nfc_protocol.c + * @brief Main protocol hierarchy definitions. + * + * To reduce code duplication, all NFC protocols are described as a tree, whose + * structure is shown in the diagram below. The (Start) node is actually + * nonexistent and is there only for clarity. + * + * All its child protocols are considered base protocols, which in turn serve + * as parents to other, usually vendor-specific ones. + * + * ``` + * **************************** Protocol tree structure *************************** + * + * (Start) + * | + * +------------------------+-----------+---------+------------+ + * | | | | | + * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB + * | | | + * +---------------+-------------+ ISO14443-4B SLIX + * | | | + * ISO14443-4A Mf Ultralight Mf Classic + * | + * Mf Desfire + * ``` + * + * When implementing a new protocol, its place in the tree must be determined first. + * If no appropriate base protocols exists, then it must be a base protocol itself. + * + * This file is to be modified upon adding a new protocol (see below). + * + */ +#include "nfc_protocol.h" + +#include + +/** + * @brief Tree node describing a protocol. + * + * All base protocols (see above) have NfcProtocolInvalid + * in the parent_protocol field. + */ +typedef struct { + NfcProtocol parent_protocol; /**< Parent protocol identifier. */ + size_t children_num; /** < Number of the child protocols. */ + const NfcProtocol* children_protocol; /**< Pointer to an array of child protocol identifiers. */ +} NfcProtocolTreeNode; + +/** List of ISO14443-3A child protocols. */ +static const NfcProtocol nfc_protocol_iso14443_3a_children_protocol[] = { + NfcProtocolIso14443_4a, + NfcProtocolMfUltralight, +}; + +/** List of ISO14443-3B child protocols. */ +static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { + NfcProtocolIso14443_4b, +}; + +/** List of ISO14443-4A child protocols. */ +static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { + NfcProtocolMfDesfire, +}; + +/** List of ISO115693-3 child protocols. */ +static const NfcProtocol nfc_protocol_iso15693_3_children_protocol[] = { + NfcProtocolSlix, +}; + +/* Add new child protocol lists here (if necessary) */ + +/** + * @brief Flattened description of the NFC protocol tree. + * + * When implementing a new protocol, add the node here under its + * own index defined in nfc_protocol.h. + * + * Additionally, if it has an already implemented protocol as a parent, + * add its identifier to its respective list of child protocols (see above). + */ +static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = COUNT_OF(nfc_protocol_iso14443_3a_children_protocol), + .children_protocol = nfc_protocol_iso14443_3a_children_protocol, + }, + [NfcProtocolIso14443_3b] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = COUNT_OF(nfc_protocol_iso14443_3b_children_protocol), + .children_protocol = nfc_protocol_iso14443_3b_children_protocol, + }, + [NfcProtocolIso14443_4a] = + { + .parent_protocol = NfcProtocolIso14443_3a, + .children_num = COUNT_OF(nfc_protocol_iso14443_4a_children_protocol), + .children_protocol = nfc_protocol_iso14443_4a_children_protocol, + }, + [NfcProtocolIso14443_4b] = + { + .parent_protocol = NfcProtocolIso14443_3b, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolIso15693_3] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = COUNT_OF(nfc_protocol_iso15693_3_children_protocol), + .children_protocol = nfc_protocol_iso15693_3_children_protocol, + }, + [NfcProtocolFelica] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolMfUltralight] = + { + .parent_protocol = NfcProtocolIso14443_3a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolMfClassic] = + { + .parent_protocol = NfcProtocolIso14443_3a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolMfDesfire] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolSlix] = + { + .parent_protocol = NfcProtocolIso15693_3, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolSt25tb] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = 0, + .children_protocol = NULL, + }, + /* Add new protocols here */ +}; + +NfcProtocol nfc_protocol_get_parent(NfcProtocol protocol) { + furi_assert(protocol < NfcProtocolNum); + + return nfc_protocol_nodes[protocol].parent_protocol; +} + +bool nfc_protocol_has_parent(NfcProtocol protocol, NfcProtocol parent_protocol) { + furi_assert(protocol < NfcProtocolNum); + furi_assert(parent_protocol < NfcProtocolNum); + + bool parent_found = false; + const NfcProtocolTreeNode* iter = &nfc_protocol_nodes[protocol]; + + while(iter->parent_protocol != NfcProtocolInvalid) { + if(iter->parent_protocol == parent_protocol) { + parent_found = true; + break; + } + iter = &nfc_protocol_nodes[iter->parent_protocol]; + } + + return parent_found; +} diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h new file mode 100644 index 0000000000..55aa8a5895 --- /dev/null +++ b/lib/nfc/protocols/nfc_protocol.h @@ -0,0 +1,219 @@ +/** + * @file nfc_protocol.h + * @brief Top-level NFC protocol definitions. + * + * This file is to be modified upon adding a new protocol (see below). + * + * # How to add a new NFC protocol + * + * ## 1. Gather information + * + * Having proper protocol documentation would be ideal, although they are often available only for a fee, or given under an NDA. + * As an alternative, reading code from relevant open-source projects or notes gathered by means of reverse engineering will do. + * + * ### 1.1 Technology + * + * Check whether the NFC technology required for the protocol is supported. If no support exists, adding the protocol may + * be problematic, since it is highly hardware-dependent. + * + * @see NfcTech for the enumeration of supported NFC technologies. + * + * ### 1.2 Base protocols + * + * Check whether the protocol to be implemented is built on top of some already supported protocol. + * + * @see NfcProtocol for the enumeration of supported NFC protocols. + * + * If the answer is "yes", then the protocol to be implemented is a child protocol. If no, then it will become a base protocol. + * Sometimes it will be necessary to implement both, e.g. when the target protocol is built on top of a base one, + * but the latter is currently not supported. + * + * ## 2. Create the files + * + * ### 2.1 Recommended file structure + * + * The recommended file structure for a protocol is as follows: + * + * ```text + * protocols + * | + * +- protocol_name + * | + * +- protocol_name.h + * | + * +- protocol_name.c + * | + * +- protocol_name_device_defs.h + * | + * +- protocol_name_poller.h + * | + * +- protocol_name_poller.c + * | + * +- protocol_name_poller_defs.h + * | + * . + * . (files below are optional) + * . + * | + * +- protocol_name_listener.h | + * | | + * +- protocol_name_listener.c |- add for emulation support + * | | + * +- protocol_name_listener_defs.h | + * | + * +- protocol_name_sync_api.h | + * | |- add for synchronous API support + * +- protocol_name_sync_api.c | + * | + * ``` + * + * Additionally, an arbitrary amount of private `protocol_name_*_i.h` header files may be created. Do not put implementation + * details in the regular header files, as they will be exposed in the public firmware API later on. + * + * ### 2.2 File structure explanation + * + * | Filename | Explanation | + * |:------------------------------|:------------| + * | protocol_name.h | Main protocol data structure and associated functions declarations. It is recommended to keep the former as opaque pointer. | + * | protocol_name.c | Implementations of functions declared in `protocol_name.h`. | + * | protocol_name_device_defs.h | Declarations for use by the NfcDevice library. See nfc_device_base_i.h for more info. | + * | protocol_name_poller.h | Protocol-specific poller and associated functions declarations. | + * | protocol_name_poller.c | Implementation of functions declared in `protocol_name_poller.h`. | + * | protocol_name_poller_defs.h | Declarations for use by the NfcPoller library. See nfc_poller_base.h for more info. | + * | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. | + * | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. | + * | protocol_name_listener_defs.h | Declarations for use by the NfcListener library. See nfc_listener_base.h for more info. Optional, needed for emulation support. | + * | protocol_name_sync_api.h | Synchronous API declarations. (See below for sync API explanation). Optional.| + * | protocol_name_sync_api.c | Synchronous API implementation. Optional. | + * + * ## 3 Implement the code + * + * ### 3.1 Protocol data structure + * + * A protocol data structure is what holds all data that can be possibly read from a card of a certain type. It may include a unique identifier (UID), + * configuration bytes and flags, built-in memory, and so on. + * Additionally, it must implement the NfcDevice interface so that it could be used by the firmware. + * + * @see nfc_device_base_i.h for the device interface description. + * + * @note It is strongly recommended to implement such a structure as an opaque type and access it via specialised methods only. + * + * If the protocol being implemented is a child protocol, then its data must include a pointer to the parent protocol data structure. + * It is the protocol's responsibility to correctly create and delete the instance the pointer is pointing to. + * + * ### 3.2 Poller (reading functionality) + * + * A poller contains the functions necessary to successfully read a card of supported type. It must also implement a specific interface, + * namely described by the NfcPollerBase type. + * + * Upon creation, a poller instance will receive either a pointer to the Nfc instance (if it's a base protocol), or a pointer to another poller + * instance (if it is a child protocol) as the `alloc()` parameter. + * + * @see nfc_poller_base.h for the poller interface description. + * + * ### 3.3 Listener (emulation functionality) + * + * A listener implementation is optional, needed only when emulation is required/possible. + * + * Same considerations apply to the listener as for the poller. Additionally, upon creation it will receive an additional parameter + * in the form of a pointer to the matching protocol data structure, which will be used during emulation. The listener instance + * does not own this data and does not need to worry about its deletion. + * + * @see nfc_listener_base.h for the listener interface description. + * + * ### 3.4 Synchronous API + * + * Synchronous API does exaclty what it says -- it provides a set of blocking functions for easier use of pollers. + * Instead of creating all necessary instances and setting up callbacks manually, it does it automatically, at the + * expense of some flexibility. + * + * The most notable use of sync API is in the supported card plugins, so it's a good idea to implement it if such a plugin + * is to be implemented afterwards. + * + * ### 3.5 Registering the protocol + * + * After completing the protocol, it must be registered within the NfcProtocol system in order for it to be usable. + * + * 1. In nfc_protocol.h, add a new entry in the NfcProtocol enum in the form of NfcProtocolProtocolName. + * 2. In nfc_protocol.c, add a new entry in the `nfc_protocol_nodes[]` array under the appropriate index. + * 1. If it is a child protocol, register it as a child in the respective `nfc_base_protocol_name_children_protocol[]` array. + * 2. If the protocol has children on its own, create a `nfc_protocol_name_children_protocol[]` array + * with respective identifiers and register it in the protocol entry added in step 2. + * 3. In nfc_device_defs.c, include `protocol_name_device_defs.h` and add a pointer to the + * `protocol_name_device_defs` structure under the appropriate index. + * 4. In nfc_poller_defs.c, include `protocol_name_poller_defs.h` and add a pointer to the + * `protocol_name_poller_defs` structure under the appropriate index. + * 5. (Optional) If emulation support was implemented, do the step 4, but for the listener. + * 6. Add `protocol_name.h`, `protocol_name_poller.h`, and optionally, `protocol_name_listener.h` + * and `protocol_name_sync_api.h` into the `SDK_HEADERS` list in the SConscript file. + * This will export the protocol's types and functions for use by the applications. + * 7. Done! + * + * ## What's next? + * + * It's about time to integrate the newly implemented protocol into the main NFC application. Without that, reading a card + * of this type would crash it. + * + * @see nfc_protocol_support.h for more information on protocol integration. + * + * After having done that, a supported card plugin may be implemented to take further advantage of the new protocol. + * + * @see nfc_supported_card_plugin.h for more information on supported card plugins. + * + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumeration of all available NFC protocols. + * + * When implementing a new protocol, add its identifier before the + * NfcProtocolNum entry. + */ +typedef enum { + NfcProtocolIso14443_3a, + NfcProtocolIso14443_3b, + NfcProtocolIso14443_4a, + NfcProtocolIso14443_4b, + NfcProtocolIso15693_3, + NfcProtocolFelica, + NfcProtocolMfUltralight, + NfcProtocolMfClassic, + NfcProtocolMfDesfire, + NfcProtocolSlix, + NfcProtocolSt25tb, + /* Add new protocols here */ + + NfcProtocolNum, /**< Special value representing the number of available protocols. */ + + NfcProtocolInvalid, /**< Special value representing an invalid state. */ +} NfcProtocol; + +/** + * @brief Get the immediate parent of a specific protocol. + * + * @param[in] protocol identifier of the protocol in question. + * @returns parent protocol identifier if it has one, or NfcProtocolInvalid otherwise. + */ +NfcProtocol nfc_protocol_get_parent(NfcProtocol protocol); + +/** + * @brief Determine if a specific protocol has a parent on an arbitrary level. + * + * Unlike nfc_protocol_get_parent(), this function will traverse the full protocol hierarchy + * and check each parent node for the matching protocol type. + * + * @param[in] protocol identifier of the protocol in question. + * @param[in] parent_protocol identifier of the parent protocol in question. + * @returns true if the parent of given type exists, false otherwise. + */ +bool nfc_protocol_has_parent(NfcProtocol protocol, NfcProtocol parent_protocol); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c deleted file mode 100644 index ab4f3f23c6..0000000000 --- a/lib/nfc/protocols/nfca.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "nfca.h" -#include -#include -#include - -#define NFCA_CRC_INIT (0x6363) - -#define NFCA_F_SIG (13560000.0) -#define T_SIG 7374 //73.746ns*100 -#define T_SIG_x8 58992 //T_SIG*8 -#define T_SIG_x8_x8 471936 //T_SIG*8*8 -#define T_SIG_x8_x9 530928 //T_SIG*8*9 - -#define NFCA_SIGNAL_MAX_EDGES (1350) - -typedef struct { - uint8_t cmd; - uint8_t param; -} nfca_cmd_rats; - -static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; - -static uint8_t nfca_halt_req[] = {NFCA_CMD_HALT, 0x00}; - -uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { - uint16_t crc = NFCA_CRC_INIT; - uint8_t byte = 0; - - for(uint8_t i = 0; i < len; i++) { - byte = buff[i]; - byte ^= (uint8_t)(crc & 0xff); - byte ^= byte << 4; - crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^ - (((uint16_t)byte) >> 4); - } - - return crc; -} - -void nfca_append_crc16(uint8_t* buff, uint16_t len) { - uint16_t crc = nfca_get_crc16(buff, len); - buff[len] = (uint8_t)crc; - buff[len + 1] = (uint8_t)(crc >> 8); -} - -bool nfca_emulation_handler( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len) { - bool halt = false; - uint8_t rx_bytes = buff_rx_len / 8; - - if(rx_bytes == sizeof(nfca_halt_req) && !memcmp(buff_rx, nfca_halt_req, rx_bytes)) { - halt = true; - } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { - memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); - *buff_tx_len = sizeof(nfca_default_ats) * 8; - } - - return halt; -} - -static void nfca_add_bit(DigitalSignal* signal, bool bit) { - if(bit) { - signal->start_level = true; - for(size_t i = 0; i < 7; i++) { - signal->edge_timings[i] = T_SIG_x8; - } - signal->edge_timings[7] = T_SIG_x8_x9; - signal->edge_cnt = 8; - } else { - signal->start_level = false; - signal->edge_timings[0] = T_SIG_x8_x8; - for(size_t i = 1; i < 9; i++) { - signal->edge_timings[i] = T_SIG_x8; - } - signal->edge_cnt = 9; - } -} - -static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) { - for(uint8_t i = 0; i < 8; i++) { - if(byte & (1 << i)) { - digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); - } else { - digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); - } - } - if(parity) { - digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); - } else { - digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); - } -} - -NfcaSignal* nfca_signal_alloc() { - NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal)); - nfca_signal->one = digital_signal_alloc(10); - nfca_signal->zero = digital_signal_alloc(10); - nfca_add_bit(nfca_signal->one, true); - nfca_add_bit(nfca_signal->zero, false); - nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES); - - return nfca_signal; -} - -void nfca_signal_free(NfcaSignal* nfca_signal) { - furi_assert(nfca_signal); - - digital_signal_free(nfca_signal->one); - digital_signal_free(nfca_signal->zero); - digital_signal_free(nfca_signal->tx_signal); - free(nfca_signal); -} - -void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) { - furi_assert(nfca_signal); - furi_assert(data); - furi_assert(parity); - - nfca_signal->tx_signal->edge_cnt = 0; - nfca_signal->tx_signal->start_level = true; - // Start of frame - digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); - - if(bits < 8) { - for(size_t i = 0; i < bits; i++) { - if(FURI_BIT(data[0], i)) { - digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); - } else { - digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); - } - } - } else { - for(size_t i = 0; i < bits / 8; i++) { - nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07)))); - } - } -} diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h deleted file mode 100644 index e4978a3e0b..0000000000 --- a/lib/nfc/protocols/nfca.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include - -#include - -#define NFCA_CMD_RATS (0xE0U) -#define NFCA_CMD_HALT (0x50U) - -typedef struct { - DigitalSignal* one; - DigitalSignal* zero; - DigitalSignal* tx_signal; -} NfcaSignal; - -uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len); - -void nfca_append_crc16(uint8_t* buff, uint16_t len); - -bool nfca_emulation_handler( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len); - -NfcaSignal* nfca_signal_alloc(); - -void nfca_signal_free(NfcaSignal* nfca_signal); - -void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity); diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c deleted file mode 100644 index 2814632811..0000000000 --- a/lib/nfc/protocols/nfcv.c +++ /dev/null @@ -1,1438 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "nfcv.h" -#include "nfc_util.h" -#include "slix.h" - -#define TAG "NfcV" - -/* macros to map "modulate field" flag to GPIO level */ -#define GPIO_LEVEL_MODULATED NFCV_LOAD_MODULATION_POLARITY -#define GPIO_LEVEL_UNMODULATED (!GPIO_LEVEL_MODULATED) - -/* timing macros */ -#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f) -#define DIGITAL_SIGNAL_UNIT_US (100000.0f) - -ReturnCode nfcv_inventory(uint8_t* uid) { - uint16_t received = 0; - rfalNfcvInventoryRes res; - ReturnCode ret = ERR_NONE; - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - /* TODO: needs proper abstraction via furi_hal(_ll)_* */ - ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret == ERR_NONE) { - if(uid != NULL) { - memcpy(uid, res.UID, NFCV_UID_LENGTH); - } - } - - return ret; -} - -ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) { - UNUSED(reader); - - uint16_t received = 0; - for(size_t block = 0; block < nfcv_data->block_num; block++) { - uint8_t rxBuf[32]; - FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1)); - - ReturnCode ret = ERR_NONE; - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - ret = rfalNfcvPollerReadSingleBlock( - RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received); - - if(ret == ERR_NONE) { - break; - } - } - if(ret != ERR_NONE) { - FURI_LOG_D(TAG, "failed to read: %d", ret); - return ret; - } - memcpy( - &(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size); - FURI_LOG_D( - TAG, - " %02X %02X %02X %02X", - nfcv_data->data[block * nfcv_data->block_size + 0], - nfcv_data->data[block * nfcv_data->block_size + 1], - nfcv_data->data[block * nfcv_data->block_size + 2], - nfcv_data->data[block * nfcv_data->block_size + 3]); - } - - return ERR_NONE; -} - -ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - uint8_t rxBuf[32]; - uint16_t received = 0; - ReturnCode ret = ERR_NONE; - - FURI_LOG_D(TAG, "Read SYSTEM INFORMATION..."); - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - /* TODO: needs proper abstraction via furi_hal(_ll)_* */ - ret = rfalNfcvPollerGetSystemInformation( - RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret == ERR_NONE) { - nfc_data->type = FuriHalNfcTypeV; - nfc_data->uid_len = NFCV_UID_LENGTH; - /* UID is stored reversed in this response */ - for(int pos = 0; pos < nfc_data->uid_len; pos++) { - nfc_data->uid[pos] = rxBuf[2 + (NFCV_UID_LENGTH - 1 - pos)]; - } - nfcv_data->dsfid = rxBuf[NFCV_UID_LENGTH + 2]; - nfcv_data->afi = rxBuf[NFCV_UID_LENGTH + 3]; - nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1; - nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1; - nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6]; - FURI_LOG_D( - TAG, - " UID: %02X %02X %02X %02X %02X %02X %02X %02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); - FURI_LOG_D( - TAG, - " DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d", - nfcv_data->dsfid, - nfcv_data->afi, - nfcv_data->block_num, - nfcv_data->block_size, - nfcv_data->ic_ref); - return ret; - } - FURI_LOG_D(TAG, "Failed: %d", ret); - - return ret; -} - -bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(reader); - furi_assert(nfc_data); - furi_assert(nfcv_data); - - if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) { - return false; - } - - if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) { - return false; - } - - /* clear all know sub type data before reading them */ - memset(&nfcv_data->sub_data, 0x00, sizeof(nfcv_data->sub_data)); - - if(slix_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX detected"); - nfcv_data->sub_type = NfcVTypeSlix; - } else if(slix2_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX2 detected"); - nfcv_data->sub_type = NfcVTypeSlix2; - if(slix2_read_custom(nfc_data, nfcv_data) != ERR_NONE) { - return false; - } - } else if(slix_s_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX-S detected"); - nfcv_data->sub_type = NfcVTypeSlixS; - } else if(slix_l_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX-L detected"); - nfcv_data->sub_type = NfcVTypeSlixL; - } else { - nfcv_data->sub_type = NfcVTypePlain; - } - - return true; -} - -void nfcv_crc(uint8_t* data, uint32_t length) { - uint32_t reg = 0xFFFF; - - for(size_t i = 0; i < length; i++) { - reg = reg ^ ((uint32_t)data[i]); - for(size_t j = 0; j < 8; j++) { - if(reg & 0x0001) { - reg = (reg >> 1) ^ 0x8408; - } else { - reg = (reg >> 1); - } - } - } - - uint16_t crc = ~(uint16_t)(reg & 0xffff); - - data[length + 0] = crc & 0xFF; - data[length + 1] = crc >> 8; -} - -void nfcv_emu_free_signals(NfcVEmuAirSignals* signals) { - furi_assert(signals); - - if(signals->nfcv_resp_one) { - digital_signal_free(signals->nfcv_resp_one); - } - if(signals->nfcv_resp_zero) { - digital_signal_free(signals->nfcv_resp_zero); - } - if(signals->nfcv_resp_sof) { - digital_signal_free(signals->nfcv_resp_sof); - } - if(signals->nfcv_resp_eof) { - digital_signal_free(signals->nfcv_resp_eof); - } - signals->nfcv_resp_one = NULL; - signals->nfcv_resp_zero = NULL; - signals->nfcv_resp_sof = NULL; - signals->nfcv_resp_eof = NULL; -} - -bool nfcv_emu_alloc_signals(NfcVEmuAir* air, NfcVEmuAirSignals* signals, uint32_t slowdown) { - furi_assert(air); - furi_assert(signals); - - bool success = true; - - if(!signals->nfcv_resp_one) { - /* logical one: unmodulated then 8 pulses */ - signals->nfcv_resp_one = digital_signal_alloc( - slowdown * (air->nfcv_resp_unmod->edge_cnt + 8 * air->nfcv_resp_pulse->edge_cnt)); - if(!signals->nfcv_resp_one) { - return false; - } - for(size_t i = 0; i < slowdown; i++) { - success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_unmod); - } - for(size_t i = 0; i < slowdown * 8; i++) { - success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_pulse); - } - if(!success) { - return false; - } - } - if(!signals->nfcv_resp_zero) { - /* logical zero: 8 pulses then unmodulated */ - signals->nfcv_resp_zero = digital_signal_alloc( - slowdown * (8 * air->nfcv_resp_pulse->edge_cnt + air->nfcv_resp_unmod->edge_cnt)); - if(!signals->nfcv_resp_zero) { - return false; - } - for(size_t i = 0; i < slowdown * 8; i++) { - success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_pulse); - } - for(size_t i = 0; i < slowdown; i++) { - success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_unmod); - } - if(!success) { - return false; - } - } - if(!signals->nfcv_resp_sof) { - /* SOF: unmodulated, 24 pulses, logic 1 */ - signals->nfcv_resp_sof = digital_signal_alloc( - slowdown * (3 * air->nfcv_resp_unmod->edge_cnt + 24 * air->nfcv_resp_pulse->edge_cnt) + - signals->nfcv_resp_one->edge_cnt); - if(!signals->nfcv_resp_sof) { - return false; - } - for(size_t i = 0; i < slowdown * 3; i++) { - success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_unmod); - } - for(size_t i = 0; i < slowdown * 24; i++) { - success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_pulse); - } - success &= digital_signal_append(signals->nfcv_resp_sof, signals->nfcv_resp_one); - if(!success) { - return false; - } - } - if(!signals->nfcv_resp_eof) { - /* EOF: logic 0, 24 pulses, unmodulated */ - signals->nfcv_resp_eof = digital_signal_alloc( - signals->nfcv_resp_zero->edge_cnt + - slowdown * (24 * air->nfcv_resp_pulse->edge_cnt + 3 * air->nfcv_resp_unmod->edge_cnt) + - air->nfcv_resp_unmod->edge_cnt); - if(!signals->nfcv_resp_eof) { - return false; - } - success &= digital_signal_append(signals->nfcv_resp_eof, signals->nfcv_resp_zero); - for(size_t i = 0; i < slowdown * 23; i++) { - success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_pulse); - } - /* we don't want to add the last level as we just want a transition to "unmodulated" again */ - for(size_t i = 0; i < slowdown; i++) { - success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_half_pulse); - } - } - return success; -} - -bool nfcv_emu_alloc(NfcVData* nfcv_data) { - furi_assert(nfcv_data); - - if(!nfcv_data->frame) { - nfcv_data->frame = malloc(NFCV_FRAMESIZE_MAX); - if(!nfcv_data->frame) { - return false; - } - } - - if(!nfcv_data->emu_air.nfcv_signal) { - /* assuming max frame length is 255 bytes */ - nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi); - if(!nfcv_data->emu_air.nfcv_signal) { - return false; - } - } - if(!nfcv_data->emu_air.nfcv_resp_unmod) { - /* unmodulated 256/fc or 1024/fc signal as building block */ - nfcv_data->emu_air.nfcv_resp_unmod = digital_signal_alloc(4); - if(!nfcv_data->emu_air.nfcv_resp_unmod) { - return false; - } - nfcv_data->emu_air.nfcv_resp_unmod->start_level = GPIO_LEVEL_UNMODULATED; - nfcv_data->emu_air.nfcv_resp_unmod->edge_timings[0] = - (uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_unmod->edge_cnt = 1; - } - if(!nfcv_data->emu_air.nfcv_resp_pulse) { - /* modulated fc/32 or fc/8 pulse as building block */ - nfcv_data->emu_air.nfcv_resp_pulse = digital_signal_alloc(4); - if(!nfcv_data->emu_air.nfcv_resp_pulse) { - return false; - } - nfcv_data->emu_air.nfcv_resp_pulse->start_level = GPIO_LEVEL_MODULATED; - nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[0] = - (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[1] = - (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_pulse->edge_cnt = 2; - } - - if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { - /* modulated fc/32 or fc/8 pulse as building block */ - nfcv_data->emu_air.nfcv_resp_half_pulse = digital_signal_alloc(4); - if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { - return false; - } - nfcv_data->emu_air.nfcv_resp_half_pulse->start_level = GPIO_LEVEL_MODULATED; - nfcv_data->emu_air.nfcv_resp_half_pulse->edge_timings[0] = - (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_half_pulse->edge_cnt = 1; - } - - bool success = true; - success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_high, 1); - success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_low, 4); - - if(!success) { - FURI_LOG_E(TAG, "Failed to allocate signals"); - return false; - } - - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_SOF, - nfcv_data->emu_air.signals_high.nfcv_resp_sof); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_BIT0, - nfcv_data->emu_air.signals_high.nfcv_resp_zero); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_BIT1, - nfcv_data->emu_air.signals_high.nfcv_resp_one); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_EOF, - nfcv_data->emu_air.signals_high.nfcv_resp_eof); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_SOF, - nfcv_data->emu_air.signals_low.nfcv_resp_sof); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_BIT0, - nfcv_data->emu_air.signals_low.nfcv_resp_zero); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_BIT1, - nfcv_data->emu_air.signals_low.nfcv_resp_one); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_EOF, - nfcv_data->emu_air.signals_low.nfcv_resp_eof); - - return true; -} - -void nfcv_emu_free(NfcVData* nfcv_data) { - furi_assert(nfcv_data); - - if(nfcv_data->frame) { - free(nfcv_data->frame); - } - if(nfcv_data->emu_protocol_ctx) { - free(nfcv_data->emu_protocol_ctx); - } - if(nfcv_data->emu_air.nfcv_resp_unmod) { - digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod); - } - if(nfcv_data->emu_air.nfcv_resp_pulse) { - digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse); - } - if(nfcv_data->emu_air.nfcv_resp_half_pulse) { - digital_signal_free(nfcv_data->emu_air.nfcv_resp_half_pulse); - } - if(nfcv_data->emu_air.nfcv_signal) { - digital_sequence_free(nfcv_data->emu_air.nfcv_signal); - } - if(nfcv_data->emu_air.reader_signal) { - // Stop pulse reader and disable bus before free - pulse_reader_stop(nfcv_data->emu_air.reader_signal); - // Free pulse reader - pulse_reader_free(nfcv_data->emu_air.reader_signal); - } - - nfcv_data->frame = NULL; - nfcv_data->emu_air.nfcv_resp_unmod = NULL; - nfcv_data->emu_air.nfcv_resp_pulse = NULL; - nfcv_data->emu_air.nfcv_resp_half_pulse = NULL; - nfcv_data->emu_air.nfcv_signal = NULL; - nfcv_data->emu_air.reader_signal = NULL; - - nfcv_emu_free_signals(&nfcv_data->emu_air.signals_high); - nfcv_emu_free_signals(&nfcv_data->emu_air.signals_low); -} - -void nfcv_emu_send( - FuriHalNfcTxRxContext* tx_rx, - NfcVData* nfcv, - uint8_t* data, - uint8_t length, - NfcVSendFlags flags, - uint32_t send_time) { - furi_assert(tx_rx); - furi_assert(nfcv); - - /* picked default value (0) to match the most common format */ - if(flags == NfcVSendFlagsNormal) { - flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof | - NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; - } - - if(flags & NfcVSendFlagsCrc) { - nfcv_crc(data, length); - length += 2; - } - - /* depending on the request flags, send with high or low rate */ - uint32_t bit0 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT0 : NFCV_SIG_LOW_BIT0; - uint32_t bit1 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT1 : NFCV_SIG_LOW_BIT1; - uint32_t sof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_SOF : NFCV_SIG_LOW_SOF; - uint32_t eof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_EOF : NFCV_SIG_LOW_EOF; - - digital_sequence_clear(nfcv->emu_air.nfcv_signal); - - if(flags & NfcVSendFlagsSof) { - digital_sequence_add(nfcv->emu_air.nfcv_signal, sof); - } - - for(int bit_total = 0; bit_total < length * 8; bit_total++) { - uint32_t byte_pos = bit_total / 8; - uint32_t bit_pos = bit_total % 8; - uint8_t bit_val = 0x01 << bit_pos; - - digital_sequence_add(nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? bit1 : bit0); - } - - if(flags & NfcVSendFlagsEof) { - digital_sequence_add(nfcv->emu_air.nfcv_signal, eof); - } - - furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); - digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time); - digital_sequence_send(nfcv->emu_air.nfcv_signal); - furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); - - if(tx_rx->sniff_tx) { - tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context); - } -} - -static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) { - for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { - dst[pos] = src[NFCV_UID_LENGTH - 1 - pos]; - } -} - -static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) { - for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { - if(dst[pos] != src[NFCV_UID_LENGTH - 1 - pos]) { - return 1; - } - } - return 0; -} - -void nfcv_emu_handle_packet( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - - if(nfcv_data->frame_length < 2) { - return; - } - - if(nfcv_data->echo_mode) { - nfcv_emu_send( - tx_rx, - nfcv_data, - nfcv_data->frame, - nfcv_data->frame_length, - NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); - return; - } - - /* parse the frame data for the upcoming part 3 handling */ - ctx->flags = nfcv_data->frame[0]; - ctx->command = nfcv_data->frame[1]; - ctx->selected = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && (ctx->flags & NFCV_REQ_FLAG_SELECT); - ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && - (ctx->flags & NFCV_REQ_FLAG_ADDRESS); - ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); - ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); - ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); - ctx->response_flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof; - ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4380); - - if(ctx->flags & NFCV_REQ_FLAG_DATA_RATE) { - ctx->response_flags |= NfcVSendFlagsHighRate; - } - if(ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) { - ctx->response_flags |= NfcVSendFlagsTwoSubcarrier; - } - - if(ctx->payload_offset + 2 > nfcv_data->frame_length) { -#ifdef NFCV_VERBOSE - FURI_LOG_D(TAG, "command 0x%02X, but packet is too short", ctx->command); -#endif - return; - } - - /* standard behavior is implemented */ - if(ctx->addressed) { - uint8_t* address = &nfcv_data->frame[ctx->address_offset]; - if(nfcv_revuidcmp(address, nfc_data->uid)) { -#ifdef NFCV_VERBOSE - FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command); - FURI_LOG_D( - TAG, - " dest: %02X%02X%02X%02X%02X%02X%02X%02X", - address[7], - address[6], - address[5], - address[4], - address[3], - address[2], - address[1], - address[0]); - FURI_LOG_D( - TAG, - " our UID: %02X%02X%02X%02X%02X%02X%02X%02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); -#endif - return; - } - } - - if(ctx->selected && !nfcv_data->selected) { -#ifdef NFCV_VERBOSE - FURI_LOG_D( - TAG, - "selected card shall execute command 0x%02X, but we were not selected", - ctx->command); -#endif - return; - } - - /* then give control to the card subtype specific protocol filter */ - if(ctx->emu_protocol_filter != NULL) { - if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) { - if(strlen(nfcv_data->last_command) > 0) { -#ifdef NFCV_VERBOSE - FURI_LOG_D( - TAG, "Received command %s (handled by filter)", nfcv_data->last_command); -#endif - } - return; - } - } - - switch(ctx->command) { - case NFCV_CMD_INVENTORY: { - bool respond = false; - - if(ctx->flags & NFCV_REQ_FLAG_AFI) { - uint8_t afi = nfcv_data->frame[ctx->payload_offset]; - - uint8_t family = (afi & 0xF0); - uint8_t subfamily = (afi & 0x0F); - - if(family) { - if(subfamily) { - /* selected family and subfamily only */ - if(afi == nfcv_data->afi) { - respond = true; - } - } else { - /* selected family, any subfamily */ - if(family == (nfcv_data->afi & 0xf0)) { - respond = true; - } - } - } else { - if(subfamily) { - /* proprietary subfamily only */ - if(afi == nfcv_data->afi) { - respond = true; - } - } else { - /* all families and subfamilies */ - respond = true; - } - } - - } else { - respond = true; - } - - if(!nfcv_data->quiet && respond) { - int buffer_pos = 0; - ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; - ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; - nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); - buffer_pos += NFCV_UID_LENGTH; - - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - buffer_pos, - ctx->response_flags, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY"); - } else { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY (quiet)"); - } - break; - } - - case NFCV_CMD_STAY_QUIET: { - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET"); - nfcv_data->quiet = true; - break; - } - - case NFCV_CMD_LOCK_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - nfcv_data->security_status[block] |= 0x01; - nfcv_data->modified = true; - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK BLOCK %d", block); - break; - } - - case NFCV_CMD_WRITE_DSFID: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - - if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { - nfcv_data->dsfid = id; - nfcv_data->modified = true; - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE DSFID %02X", id); - break; - } - - case NFCV_CMD_WRITE_AFI: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - - if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { - nfcv_data->afi = id; - nfcv_data->modified = true; - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE AFI %02X", id); - break; - } - - case NFCV_CMD_LOCK_DSFID: { - if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { - nfcv_data->security_status[0] |= NfcVLockBitDsfid; - nfcv_data->modified = true; - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK DSFID"); - break; - } - - case NFCV_CMD_LOCK_AFI: { - if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { - nfcv_data->security_status[0] |= NfcVLockBitAfi; - nfcv_data->modified = true; - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK AFI"); - break; - } - - case NFCV_CMD_SELECT: { - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_data->selected = true; - nfcv_data->quiet = false; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT"); - break; - } - - case NFCV_CMD_RESET_TO_READY: { - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_data->quiet = false; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "RESET_TO_READY"); - break; - } - - case NFCV_CMD_READ_MULTI_BLOCK: - case NFCV_CMD_READ_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - int blocks = 1; - - if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { - blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; - } - - /* limit the maximum block count, underflow accepted */ - if(block + blocks > nfcv_data->block_num) { - blocks = nfcv_data->block_num - block; - } - - /* only respond with the valid blocks, if there are any */ - if(blocks > 0) { - uint8_t buffer_pos = 0; - - ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; - - for(int block_index = 0; block_index < blocks; block_index++) { - int block_current = block + block_index; - /* prepend security status */ - if(ctx->flags & NFCV_REQ_FLAG_OPTION) { - ctx->response_buffer[buffer_pos++] = - nfcv_data->security_status[1 + block_current]; - } - /* then the data block */ - memcpy( - &ctx->response_buffer[buffer_pos], - &nfcv_data->data[nfcv_data->block_size * block_current], - nfcv_data->block_size); - buffer_pos += nfcv_data->block_size; - } - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - buffer_pos, - ctx->response_flags, - ctx->send_time); - } else { - /* reply with an error only in addressed or selected mode */ - if(ctx->addressed || ctx->selected) { - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); - } - } - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block); - - break; - } - - case NFCV_CMD_WRITE_MULTI_BLOCK: - case NFCV_CMD_WRITE_BLOCK: { - uint8_t blocks = 1; - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t data_pos = ctx->payload_offset + 1; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - blocks = nfcv_data->frame[data_pos] + 1; - data_pos++; - } - - uint8_t* data = &nfcv_data->frame[data_pos]; - uint32_t data_len = nfcv_data->block_size * blocks; - - if((block + blocks) <= nfcv_data->block_num && - (data_pos + data_len + 2) == nfcv_data->frame_length) { - ctx->response_buffer[0] = NFCV_NOERROR; - memcpy( - &nfcv_data->data[nfcv_data->block_size * block], - &nfcv_data->frame[data_pos], - data_len); - nfcv_data->modified = true; - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } else { - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); - } - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE MULTI BLOCK %d, %d blocks", - block, - blocks); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE BLOCK %d <- %02X %02X %02X %02X", - block, - data[0], - data[1], - data[2], - data[3]); - } - break; - } - - case NFCV_CMD_GET_SYSTEM_INFO: { - int buffer_pos = 0; - ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; - ctx->response_buffer[buffer_pos++] = NFCV_SYSINFO_FLAG_DSFID | NFCV_SYSINFO_FLAG_AFI | - NFCV_SYSINFO_FLAG_MEMSIZE | NFCV_SYSINFO_FLAG_ICREF; - nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); - buffer_pos += NFCV_UID_LENGTH; - ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; /* DSFID */ - ctx->response_buffer[buffer_pos++] = nfcv_data->afi; /* AFI */ - ctx->response_buffer[buffer_pos++] = nfcv_data->block_num - 1; /* number of blocks */ - ctx->response_buffer[buffer_pos++] = nfcv_data->block_size - 1; /* block size */ - ctx->response_buffer[buffer_pos++] = nfcv_data->ic_ref; /* IC reference */ - - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - buffer_pos, - ctx->response_flags, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO"); - break; - } - - case NFCV_CMD_CUST_ECHO_MODE: { - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_data->echo_mode = true; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO mode"); - break; - } - - case NFCV_CMD_CUST_ECHO_DATA: { - nfcv_emu_send( - tx_rx, - nfcv_data, - &nfcv_data->frame[ctx->payload_offset], - nfcv_data->frame_length - ctx->payload_offset - 2, - NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); - break; - } - - default: - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "unsupported: %02X", - ctx->command); - break; - } - - if(strlen(nfcv_data->last_command) > 0) { -#ifdef NFCV_VERBOSE - FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); -#endif - } -} - -void nfcv_emu_sniff_packet( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - - if(nfcv_data->frame_length < 2) { - return; - } - - /* parse the frame data for the upcoming part 3 handling */ - ctx->flags = nfcv_data->frame[0]; - ctx->command = nfcv_data->frame[1]; - ctx->selected = (ctx->flags & NFCV_REQ_FLAG_SELECT); - ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && - (ctx->flags & NFCV_REQ_FLAG_ADDRESS); - ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); - ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); - ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); - - char flags_string[5]; - - snprintf( - flags_string, - 5, - "%c%c%c%d", - (ctx->flags & NFCV_REQ_FLAG_INVENTORY) ? - 'I' : - (ctx->addressed ? 'A' : (ctx->selected ? 'S' : '*')), - ctx->advanced ? 'X' : ' ', - (ctx->flags & NFCV_REQ_FLAG_DATA_RATE) ? 'h' : 'l', - (ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) ? 2 : 1); - - switch(ctx->command) { - case NFCV_CMD_INVENTORY: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s INVENTORY", flags_string); - break; - } - - case NFCV_CMD_STAY_QUIET: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s STAYQUIET", flags_string); - nfcv_data->quiet = true; - break; - } - - case NFCV_CMD_LOCK_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s LOCK %d", - flags_string, - block); - break; - } - - case NFCV_CMD_WRITE_DSFID: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WR DSFID %d", - flags_string, - id); - break; - } - - case NFCV_CMD_WRITE_AFI: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WR AFI %d", - flags_string, - id); - break; - } - - case NFCV_CMD_LOCK_DSFID: { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s LOCK DSFID", - flags_string); - break; - } - - case NFCV_CMD_LOCK_AFI: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s LOCK AFI", flags_string); - break; - } - - case NFCV_CMD_SELECT: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s SELECT", flags_string); - break; - } - - case NFCV_CMD_RESET_TO_READY: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s RESET", flags_string); - break; - } - - case NFCV_CMD_READ_MULTI_BLOCK: - case NFCV_CMD_READ_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t blocks = 1; - - if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { - blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; - } - - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s READ %d cnt: %d", - flags_string, - block, - blocks); - - break; - } - - case NFCV_CMD_WRITE_MULTI_BLOCK: - case NFCV_CMD_WRITE_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t blocks = 1; - uint8_t data_pos = 1; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; - data_pos++; - } - - uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos]; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WRITE %d, cnd %d", - flags_string, - block, - blocks); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WRITE %d %02X %02X %02X %02X", - flags_string, - block, - data[0], - data[1], - data[2], - data[3]); - } - break; - } - - case NFCV_CMD_GET_SYSTEM_INFO: { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s SYSTEMINFO", - flags_string); - break; - } - - default: - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s unsupported: %02X", - flags_string, - ctx->command); - break; - } - - if(strlen(nfcv_data->last_command) > 0) { - FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); - } -} - -void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(nfc_data); - furi_assert(nfcv_data); - - if(!nfcv_emu_alloc(nfcv_data)) { - FURI_LOG_E(TAG, "Failed to allocate structures"); - nfcv_data->ready = false; - return; - } - - strcpy(nfcv_data->last_command, ""); - nfcv_data->quiet = false; - nfcv_data->selected = false; - nfcv_data->modified = false; - - /* everything is initialized */ - nfcv_data->ready = true; - - /* ensure the GPIO is already in unmodulated state */ - furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); - - rfal_platform_spi_acquire(); - /* stop operation to configure for transparent and passive mode */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - /* set enable, rx_enable and field detector enable */ - st25r3916WriteRegister( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - /* explicitely set the modulation resistor in case system config changes for some reason */ - st25r3916WriteRegister( - ST25R3916_REG_PT_MOD, - (0 << ST25R3916_REG_PT_MOD_ptm_res_shift) | (15 << ST25R3916_REG_PT_MOD_pt_res_shift)); - /* target mode: target, other fields do not have any effect as we use transparent mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ); - /* let us modulate the field using MOSI, read ASK modulation using IRQ */ - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); - - furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); - - /* if not set already, initialize the default protocol handler */ - if(!nfcv_data->emu_protocol_ctx) { - nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx)); - if(nfcv_data->sub_type == NfcVTypeSniff) { - nfcv_data->emu_protocol_handler = &nfcv_emu_sniff_packet; - } else { - nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet; - } - } - - FURI_LOG_D(TAG, "Starting NfcV emulation"); - FURI_LOG_D( - TAG, - " UID: %02X %02X %02X %02X %02X %02X %02X %02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); - - switch(nfcv_data->sub_type) { - case NfcVTypeSlixL: - FURI_LOG_D(TAG, " Card type: SLIX-L"); - slix_l_prepare(nfcv_data); - break; - case NfcVTypeSlixS: - FURI_LOG_D(TAG, " Card type: SLIX-S"); - slix_s_prepare(nfcv_data); - break; - case NfcVTypeSlix2: - FURI_LOG_D(TAG, " Card type: SLIX2"); - slix2_prepare(nfcv_data); - break; - case NfcVTypeSlix: - FURI_LOG_D(TAG, " Card type: SLIX"); - slix_prepare(nfcv_data); - break; - case NfcVTypePlain: - FURI_LOG_D(TAG, " Card type: Plain"); - break; - case NfcVTypeSniff: - FURI_LOG_D(TAG, " Card type: Sniffing"); - break; - } - - /* allocate a 512 edge buffer, more than enough */ - nfcv_data->emu_air.reader_signal = - pulse_reader_alloc(&gpio_nfc_irq_rfid_pull, NFCV_PULSE_BUFFER); - /* timebase shall be 1 ns */ - pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond); - /* and configure to already calculate the number of bits */ - pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, NFCV_PULSE_DURATION_NS); - /* this IO is fed into the µC via a diode, so we need a pulldown */ - pulse_reader_set_pull(nfcv_data->emu_air.reader_signal, GpioPullDown); - - /* start sampling */ - pulse_reader_start(nfcv_data->emu_air.reader_signal); -} - -void nfcv_emu_deinit(NfcVData* nfcv_data) { - furi_assert(nfcv_data); - - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); - nfcv_emu_free(nfcv_data); - - if(nfcv_data->emu_protocol_ctx) { - free(nfcv_data->emu_protocol_ctx); - nfcv_data->emu_protocol_ctx = NULL; - } - - /* set registers back to how we found them */ - st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0x00); - st25r3916WriteRegister(ST25R3916_REG_MODE, 0x08); - rfal_platform_spi_release(); -} - -bool nfcv_emu_loop( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - NfcVData* nfcv_data, - uint32_t timeout_ms) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data); - - bool ret = false; - uint32_t frame_state = NFCV_FRAME_STATE_SOF1; - uint32_t periods_previous = 0; - uint32_t frame_pos = 0; - uint32_t byte_value = 0; - uint32_t bits_received = 0; - uint32_t timeout = timeout_ms * 1000; - bool wait_for_pulse = false; - - if(!nfcv_data->ready) { - return false; - } - -#ifdef NFCV_DIAGNOSTIC_DUMPS - uint8_t period_buffer[NFCV_DIAGNOSTIC_DUMP_SIZE]; - uint32_t period_buffer_pos = 0; -#endif - - while(true) { - uint32_t periods = pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout); - uint32_t timestamp = DWT->CYCCNT; - - /* when timed out, reset to SOF state */ - if(periods == PULSE_READER_NO_EDGE || periods == PULSE_READER_LOST_EDGE) { - break; - } - -#ifdef NFCV_DIAGNOSTIC_DUMPS - if(period_buffer_pos < sizeof(period_buffer)) { - period_buffer[period_buffer_pos++] = periods; - } -#endif - - /* short helper for detecting a pulse position */ - if(wait_for_pulse) { - wait_for_pulse = false; - if(periods != 1) { - frame_state = NFCV_FRAME_STATE_RESET; - } - continue; - } - - switch(frame_state) { - case NFCV_FRAME_STATE_SOF1: - if(periods == 1) { - frame_state = NFCV_FRAME_STATE_SOF2; - } else { - frame_state = NFCV_FRAME_STATE_SOF1; - break; - } - break; - - case NFCV_FRAME_STATE_SOF2: - /* waiting for the second low period, telling us about coding */ - if(periods == 6) { - frame_state = NFCV_FRAME_STATE_CODING_256; - periods_previous = 0; - wait_for_pulse = true; - } else if(periods == 4) { - frame_state = NFCV_FRAME_STATE_CODING_4; - periods_previous = 2; - wait_for_pulse = true; - } else { - frame_state = NFCV_FRAME_STATE_RESET; - } - break; - - case NFCV_FRAME_STATE_CODING_256: - if(periods_previous > periods) { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - /* previous symbol left us with some pulse periods */ - periods -= periods_previous; - - if(periods > 512) { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } else if(periods == 2) { - frame_state = NFCV_FRAME_STATE_EOF; - break; - } - - periods_previous = 512 - (periods + 1); - byte_value = (periods - 1) / 2; - if(frame_pos < NFCV_FRAMESIZE_MAX) { - nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; - } - - wait_for_pulse = true; - - break; - - case NFCV_FRAME_STATE_CODING_4: - if(periods_previous > periods) { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - /* previous symbol left us with some pulse periods */ - periods -= periods_previous; - periods_previous = 0; - - byte_value >>= 2; - bits_received += 2; - - if(periods == 1) { - byte_value |= 0x00 << 6; // -V684 - periods_previous = 6; - } else if(periods == 3) { - byte_value |= 0x01 << 6; - periods_previous = 4; - } else if(periods == 5) { - byte_value |= 0x02 << 6; - periods_previous = 2; - } else if(periods == 7) { - byte_value |= 0x03 << 6; - periods_previous = 0; - } else if(periods == 2) { - frame_state = NFCV_FRAME_STATE_EOF; - break; - } else { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - if(bits_received >= 8) { - if(frame_pos < NFCV_FRAMESIZE_MAX) { - nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; - } - bits_received = 0; - } - wait_for_pulse = true; - break; - } - - /* post-state-machine cleanup and reset */ - if(frame_state == NFCV_FRAME_STATE_RESET) { - frame_state = NFCV_FRAME_STATE_SOF1; - } else if(frame_state == NFCV_FRAME_STATE_EOF) { - nfcv_data->frame_length = frame_pos; - nfcv_data->eof_timestamp = timestamp; - break; - } - } - - if(frame_state == NFCV_FRAME_STATE_EOF) { - /* we know that this code uses TIM2, so stop pulse reader */ - pulse_reader_stop(nfcv_data->emu_air.reader_signal); - if(tx_rx->sniff_rx) { - tx_rx->sniff_rx(nfcv_data->frame, frame_pos * 8, false, tx_rx->sniff_context); - } - nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data); - - pulse_reader_start(nfcv_data->emu_air.reader_signal); - ret = true; - - } -#ifdef NFCV_VERBOSE - else { - if(frame_state != NFCV_FRAME_STATE_SOF1) { - FURI_LOG_T(TAG, "leaving while in state: %lu", frame_state); - } - } -#endif - -#ifdef NFCV_DIAGNOSTIC_DUMPS - if(period_buffer_pos) { - FURI_LOG_T(TAG, "pulses:"); - for(uint32_t pos = 0; pos < period_buffer_pos; pos++) { - FURI_LOG_T(TAG, " #%lu: %u", pos, period_buffer[pos]); - } - } -#endif - - return ret; -} diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h deleted file mode 100644 index e4139de998..0000000000 --- a/lib/nfc/protocols/nfcv.h +++ /dev/null @@ -1,334 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include "nfc_util.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* true: modulating releases load, false: modulating adds load resistor to field coil */ -#define NFCV_LOAD_MODULATION_POLARITY (false) - -#define NFCV_FC (13560000.0f) /* MHz */ -#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */ -#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */ -#define NFCV_PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) - -/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum number of blocks is defined as 256 */ -#define NFCV_BLOCKS_MAX 256 -/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum size of blocks is defined as 32 */ -#define NFCV_BLOCKSIZE_MAX 32 -/* the resulting memory size a card can have */ -#define NFCV_MEMSIZE_MAX (NFCV_BLOCKS_MAX * NFCV_BLOCKSIZE_MAX) -/* ISO/IEC 15693-3:2019(E) 7.1b: standard allows up to 8192, the maxium frame length that we are expected to receive/send is less */ -#define NFCV_FRAMESIZE_MAX (1 + NFCV_MEMSIZE_MAX + NFCV_BLOCKS_MAX) - -/* maximum string length for log messages */ -#define NFCV_LOG_STR_LEN 128 -/* maximum of pulses to be buffered by pulse reader */ -#define NFCV_PULSE_BUFFER 512 - -//#define NFCV_DIAGNOSTIC_DUMPS -//#define NFCV_DIAGNOSTIC_DUMP_SIZE 256 -//#define NFCV_VERBOSE - -/* helpers to calculate the send time based on DWT->CYCCNT */ -#define NFCV_FDT_USEC(usec) ((usec)*64) -#define NFCV_FDT_FC(ticks) ((ticks)*6400 / 1356) - -/* state machine when receiving frame bits */ -#define NFCV_FRAME_STATE_SOF1 0 -#define NFCV_FRAME_STATE_SOF2 1 -#define NFCV_FRAME_STATE_CODING_4 2 -#define NFCV_FRAME_STATE_CODING_256 3 -#define NFCV_FRAME_STATE_EOF 4 -#define NFCV_FRAME_STATE_RESET 5 - -/* sequences for every section of a frame */ -#define NFCV_SIG_SOF 0 -#define NFCV_SIG_BIT0 1 -#define NFCV_SIG_BIT1 2 -#define NFCV_SIG_EOF 3 -#define NFCV_SIG_LOW_SOF 4 -#define NFCV_SIG_LOW_BIT0 5 -#define NFCV_SIG_LOW_BIT1 6 -#define NFCV_SIG_LOW_EOF 7 - -/* various constants */ -#define NFCV_COMMAND_RETRIES 5 -#define NFCV_UID_LENGTH 8 - -/* ISO15693 protocol flags */ -typedef enum { - /* ISO15693 protocol flags when INVENTORY is NOT set */ - NFCV_REQ_FLAG_SUB_CARRIER = (1 << 0), - NFCV_REQ_FLAG_DATA_RATE = (1 << 1), - NFCV_REQ_FLAG_INVENTORY = (1 << 2), - NFCV_REQ_FLAG_PROTOCOL_EXT = (1 << 3), - NFCV_REQ_FLAG_SELECT = (1 << 4), - NFCV_REQ_FLAG_ADDRESS = (1 << 5), - NFCV_REQ_FLAG_OPTION = (1 << 6), - /* ISO15693 protocol flags when INVENTORY flag is set */ - NFCV_REQ_FLAG_AFI = (1 << 4), - NFCV_REQ_FLAG_NB_SLOTS = (1 << 5) -} NfcVRequestFlags; - -/* ISO15693 protocol flags */ -typedef enum { - NFCV_RES_FLAG_ERROR = (1 << 0), - NFCV_RES_FLAG_VALIDITY = (1 << 1), - NFCV_RES_FLAG_FINAL = (1 << 2), - NFCV_RES_FLAG_PROTOCOL_EXT = (1 << 3), - NFCV_RES_FLAG_SEC_LEN1 = (1 << 4), - NFCV_RES_FLAG_SEC_LEN2 = (1 << 5), - NFCV_RES_FLAG_WAIT_EXT = (1 << 6), -} NfcVRsponseFlags; - -/* flags for SYSINFO response */ -typedef enum { - NFCV_SYSINFO_FLAG_DSFID = (1 << 0), - NFCV_SYSINFO_FLAG_AFI = (1 << 1), - NFCV_SYSINFO_FLAG_MEMSIZE = (1 << 2), - NFCV_SYSINFO_FLAG_ICREF = (1 << 3) -} NfcVSysinfoFlags; - -/* ISO15693 command codes */ -typedef enum { - /* mandatory command codes */ - NFCV_CMD_INVENTORY = 0x01, - NFCV_CMD_STAY_QUIET = 0x02, - /* optional command codes */ - NFCV_CMD_READ_BLOCK = 0x20, - NFCV_CMD_WRITE_BLOCK = 0x21, - NFCV_CMD_LOCK_BLOCK = 0x22, - NFCV_CMD_READ_MULTI_BLOCK = 0x23, - NFCV_CMD_WRITE_MULTI_BLOCK = 0x24, - NFCV_CMD_SELECT = 0x25, - NFCV_CMD_RESET_TO_READY = 0x26, - NFCV_CMD_WRITE_AFI = 0x27, - NFCV_CMD_LOCK_AFI = 0x28, - NFCV_CMD_WRITE_DSFID = 0x29, - NFCV_CMD_LOCK_DSFID = 0x2A, - NFCV_CMD_GET_SYSTEM_INFO = 0x2B, - NFCV_CMD_READ_MULTI_SECSTATUS = 0x2C, - /* advanced command codes */ - NFCV_CMD_ADVANCED = 0xA0, - /* flipper zero custom command codes */ - NFCV_CMD_CUST_ECHO_MODE = 0xDE, - NFCV_CMD_CUST_ECHO_DATA = 0xDF -} NfcVCommands; - -/* ISO15693 Response error codes */ -typedef enum { - NFCV_NOERROR = 0x00, - NFCV_ERROR_CMD_NOT_SUP = 0x01, // Command not supported - NFCV_ERROR_CMD_NOT_REC = 0x02, // Command not recognized (eg. parameter error) - NFCV_ERROR_CMD_OPTION = 0x03, // Command option not supported - NFCV_ERROR_GENERIC = 0x0F, // No additional Info about this error - NFCV_ERROR_BLOCK_UNAVAILABLE = 0x10, - NFCV_ERROR_BLOCK_LOCKED_ALREADY = 0x11, // cannot lock again - NFCV_ERROR_BLOCK_LOCKED = 0x12, // cannot be changed - NFCV_ERROR_BLOCK_WRITE = 0x13, // Writing was unsuccessful - NFCV_ERROR_BLOCL_WRITELOCK = 0x14 // Locking was unsuccessful -} NfcVErrorcodes; - -typedef enum { - NfcVLockBitDsfid = 1 << 0, - NfcVLockBitAfi = 1 << 1, - NfcVLockBitEas = 1 << 2, - NfcVLockBitPpl = 1 << 3, -} NfcVLockBits; - -typedef enum { - NfcVAuthMethodManual, - NfcVAuthMethodTonieBox, -} NfcVAuthMethod; - -typedef enum { - NfcVTypePlain = 0, - NfcVTypeSlix = 1, - NfcVTypeSlixS = 2, - NfcVTypeSlixL = 3, - NfcVTypeSlix2 = 4, - NfcVTypeSniff = 255, -} NfcVSubtype; - -typedef enum { - NfcVSendFlagsNormal = 0, - NfcVSendFlagsSof = 1 << 0, - NfcVSendFlagsCrc = 1 << 1, - NfcVSendFlagsEof = 1 << 2, - NfcVSendFlagsOneSubcarrier = 0, - NfcVSendFlagsTwoSubcarrier = 1 << 3, - NfcVSendFlagsLowRate = 0, - NfcVSendFlagsHighRate = 1 << 4 -} NfcVSendFlags; - -/* SLIX specific config flags */ -typedef enum { - NfcVSlixDataFlagsNone = 0, - NfcVSlixDataFlagsHasKeyRead = 1 << 0, - NfcVSlixDataFlagsHasKeyWrite = 1 << 1, - NfcVSlixDataFlagsHasKeyPrivacy = 1 << 2, - NfcVSlixDataFlagsHasKeyDestroy = 1 << 3, - NfcVSlixDataFlagsHasKeyEas = 1 << 4, - NfcVSlixDataFlagsValidKeyRead = 1 << 8, - NfcVSlixDataFlagsValidKeyWrite = 1 << 9, - NfcVSlixDataFlagsValidKeyPrivacy = 1 << 10, - NfcVSlixDataFlagsValidKeyDestroy = 1 << 11, - NfcVSlixDataFlagsValidKeyEas = 1 << 12, - NfcVSlixDataFlagsPrivacy = 1 << 16, - NfcVSlixDataFlagsDestroyed = 1 << 17 -} NfcVSlixDataFlags; - -/* abstract the file read/write operations for all SLIX types to reduce duplicated code */ -typedef enum { - SlixFeatureRead = 1 << 0, - SlixFeatureWrite = 1 << 1, - SlixFeaturePrivacy = 1 << 2, - SlixFeatureDestroy = 1 << 3, - SlixFeatureEas = 1 << 4, - SlixFeatureSignature = 1 << 5, - SlixFeatureProtection = 1 << 6, - - SlixFeatureSlix = SlixFeatureEas, - SlixFeatureSlixS = - (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | - SlixFeatureEas), - SlixFeatureSlixL = (SlixFeaturePrivacy | SlixFeatureDestroy | SlixFeatureEas), - SlixFeatureSlix2 = - (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | - SlixFeatureEas | SlixFeatureSignature | SlixFeatureProtection), -} SlixTypeFeatures; - -typedef struct { - uint32_t flags; - uint8_t key_read[4]; - uint8_t key_write[4]; - uint8_t key_privacy[4]; - uint8_t key_destroy[4]; - uint8_t key_eas[4]; - uint8_t rand[2]; - uint8_t signature[32]; - /* SLIX2 options */ - uint8_t pp_pointer; - uint8_t pp_condition; -} NfcVSlixData; - -typedef union { - NfcVSlixData slix; -} NfcVSubtypeData; - -typedef struct { - DigitalSignal* nfcv_resp_sof; - DigitalSignal* nfcv_resp_one; - DigitalSignal* nfcv_resp_zero; - DigitalSignal* nfcv_resp_eof; -} NfcVEmuAirSignals; - -typedef struct { - PulseReader* reader_signal; - DigitalSignal* nfcv_resp_pulse; /* pulse length, fc/32 */ - DigitalSignal* nfcv_resp_half_pulse; /* half pulse length, fc/32 */ - DigitalSignal* nfcv_resp_unmod; /* unmodulated length 256/fc */ - NfcVEmuAirSignals signals_high; - NfcVEmuAirSignals signals_low; - DigitalSequence* nfcv_signal; -} NfcVEmuAir; - -typedef void (*NfcVEmuProtocolHandler)( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data); -typedef bool (*NfcVEmuProtocolFilter)( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data); - -/* the default ISO15693 handler context */ -typedef struct { - uint8_t flags; /* ISO15693-3 flags of the header as specified */ - uint8_t command; /* ISO15693-3 command at offset 1 as specified */ - bool selected; /* ISO15693-3 flags: selected frame */ - bool addressed; /* ISO15693-3 flags: addressed frame */ - bool advanced; /* ISO15693-3 command: advanced command */ - uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */ - uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */ - - uint8_t response_buffer[NFCV_FRAMESIZE_MAX]; /* pre-allocated response buffer */ - NfcVSendFlags response_flags; /* flags to use when sending response */ - uint32_t send_time; /* timestamp when to send the response */ - - NfcVEmuProtocolFilter emu_protocol_filter; -} NfcVEmuProtocolCtx; - -typedef struct { - /* common ISO15693 fields, being specified in ISO15693-3 */ - uint8_t dsfid; - uint8_t afi; - uint8_t ic_ref; - uint16_t block_num; - uint8_t block_size; - uint8_t data[NFCV_MEMSIZE_MAX]; - uint8_t security_status[1 + NFCV_BLOCKS_MAX]; - bool selected; - bool quiet; - - bool modified; - bool ready; - bool echo_mode; - - /* specfic variant infos */ - NfcVSubtype sub_type; - NfcVSubtypeData sub_data; - NfcVAuthMethod auth_method; - - /* precalced air level data */ - NfcVEmuAir emu_air; - - uint8_t* frame; /* [NFCV_FRAMESIZE_MAX] ISO15693-2 incoming raw data from air layer */ - uint8_t frame_length; /* ISO15693-2 length of incoming data */ - uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */ - - /* handler for the protocol layer as specified in ISO15693-3 */ - NfcVEmuProtocolHandler emu_protocol_handler; - void* emu_protocol_ctx; - /* runtime data */ - char last_command[NFCV_LOG_STR_LEN]; - char error[NFCV_LOG_STR_LEN]; -} NfcVData; - -typedef struct { - uint16_t blocks_to_read; - int16_t blocks_read; -} NfcVReader; - -ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data); -ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data); -ReturnCode nfcv_inventory(uint8_t* uid); -bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data); - -void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); -void nfcv_emu_deinit(NfcVData* nfcv_data); -bool nfcv_emu_loop( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - NfcVData* nfcv_data, - uint32_t timeout_ms); -void nfcv_emu_send( - FuriHalNfcTxRxContext* tx_rx, - NfcVData* nfcv, - uint8_t* data, - uint8_t length, - NfcVSendFlags flags, - uint32_t send_time); - -#ifdef __cplusplus -} -#endif diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c deleted file mode 100644 index 4b15f4b977..0000000000 --- a/lib/nfc/protocols/slix.c +++ /dev/null @@ -1,784 +0,0 @@ - -#include -#include "nfcv.h" -#include "slix.h" -#include "nfc_util.h" -#include -#include "furi_hal_nfc.h" -#include - -#define TAG "Slix" - -ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(nfc_data); - furi_assert(nfcv_data); - - uint8_t rxBuf[32]; - uint16_t received = 0; - ReturnCode ret = ERR_NONE; - - FURI_LOG_D(TAG, "Read NXP SYSTEM INFORMATION..."); - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - uint8_t cmd[] = {}; - uint8_t uid[NFCV_UID_LENGTH]; - - /* UID is stored reversed in requests */ - for(int pos = 0; pos < nfc_data->uid_len; pos++) { - uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; - } - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION, - RFAL_NFCV_REQ_FLAG_DEFAULT, - NFCV_MANUFACTURER_NXP, - uid, - cmd, - sizeof(cmd), - rxBuf, - sizeof(rxBuf), - &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret != ERR_NONE || received != 8) { //-V560 - FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); - return ret; - } - FURI_LOG_D(TAG, "Success..."); - - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - slix->pp_pointer = rxBuf[1]; - slix->pp_condition = rxBuf[2]; - - /* convert NXP's to our internal lock bits format */ - nfcv_data->security_status[0] = 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitDsfid) ? NfcVLockBitDsfid : 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitAfi) ? NfcVLockBitAfi : 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitEas) ? NfcVLockBitEas : 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitPpl) ? NfcVLockBitPpl : 0; - - return ERR_NONE; -} - -ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(nfc_data); - furi_assert(nfcv_data); - - uint8_t rxBuf[64]; - uint16_t received = 0; - ReturnCode ret = ERR_NONE; - - FURI_LOG_D(TAG, "Read SIGNATURE..."); - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - uint8_t cmd[] = {}; - uint8_t uid[NFCV_UID_LENGTH]; - - /* UID is stored reversed in requests */ - for(int pos = 0; pos < nfc_data->uid_len; pos++) { - uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; - } - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_READ_SIGNATURE, - RFAL_NFCV_REQ_FLAG_DEFAULT, - NFCV_MANUFACTURER_NXP, - uid, - cmd, - sizeof(cmd), - rxBuf, - sizeof(rxBuf), - &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret != ERR_NONE || received != 33) { //-V560 - FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); - return ret; - } - FURI_LOG_D(TAG, "Success..."); - - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - memcpy(slix->signature, &rxBuf[1], 32); - - return ERR_NONE; -} - -ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - ReturnCode ret = ERR_NONE; - - ret = slix2_read_nxp_sysinfo(nfc_data, nfcv_data); - if(ret != ERR_NONE) { - return ret; - } - ret = slix2_read_signature(nfc_data, nfcv_data); - - return ret; -} - -static uint32_t slix_read_be(uint8_t* data, uint32_t length) { - uint32_t value = 0; - - for(uint32_t pos = 0; pos < length; pos++) { - value <<= 8; - value |= data[pos]; - } - - return value; -} - -uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) { - return (nfc_data->uid[3] >> 3) & 3; -} - -bool slix_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && - slix_get_ti(nfc_data) == 2) { - return true; - } - return false; -} - -bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && - slix_get_ti(nfc_data) == 1) { - return true; - } - return false; -} - -bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) { - return true; - } - return false; -} - -bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) { - return true; - } - return false; -} - -ReturnCode slix_get_random(NfcVData* data) { - uint16_t received = 0; - uint8_t rxBuf[32]; - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_GET_RANDOM_NUMBER, - RFAL_NFCV_REQ_FLAG_DEFAULT, - NFCV_MANUFACTURER_NXP, - NULL, - NULL, - 0, - rxBuf, - sizeof(rxBuf), - &received); - - if(ret == ERR_NONE) { - if(received != 3) { - return ERR_PROTO; - } - if(data != NULL) { - data->sub_data.slix.rand[0] = rxBuf[2]; - data->sub_data.slix.rand[1] = rxBuf[1]; - } - } - - return ret; -} - -ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { - furi_assert(data); - - uint16_t received = 0; - uint8_t rxBuf[32]; - uint8_t cmd_set_pass[] = { - password_id, - data->sub_data.slix.rand[1], - data->sub_data.slix.rand[0], - data->sub_data.slix.rand[1], - data->sub_data.slix.rand[0]}; - uint8_t* password = NULL; - - switch(password_id) { - case SLIX_PASS_READ: - password = data->sub_data.slix.key_read; - break; - case SLIX_PASS_WRITE: - password = data->sub_data.slix.key_write; - break; - case SLIX_PASS_PRIVACY: - password = data->sub_data.slix.key_privacy; - break; - case SLIX_PASS_DESTROY: - password = data->sub_data.slix.key_destroy; - break; - case SLIX_PASS_EASAFI: - password = data->sub_data.slix.key_eas; - break; - default: - break; - } - - if(!password) { - return ERR_NOTSUPP; - } - - for(int pos = 0; pos < 4; pos++) { - cmd_set_pass[1 + pos] ^= password[3 - pos]; - } - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_SET_PASSWORD, - RFAL_NFCV_REQ_FLAG_DATA_RATE, - NFCV_MANUFACTURER_NXP, - NULL, - cmd_set_pass, - sizeof(cmd_set_pass), - rxBuf, - sizeof(rxBuf), - &received); - - return ret; -} - -static void slix_generic_pass_infos( - uint8_t password_id, - NfcVSlixData* slix, - uint8_t** password, - uint32_t* flag_valid, - uint32_t* flag_set) { - switch(password_id) { - case SLIX_PASS_READ: - *password = slix->key_read; - *flag_valid = NfcVSlixDataFlagsValidKeyRead; - *flag_set = NfcVSlixDataFlagsHasKeyRead; - break; - case SLIX_PASS_WRITE: - *password = slix->key_write; - *flag_valid = NfcVSlixDataFlagsValidKeyWrite; - *flag_set = NfcVSlixDataFlagsHasKeyWrite; - break; - case SLIX_PASS_PRIVACY: - *password = slix->key_privacy; - *flag_valid = NfcVSlixDataFlagsValidKeyPrivacy; - *flag_set = NfcVSlixDataFlagsHasKeyPrivacy; - break; - case SLIX_PASS_DESTROY: - *password = slix->key_destroy; - *flag_valid = NfcVSlixDataFlagsValidKeyDestroy; - *flag_set = NfcVSlixDataFlagsHasKeyDestroy; - break; - case SLIX_PASS_EASAFI: - *password = slix->key_eas; - *flag_valid = NfcVSlixDataFlagsValidKeyEas; - *flag_set = NfcVSlixDataFlagsHasKeyEas; - break; - default: - break; - } -} - -bool slix_generic_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in, - uint32_t password_supported) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - - if((slix->flags & NfcVSlixDataFlagsPrivacy) && - ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && - ctx->command != NFCV_CMD_NXP_SET_PASSWORD) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "command 0x%02X ignored, privacy mode", - ctx->command); - FURI_LOG_D(TAG, "%s", nfcv_data->last_command); - return true; - } - - bool handled = false; - - switch(ctx->command) { - case NFCV_CMD_NXP_GET_RANDOM_NUMBER: { - slix->rand[0] = furi_hal_random_get(); - slix->rand[1] = furi_hal_random_get(); - - ctx->response_buffer[0] = NFCV_NOERROR; - ctx->response_buffer[1] = slix->rand[1]; - ctx->response_buffer[2] = slix->rand[0]; - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "GET_RANDOM_NUMBER -> 0x%02X%02X", - slix->rand[0], - slix->rand[1]); - - handled = true; - break; - } - - case NFCV_CMD_NXP_SET_PASSWORD: { - /* the password to be set is the first parameter */ - uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; - /* right after that is the XORed password */ - uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; - - /* only handle if the password type is supported */ - if(!(password_id & password_supported)) { - break; - } - - /* fetch the last RAND value */ - uint8_t* rand = slix->rand; - - /* first calc the password that has been sent */ - uint8_t password_rcv[4]; - for(int pos = 0; pos < 4; pos++) { - password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; - } - uint32_t pass_received = slix_read_be(password_rcv, 4); - - /* then determine the password type (or even update if not set yet) */ - uint8_t* password = NULL; - uint32_t flag_valid = 0; - uint32_t flag_set = 0; - - slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); - - /* when the password is not supported, return silently */ - if(!password) { - break; - } - - /* check if the password is known */ - bool pass_valid = false; - uint32_t pass_expect = 0; - - if(slix->flags & flag_set) { - /* if so, fetch the stored password and compare */ - pass_expect = slix_read_be(password, 4); - pass_valid = (pass_expect == pass_received); - } else { - /* if not known, just accept it and store that password */ - memcpy(password, password_rcv, 4); - nfcv_data->modified = true; - slix->flags |= flag_set; - - pass_valid = true; - } - - /* if the pass was valid or accepted for other reasons, continue */ - if(pass_valid) { - slix->flags |= flag_valid; - - /* handle actions when a correct password was given, aside of setting the flag */ - switch(password_id) { - case SLIX_PASS_PRIVACY: - slix->flags &= ~NfcVSlixDataFlagsPrivacy; - nfcv_data->modified = true; - break; - case SLIX_PASS_DESTROY: - slix->flags |= NfcVSlixDataFlagsDestroyed; - FURI_LOG_D(TAG, "Pooof! Got destroyed"); - break; - default: - break; - } - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "SET_PASSWORD #%02X 0x%08lX OK", - password_id, - pass_received); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "SET_PASSWORD #%02X 0x%08lX/%08lX FAIL", - password_id, - pass_received, - pass_expect); - } - handled = true; - break; - } - - case NFCV_CMD_NXP_WRITE_PASSWORD: { - uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; - - if(!(password_id & password_supported)) { - break; - } - - uint8_t* new_password = &nfcv_data->frame[ctx->payload_offset + 1]; - uint8_t* password = NULL; - uint32_t flag_valid = 0; - uint32_t flag_set = 0; - - slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); - - /* when the password is not supported, return silently */ - if(!password) { - break; - } - - bool pass_valid = (slix->flags & flag_valid); - if(!(slix->flags & flag_set)) { - pass_valid = true; - } - - if(pass_valid) { - slix->flags |= flag_valid; - slix->flags |= flag_set; - - memcpy(password, new_password, 4); - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD OK"); - } else { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD FAIL"); - } - handled = true; - break; - } - - case NFCV_CMD_NXP_ENABLE_PRIVACY: { - ctx->response_buffer[0] = NFCV_NOERROR; - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "NFCV_CMD_NXP_ENABLE_PRIVACY"); - - slix->flags |= NfcVSlixDataFlagsPrivacy; - handled = true; - break; - } - } - - return handled; -} - -bool slix_l_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter( - tx_rx, - nfc_data, - nfcv_data_in, - SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) { - return true; - } - - return handled; -} - -void slix_l_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix_l_protocol_filter; -} - -bool slix_s_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { - return true; - } - - return handled; -} - -void slix_s_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix_s_protocol_filter; -} - -bool slix_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) { - return true; - } - - return handled; -} - -void slix_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix_protocol_filter; -} - -bool slix2_protocol_filter( // -V524 - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { - return true; - } - - switch(ctx->command) { - /* override WRITE BLOCK for block 79 (16 bit counter) */ - case NFCV_CMD_WRITE_BLOCK: - case NFCV_CMD_WRITE_MULTI_BLOCK: { - uint8_t resp_len = 1; - uint8_t blocks = 1; - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t data_pos = ctx->payload_offset + 1; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - blocks = nfcv_data->frame[data_pos] + 1; - data_pos++; - } - - uint8_t* data = &nfcv_data->frame[data_pos]; - uint32_t data_len = nfcv_data->block_size * blocks; - - if((block + blocks) <= nfcv_data->block_num && - (data_pos + data_len + 2) == nfcv_data->frame_length) { - ctx->response_buffer[0] = NFCV_NOERROR; - - for(int block_num = block; block_num < block + blocks; block_num++) { - /* special case, 16-bit counter */ - if(block_num == 79) { - uint32_t dest; - uint32_t ctr_old; - - memcpy(&dest, &nfcv_data->frame[data_pos], 4); - memcpy(&ctr_old, &nfcv_data->data[nfcv_data->block_size * block_num], 4); - - uint32_t ctr_new = ctr_old; - bool allowed = true; - - /* increment counter */ - if(dest == 1) { - ctr_new = (ctr_old & 0xFFFF0000) | ((ctr_old + 1) & 0xFFFF); - - /* protection flag set? */ - if(ctr_old & 0x01000000) { //-V1051 - allowed = nfcv_data->sub_data.slix.flags & - NfcVSlixDataFlagsValidKeyRead; - } - } else { - ctr_new = dest; - allowed = nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsValidKeyWrite; - } - - if(allowed) { - memcpy( //-V1086 - &nfcv_data->data[nfcv_data->block_size * block_num], - &ctr_new, - 4); - } else { - /* incorrect read or write password */ - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - resp_len = 2; - } - } else { - memcpy( - &nfcv_data->data[nfcv_data->block_size * block_num], - &nfcv_data->frame[data_pos], - nfcv_data->block_size); - } - data_pos += nfcv_data->block_size; - } - nfcv_data->modified = true; - - } else { - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - resp_len = 2; - } - - bool respond = (ctx->response_buffer[0] == NFCV_NOERROR) || - (ctx->addressed || ctx->selected); - - if(respond) { - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - resp_len, - ctx->response_flags, - ctx->send_time); - } - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE MULTI BLOCK %d, %d blocks", - block, - blocks); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE BLOCK %d <- %02X %02X %02X %02X", - block, - data[0], - data[1], - data[2], - data[3]); - } - handled = true; - break; - } - - case NFCV_CMD_NXP_READ_SIGNATURE: { - uint32_t len = 0; - ctx->response_buffer[len++] = NFCV_NOERROR; - memcpy(&ctx->response_buffer[len], slix->signature, sizeof(slix->signature)); - len += sizeof(slix->signature); - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ_SIGNATURE"); - - handled = true; - break; - } - - case NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION: { - uint32_t len = 0; - uint8_t lock_bits = 0; - - /* convert our internal lock bits format into NXP's */ - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? SlixLockBitDsfid : 0; - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitAfi) ? SlixLockBitAfi : 0; - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitEas) ? SlixLockBitEas : 0; - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitPpl) ? SlixLockBitPpl : 0; - - ctx->response_buffer[len++] = NFCV_NOERROR; - ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_pointer; - ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_condition; - ctx->response_buffer[len++] = lock_bits; - ctx->response_buffer[len++] = 0x7F; /* features LSB */ - ctx->response_buffer[len++] = 0x35; /* features */ - ctx->response_buffer[len++] = 0; /* features */ - ctx->response_buffer[len++] = 0; /* features MSB */ - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "GET_NXP_SYSTEM_INFORMATION"); - - handled = true; - break; - } - } - - return handled; -} - -void slix2_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix2_protocol_filter; -} diff --git a/lib/nfc/protocols/slix.h b/lib/nfc/protocols/slix.h deleted file mode 100644 index 67f09e46d6..0000000000 --- a/lib/nfc/protocols/slix.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include -#include "nfc_util.h" -#include - -#define NFCV_MANUFACTURER_NXP 0x04 - -/* ISO15693-3 CUSTOM NXP COMMANDS */ -typedef enum { - NFCV_CMD_NXP_SET_EAS = 0xA2, - NFCV_CMD_NXP_RESET_EAS = 0xA3, - NFCV_CMD_NXP_LOCK_EAS = 0xA4, - NFCV_CMD_NXP_EAS_ALARM = 0xA5, - NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI = 0xA6, - NFCV_CMD_NXP_WRITE_EAS_ID = 0xA7, - NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION = 0xAB, - NFCV_CMD_NXP_INVENTORY_PAGE_READ = 0xB0, - NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST = 0xB1, - NFCV_CMD_NXP_GET_RANDOM_NUMBER = 0xB2, - NFCV_CMD_NXP_SET_PASSWORD = 0xB3, - NFCV_CMD_NXP_WRITE_PASSWORD = 0xB4, - NFCV_CMD_NXP_64_BIT_PASSWORD_PROTECTION = 0xB5, - NFCV_CMD_NXP_PROTECT_PAGE = 0xB6, - NFCV_CMD_NXP_LOCK_PAGE_PROTECTION_CONDITION = 0xB7, - NFCV_CMD_NXP_DESTROY = 0xB9, - NFCV_CMD_NXP_ENABLE_PRIVACY = 0xBA, - NFCV_CMD_NXP_STAY_QUIET_PERSISTENT = 0xBC, - NFCV_CMD_NXP_READ_SIGNATURE = 0xBD -} SlixCommands; - -/* lock bit bits used in SLIX's NXP SYSTEM INFORMATION response */ -typedef enum { - SlixLockBitAfi = 1 << 0, - SlixLockBitEas = 1 << 1, - SlixLockBitDsfid = 1 << 2, - SlixLockBitPpl = 1 << 3, -} SlixLockBits; - -/* available passwords */ -#define SLIX_PASS_READ 0x01 -#define SLIX_PASS_WRITE 0x02 -#define SLIX_PASS_PRIVACY 0x04 -#define SLIX_PASS_DESTROY 0x08 -#define SLIX_PASS_EASAFI 0x10 - -#define SLIX_PASS_ALL \ - (SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI) - -bool slix_check_card_type(FuriHalNfcDevData* nfc_data); -bool slix2_check_card_type(FuriHalNfcDevData* nfc_data); -bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data); -bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data); - -ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); -ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); -ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); - -ReturnCode slix_get_random(NfcVData* data); -ReturnCode slix_unlock(NfcVData* data, uint32_t password_id); - -void slix_prepare(NfcVData* nfcv_data); -void slix_s_prepare(NfcVData* nfcv_data); -void slix_l_prepare(NfcVData* nfcv_data); -void slix2_prepare(NfcVData* nfcv_data); diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c new file mode 100644 index 0000000000..12dc6750dd --- /dev/null +++ b/lib/nfc/protocols/slix/slix.c @@ -0,0 +1,433 @@ +#include "slix_i.h" +#include "slix_device_defs.h" + +#include +#include + +#define SLIX_PROTOCOL_NAME "SLIX" +#define SLIX_DEVICE_NAME "SLIX" + +#define SLIX_TYPE_SLIX_SLIX2 (0x01U) +#define SLIX_TYPE_SLIX_S (0x02U) +#define SLIX_TYPE_SLIX_L (0x03U) + +#define SLIX_TYPE_INDICATOR_SLIX (0x02U) +#define SLIX_TYPE_INDICATOR_SLIX2 (0x01U) + +#define SLIX_PASSWORD_READ_KEY "Password Read" +#define SLIX_PASSWORD_WRITE_KEY "Password Write" +#define SLIX_PASSWORD_PRIVACY_KEY "Password Privacy" +#define SLIX_PASSWORD_DESTROY_KEY "Password Destroy" +#define SLIX_PASSWORD_EAS_KEY "Password EAS" +#define SLIX_SIGNATURE_KEY "Signature" +#define SLIX_PRIVACY_MODE_KEY "Privacy Mode" +#define SLIX_PROTECTION_POINTER_KEY "Protection Pointer" +#define SLIX_PROTECTION_CONDITION_KEY "Protection Condition" +#define SLIX_LOCK_EAS_KEY "Lock EAS" +#define SLIX_LOCK_PPL_KEY "Lock PPL" + +typedef struct { + uint8_t iso15693_3[2]; + uint8_t icode_type; + union { + struct { + uint8_t unused_1 : 3; + uint8_t type_indicator : 2; + uint8_t unused_2 : 3; + }; + uint8_t serial_num[5]; + }; +} SlixUidLayout; + +const NfcDeviceBase nfc_device_slix = { + .protocol_name = SLIX_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)slix_alloc, + .free = (NfcDeviceFree)slix_free, + .reset = (NfcDeviceReset)slix_reset, + .copy = (NfcDeviceCopy)slix_copy, + .verify = (NfcDeviceVerify)slix_verify, + .load = (NfcDeviceLoad)slix_load, + .save = (NfcDeviceSave)slix_save, + .is_equal = (NfcDeviceEqual)slix_is_equal, + .get_name = (NfcDeviceGetName)slix_get_device_name, + .get_uid = (NfcDeviceGetUid)slix_get_uid, + .set_uid = (NfcDeviceSetUid)slix_set_uid, + .get_base_data = (NfcDeviceGetBaseData)slix_get_base_data, +}; + +static const char* slix_nfc_device_name[] = { + [SlixTypeSlix] = SLIX_DEVICE_NAME, + [SlixTypeSlixS] = SLIX_DEVICE_NAME "-S", + [SlixTypeSlixL] = SLIX_DEVICE_NAME "-L", + [SlixTypeSlix2] = SLIX_DEVICE_NAME "2", +}; + +static const SlixTypeFeatures slix_type_features[] = { + [SlixTypeSlix] = SLIX_TYPE_FEATURES_SLIX, + [SlixTypeSlixS] = SLIX_TYPE_FEATURES_SLIX_S, + [SlixTypeSlixL] = SLIX_TYPE_FEATURES_SLIX_L, + [SlixTypeSlix2] = SLIX_TYPE_FEATURES_SLIX2, +}; + +typedef struct { + const char* key; + SlixTypeFeatures feature_flag; + SlixPassword default_value; +} SlixPasswordConfig; + +static const SlixPasswordConfig slix_password_configs[] = { + [SlixPasswordTypeRead] = {SLIX_PASSWORD_READ_KEY, SLIX_TYPE_FEATURE_READ, 0x00000000U}, + [SlixPasswordTypeWrite] = {SLIX_PASSWORD_WRITE_KEY, SLIX_TYPE_FEATURE_WRITE, 0x00000000U}, + [SlixPasswordTypePrivacy] = {SLIX_PASSWORD_PRIVACY_KEY, SLIX_TYPE_FEATURE_PRIVACY, 0xFFFFFFFFU}, + [SlixPasswordTypeDestroy] = {SLIX_PASSWORD_DESTROY_KEY, SLIX_TYPE_FEATURE_DESTROY, 0xFFFFFFFFU}, + [SlixPasswordTypeEasAfi] = {SLIX_PASSWORD_EAS_KEY, SLIX_TYPE_FEATURE_EAS, 0x00000000U}, +}; + +static void slix_password_set_defaults(SlixPassword* passwords) { + for(uint32_t i = 0; i < COUNT_OF(slix_password_configs); ++i) { + passwords[i] = slix_password_configs[i].default_value; + } +} + +SlixData* slix_alloc() { + SlixData* data = malloc(sizeof(SlixData)); + + data->iso15693_3_data = iso15693_3_alloc(); + slix_password_set_defaults(data->passwords); + + return data; +} + +void slix_free(SlixData* data) { + furi_assert(data); + + iso15693_3_free(data->iso15693_3_data); + + free(data); +} + +void slix_reset(SlixData* data) { + furi_assert(data); + + iso15693_3_reset(data->iso15693_3_data); + slix_password_set_defaults(data->passwords); + + memset(&data->system_info, 0, sizeof(SlixSystemInfo)); + memset(data->signature, 0, sizeof(SlixSignature)); + + data->privacy = false; +} + +void slix_copy(SlixData* data, const SlixData* other) { + furi_assert(data); + furi_assert(other); + + iso15693_3_copy(data->iso15693_3_data, other->iso15693_3_data); + + memcpy(data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount); + memcpy(data->signature, other->signature, sizeof(SlixSignature)); + + data->system_info = other->system_info; + data->privacy = other->privacy; +} + +bool slix_verify(SlixData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + // No backward compatibility, unified format only + return false; +} + +static bool slix_load_passwords(SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { + bool ret = true; + + for(uint32_t i = 0; i < COUNT_OF(slix_password_configs); ++i) { + const SlixPasswordConfig* password_config = &slix_password_configs[i]; + + if(!slix_type_has_features(slix_type, password_config->feature_flag)) continue; + if(!flipper_format_key_exist(ff, password_config->key)) { + passwords[i] = password_config->default_value; + continue; + } + if(!flipper_format_read_hex( + ff, password_config->key, (uint8_t*)&passwords[i], sizeof(SlixPassword))) { + ret = false; + break; + } + } + + return ret; +} + +bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool loaded = false; + + do { + if(!iso15693_3_load(data->iso15693_3_data, ff, version)) break; + + const SlixType slix_type = slix_get_type(data); + if(slix_type >= SlixTypeCount) break; + + if(!slix_load_passwords(data->passwords, slix_type, ff)) break; + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { + if(flipper_format_key_exist(ff, SLIX_SIGNATURE_KEY)) { + if(!flipper_format_read_hex( + ff, SLIX_SIGNATURE_KEY, data->signature, SLIX_SIGNATURE_SIZE)) + break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { + if(flipper_format_key_exist(ff, SLIX_PRIVACY_MODE_KEY)) { + if(!flipper_format_read_bool(ff, SLIX_PRIVACY_MODE_KEY, &data->privacy, 1)) break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + SlixProtection* protection = &data->system_info.protection; + if(flipper_format_key_exist(ff, SLIX_PROTECTION_POINTER_KEY) && + flipper_format_key_exist(ff, SLIX_PROTECTION_CONDITION_KEY)) { + if(!flipper_format_read_hex( + ff, SLIX_PROTECTION_POINTER_KEY, &protection->pointer, 1)) + break; + if(!flipper_format_read_hex( + ff, SLIX_PROTECTION_CONDITION_KEY, &protection->condition, 1)) + break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { + if(flipper_format_key_exist(ff, SLIX_LOCK_EAS_KEY)) { + SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_read_bool(ff, SLIX_LOCK_EAS_KEY, &lock_bits->eas, 1)) break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + if(flipper_format_key_exist(ff, SLIX_LOCK_PPL_KEY)) { + SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_read_bool(ff, SLIX_LOCK_PPL_KEY, &lock_bits->ppl, 1)) break; + } + } + + loaded = true; + } while(false); + + return loaded; +} + +static bool + slix_save_passwords(const SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { + bool ret = true; + + for(uint32_t i = 0; i < COUNT_OF(slix_password_configs); ++i) { + const SlixPasswordConfig* password_config = &slix_password_configs[i]; + + if(!slix_type_has_features(slix_type, password_config->feature_flag)) continue; + if(!flipper_format_write_hex( + ff, password_config->key, (uint8_t*)&passwords[i], sizeof(SlixPassword))) { + ret = false; + break; + } + } + + return ret; +} + +bool slix_save(const SlixData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + const SlixType slix_type = slix_get_type(data); + if(slix_type >= SlixTypeCount) break; + + if(!iso15693_3_save(data->iso15693_3_data, ff)) break; + if(!flipper_format_write_comment_cstr(ff, SLIX_PROTOCOL_NAME " specific data")) break; + + if(!flipper_format_write_comment_cstr( + ff, + "Passwords are optional. If a password is omitted, a default value will be used")) + break; + + if(!slix_save_passwords(data->passwords, slix_type, ff)) break; + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { + if(!flipper_format_write_comment_cstr( + ff, + "This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.")) + break; + if(!flipper_format_write_hex( + ff, SLIX_SIGNATURE_KEY, data->signature, SLIX_SIGNATURE_SIZE)) + break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { + if(!flipper_format_write_bool(ff, SLIX_PRIVACY_MODE_KEY, &data->privacy, 1)) break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + const SlixProtection* protection = &data->system_info.protection; + if(!flipper_format_write_comment_cstr(ff, "Protection pointer configuration")) break; + if(!flipper_format_write_hex( + ff, SLIX_PROTECTION_POINTER_KEY, &protection->pointer, sizeof(uint8_t))) + break; + if(!flipper_format_write_hex( + ff, SLIX_PROTECTION_CONDITION_KEY, &protection->condition, sizeof(uint8_t))) + break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS) || + slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + if(!flipper_format_write_comment_cstr(ff, "SLIX Lock Bits")) break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { + const SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_write_bool(ff, SLIX_LOCK_EAS_KEY, &lock_bits->eas, 1)) break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + const SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_write_bool(ff, SLIX_LOCK_PPL_KEY, &lock_bits->ppl, 1)) break; + } + + saved = true; + } while(false); + + return saved; +} + +bool slix_is_equal(const SlixData* data, const SlixData* other) { + return iso15693_3_is_equal(data->iso15693_3_data, other->iso15693_3_data) && + memcmp(&data->system_info, &other->system_info, sizeof(SlixSystemInfo)) == 0 && + memcmp( + data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount) == + 0 && + memcmp(&data->signature, &other->signature, sizeof(SlixSignature)) == 0 && + data->privacy == other->privacy; +} + +const char* slix_get_device_name(const SlixData* data, NfcDeviceNameType name_type) { + UNUSED(name_type); + + const SlixType slix_type = slix_get_type(data); + furi_assert(slix_type < SlixTypeCount); + + return slix_nfc_device_name[slix_type]; +} + +const uint8_t* slix_get_uid(const SlixData* data, size_t* uid_len) { + return iso15693_3_get_uid(data->iso15693_3_data, uid_len); +} + +bool slix_set_uid(SlixData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso15693_3_set_uid(data->iso15693_3_data, uid, uid_len); +} + +const Iso15693_3Data* slix_get_base_data(const SlixData* data) { + furi_assert(data); + + return data->iso15693_3_data; +} + +SlixType slix_get_type(const SlixData* data) { + SlixType type = SlixTypeCount; + + do { + if(iso15693_3_get_manufacturer_id(data->iso15693_3_data) != SLIX_NXP_MANUFACTURER_CODE) + break; + + const SlixUidLayout* uid = (const SlixUidLayout*)data->iso15693_3_data->uid; + + if(uid->icode_type == SLIX_TYPE_SLIX_SLIX2) { + if(uid->type_indicator == SLIX_TYPE_INDICATOR_SLIX) { + type = SlixTypeSlix; + } else if(uid->type_indicator == SLIX_TYPE_INDICATOR_SLIX2) { + type = SlixTypeSlix2; + } + } else if(uid->icode_type == SLIX_TYPE_SLIX_S) { + type = SlixTypeSlixS; + } else if(uid->icode_type == SLIX_TYPE_SLIX_L) { + type = SlixTypeSlixL; + } + + } while(false); + + return type; +} + +SlixPassword slix_get_password(const SlixData* data, SlixPasswordType password_type) { + furi_assert(data); + furi_assert(password_type < SlixPasswordTypeCount); + + return data->passwords[password_type]; +} + +uint16_t slix_get_counter(const SlixData* data) { + furi_assert(data); + const SlixCounter* counter = (const SlixCounter*)iso15693_3_get_block_data( + data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM); + + return counter->value; +} + +bool slix_is_privacy_mode(const SlixData* data) { + furi_assert(data); + + return data->privacy; +} + +bool slix_is_block_protected( + const SlixData* data, + SlixPasswordType password_type, + uint8_t block_num) { + furi_assert(data); + furi_assert(password_type < SlixPasswordTypeCount); + + bool ret = false; + + do { + if(password_type != SlixPasswordTypeRead && password_type != SlixPasswordTypeWrite) break; + if(block_num >= iso15693_3_get_block_count(data->iso15693_3_data)) break; + if(block_num == SLIX_COUNTER_BLOCK_NUM) break; + + const bool high = block_num >= data->system_info.protection.pointer; + const bool read = password_type == SlixPasswordTypeRead; + + const uint8_t condition = high ? (read ? SLIX_PP_CONDITION_RH : SLIX_PP_CONDITION_WH) : + (read ? SLIX_PP_CONDITION_RL : SLIX_PP_CONDITION_WL); + + ret = data->system_info.protection.condition & condition; + } while(false); + + return ret; +} + +bool slix_is_counter_increment_protected(const SlixData* data) { + furi_assert(data); + + const SlixCounter* counter = (const SlixCounter*)iso15693_3_get_block_data( + data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM); + + return counter->protection != 0; +} + +bool slix_type_has_features(SlixType slix_type, SlixTypeFeatures features) { + furi_assert(slix_type < SlixTypeCount); + + return (slix_type_features[slix_type] & features) == features; +} + +bool slix_type_supports_password(SlixType slix_type, SlixPasswordType password_type) { + furi_assert(slix_type < SlixTypeCount); + furi_assert(password_type < SlixPasswordTypeCount); + + return slix_type_features[slix_type] & slix_password_configs[password_type].feature_flag; +} diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h new file mode 100644 index 0000000000..f6c1453c5d --- /dev/null +++ b/lib/nfc/protocols/slix/slix.h @@ -0,0 +1,146 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SLIX_BLOCK_SIZE (4U) +#define SLIX_SIGNATURE_SIZE (32U) + +#define SLIX_COUNTER_BLOCK_NUM (79U) + +#define SLIX_PP_CONDITION_RL (1U << 0) +#define SLIX_PP_CONDITION_WL (1U << 1) +#define SLIX_PP_CONDITION_RH (1U << 4) +#define SLIX_PP_CONDITION_WH (1U << 5) + +#define SLIX_FEATURE_FLAG_UM_PP (1UL << 0) +#define SLIX_FEATURE_FLAG_COUNTER (1UL << 1) +#define SLIX_FEATURE_FLAG_EAS_ID (1UL << 2) +#define SLIX_FEATURE_FLAG_EAS_PP (1UL << 3) +#define SLIX_FEATURE_FLAG_AFI_PP (1UL << 4) +#define SLIX_FEATURE_FLAG_INVENTORY_READ_EXT (1UL << 5) +#define SLIX_FEATURE_FLAG_EAS_IR (1UL << 6) +#define SLIX_FEATURE_FLAG_ORIGINALITY_SIG (1UL << 8) +#define SLIX_FEATURE_FLAG_ORIGINALITY_SIG_PP (1UL << 9) +#define SLIX_FEATURE_FLAG_PERSISTENT_QUIET (1UL << 10) +#define SLIX_FEATURE_FLAG_PRIVACY (1UL << 12) +#define SLIX_FEATURE_FLAG_DESTROY (1UL << 13) +#define SLIX_FEATURE_EXT (1UL << 31) + +#define SLIX_TYPE_FEATURE_READ (1U << 0) +#define SLIX_TYPE_FEATURE_WRITE (1U << 1) +#define SLIX_TYPE_FEATURE_PRIVACY (1U << 2) +#define SLIX_TYPE_FEATURE_DESTROY (1U << 3) +#define SLIX_TYPE_FEATURE_EAS (1U << 4) +#define SLIX_TYPE_FEATURE_SIGNATURE (1U << 5) +#define SLIX_TYPE_FEATURE_PROTECTION (1U << 6) + +typedef uint32_t SlixTypeFeatures; + +typedef enum { + SlixErrorNone, + SlixErrorTimeout, + SlixErrorFormat, + SlixErrorNotSupported, + SlixErrorInternal, + SlixErrorWrongPassword, + SlixErrorUidMismatch, + SlixErrorUnknown, +} SlixError; + +typedef enum { + SlixTypeSlix, + SlixTypeSlixS, + SlixTypeSlixL, + SlixTypeSlix2, + SlixTypeCount, +} SlixType; + +typedef enum { + SlixPasswordTypeRead, + SlixPasswordTypeWrite, + SlixPasswordTypePrivacy, + SlixPasswordTypeDestroy, + SlixPasswordTypeEasAfi, + SlixPasswordTypeCount, +} SlixPasswordType; + +typedef uint32_t SlixPassword; +typedef uint8_t SlixSignature[SLIX_SIGNATURE_SIZE]; +typedef bool SlixPrivacy; + +typedef struct { + uint8_t pointer; + uint8_t condition; +} SlixProtection; + +typedef struct { + bool eas; + bool ppl; +} SlixLockBits; + +typedef struct { + SlixProtection protection; + SlixLockBits lock_bits; +} SlixSystemInfo; + +typedef struct { + Iso15693_3Data* iso15693_3_data; + SlixSystemInfo system_info; + SlixSignature signature; + SlixPassword passwords[SlixPasswordTypeCount]; + SlixPrivacy privacy; +} SlixData; + +SlixData* slix_alloc(); + +void slix_free(SlixData* data); + +void slix_reset(SlixData* data); + +void slix_copy(SlixData* data, const SlixData* other); + +bool slix_verify(SlixData* data, const FuriString* device_type); + +bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version); + +bool slix_save(const SlixData* data, FlipperFormat* ff); + +bool slix_is_equal(const SlixData* data, const SlixData* other); + +const char* slix_get_device_name(const SlixData* data, NfcDeviceNameType name_type); + +const uint8_t* slix_get_uid(const SlixData* data, size_t* uid_len); + +bool slix_set_uid(SlixData* data, const uint8_t* uid, size_t uid_len); + +const Iso15693_3Data* slix_get_base_data(const SlixData* data); + +// Getters and tests + +SlixType slix_get_type(const SlixData* data); + +SlixPassword slix_get_password(const SlixData* data, SlixPasswordType password_type); + +uint16_t slix_get_counter(const SlixData* data); + +bool slix_is_privacy_mode(const SlixData* data); + +bool slix_is_block_protected( + const SlixData* data, + SlixPasswordType password_type, + uint8_t block_num); + +bool slix_is_counter_increment_protected(const SlixData* data); + +// Static methods +bool slix_type_has_features(SlixType slix_type, SlixTypeFeatures features); + +bool slix_type_supports_password(SlixType slix_type, SlixPasswordType password_type); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_device_defs.h b/lib/nfc/protocols/slix/slix_device_defs.h new file mode 100644 index 0000000000..cadd40eecd --- /dev/null +++ b/lib/nfc/protocols/slix/slix_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_slix; diff --git a/lib/nfc/protocols/slix/slix_i.c b/lib/nfc/protocols/slix/slix_i.c new file mode 100644 index 0000000000..97d66484c0 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_i.c @@ -0,0 +1,127 @@ +#include "slix_i.h" + +#include + +bool slix_error_response_parse(SlixError* error, const BitBuffer* buf) { + Iso15693_3Error iso15693_3_error; + const bool ret = iso15693_3_error_response_parse(&iso15693_3_error, buf); + + if(ret) { + *error = slix_process_iso15693_3_error(iso15693_3_error); + } + + return ret; +} + +SlixError slix_process_iso15693_3_error(Iso15693_3Error iso15693_3_error) { + switch(iso15693_3_error) { + case Iso15693_3ErrorNone: + return SlixErrorNone; + case Iso15693_3ErrorTimeout: + return SlixErrorTimeout; + case Iso15693_3ErrorFormat: + return SlixErrorFormat; + case Iso15693_3ErrorInternal: + return SlixErrorInternal; + default: + return SlixErrorUnknown; + } +} + +SlixError slix_get_nxp_system_info_response_parse(SlixData* data, const BitBuffer* buf) { + furi_assert(data); + SlixError error = SlixErrorNone; + + do { + if(slix_error_response_parse(&error, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t pp_pointer; + uint8_t pp_condition; + uint8_t lock_bits; + uint32_t feature_flags; + } SlixGetNxpSystemInfoResponseLayout; + + const size_t size_received = bit_buffer_get_size_bytes(buf); + const size_t size_required = sizeof(SlixGetNxpSystemInfoResponseLayout); + + if(size_received != size_required) { + error = SlixErrorFormat; + break; + } + + const SlixGetNxpSystemInfoResponseLayout* response = + (const SlixGetNxpSystemInfoResponseLayout*)bit_buffer_get_data(buf); + + SlixProtection* protection = &data->system_info.protection; + protection->pointer = response->pp_pointer; + protection->condition = response->pp_condition; + + Iso15693_3LockBits* iso15693_3_lock_bits = &data->iso15693_3_data->settings.lock_bits; + iso15693_3_lock_bits->dsfid = response->lock_bits & SLIX_LOCK_BITS_DSFID; + iso15693_3_lock_bits->afi = response->lock_bits & SLIX_LOCK_BITS_AFI; + + SlixLockBits* slix_lock_bits = &data->system_info.lock_bits; + slix_lock_bits->eas = response->lock_bits & SLIX_LOCK_BITS_EAS; + slix_lock_bits->ppl = response->lock_bits & SLIX_LOCK_BITS_PPL; + + } while(false); + + return error; +} + +SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer* buf) { + SlixError error = SlixErrorNone; + + do { + if(slix_error_response_parse(&error, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t signature[SLIX_SIGNATURE_SIZE]; + } SlixReadSignatureResponseLayout; + + const size_t size_received = bit_buffer_get_size_bytes(buf); + const size_t size_required = sizeof(SlixReadSignatureResponseLayout); + + if(size_received != size_required) { + error = SlixErrorFormat; + break; + } + + const SlixReadSignatureResponseLayout* response = + (const SlixReadSignatureResponseLayout*)bit_buffer_get_data(buf); + + memcpy(data, response->signature, sizeof(SlixSignature)); + } while(false); + + return error; +} + +void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password) { + furi_assert(data); + furi_assert(password_type < SlixPasswordTypeCount); + + data->passwords[password_type] = password; +} + +void slix_set_privacy_mode(SlixData* data, bool set) { + furi_assert(data); + + data->privacy = set; +} + +void slix_increment_counter(SlixData* data) { + furi_assert(data); + + const uint8_t* block_data = + iso15693_3_get_block_data(data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM); + + SlixCounter counter; + memcpy(counter.bytes, block_data, SLIX_BLOCK_SIZE); + counter.value += 1; + + iso15693_3_set_block_data( + data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM, counter.bytes, sizeof(SlixCounter)); +} diff --git a/lib/nfc/protocols/slix/slix_i.h b/lib/nfc/protocols/slix/slix_i.h new file mode 100644 index 0000000000..b5e445f31d --- /dev/null +++ b/lib/nfc/protocols/slix/slix_i.h @@ -0,0 +1,86 @@ +#pragma once + +#include "slix.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SLIX_NXP_MANUFACTURER_CODE (0x04U) + +#define SLIX_LOCK_BITS_AFI (1U << 0) +#define SLIX_LOCK_BITS_EAS (1U << 1) +#define SLIX_LOCK_BITS_DSFID (1U << 2) +#define SLIX_LOCK_BITS_PPL (1U << 3) + +#define SLIX_CMD_CUSTOM_START (0xA2U) +#define SLIX_CMD_SET_EAS (0xA2U) +#define SLIX_CMD_RESET_EAS (0xA3U) +#define SLIX_CMD_LOCK_EAS (0xA4U) +#define SLIX_CMD_EAS_ALARM (0xA5U) +#define SLIX_CMD_PASSWORD_PROTECT_EAS_AFI (0xA6U) +#define SLIX_CMD_WRITE_EAS_ID (0xA7U) +#define SLIX_CMD_GET_NXP_SYSTEM_INFORMATION (0xABU) +#define SLIX_CMD_INVENTORY_PAGE_READ (0xB0U) +#define SLIX_CMD_INVENTORY_PAGE_READ_FAST (0xB1U) +#define SLIX_CMD_GET_RANDOM_NUMBER (0xB2U) +#define SLIX_CMD_SET_PASSWORD (0xB3U) +#define SLIX_CMD_WRITE_PASSWORD (0xB4U) +#define SLIX_CMD_64_BIT_PASSWORD_PROTECTION (0xB5U) +#define SLIX_CMD_PROTECT_PAGE (0xB6U) +#define SLIX_CMD_LOCK_PAGE_PROTECTION_CONDITION (0xB7U) +#define SLIX_CMD_DESTROY (0xB9U) +#define SLIX_CMD_ENABLE_PRIVACY (0xBAU) +#define SLIX_CMD_STAY_QUIET_PERSISTENT (0xBCU) +#define SLIX_CMD_READ_SIGNATURE (0xBDU) +#define SLIX_CMD_CUSTOM_END (0xBEU) +#define SLIX_CMD_CUSTOM_COUNT (SLIX_CMD_CUSTOM_END - SLIX_CMD_CUSTOM_START) + +#define SLIX_TYPE_FEATURES_SLIX (SLIX_TYPE_FEATURE_EAS) +#define SLIX_TYPE_FEATURES_SLIX_S \ + (SLIX_TYPE_FEATURE_READ | SLIX_TYPE_FEATURE_WRITE | SLIX_TYPE_FEATURE_PRIVACY | \ + SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS) +#define SLIX_TYPE_FEATURES_SLIX_L \ + (SLIX_TYPE_FEATURE_PRIVACY | SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS) +#define SLIX_TYPE_FEATURES_SLIX2 \ + (SLIX_TYPE_FEATURE_READ | SLIX_TYPE_FEATURE_WRITE | SLIX_TYPE_FEATURE_PRIVACY | \ + SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS | SLIX_TYPE_FEATURE_SIGNATURE | \ + SLIX_TYPE_FEATURE_PROTECTION) + +#define SLIX2_FEATURE_FLAGS \ + (SLIX_FEATURE_FLAG_UM_PP | SLIX_FEATURE_FLAG_COUNTER | SLIX_FEATURE_FLAG_EAS_ID | \ + SLIX_FEATURE_FLAG_EAS_PP | SLIX_FEATURE_FLAG_AFI_PP | SLIX_FEATURE_FLAG_INVENTORY_READ_EXT | \ + SLIX_FEATURE_FLAG_EAS_IR | SLIX_FEATURE_FLAG_ORIGINALITY_SIG | \ + SLIX_FEATURE_FLAG_PERSISTENT_QUIET | SLIX_FEATURE_FLAG_PRIVACY | SLIX_FEATURE_FLAG_DESTROY) + +typedef union { + struct { + uint16_t value; + uint8_t reserved; + uint8_t protection; + }; + uint8_t bytes[SLIX_BLOCK_SIZE]; +} SlixCounter; + +// Same behaviour as iso15693_3_error_response_parse +bool slix_error_response_parse(SlixError* error, const BitBuffer* buf); + +SlixError slix_process_iso15693_3_error(Iso15693_3Error iso15693_3_error); + +SlixError slix_get_nxp_system_info_response_parse(SlixData* data, const BitBuffer* buf); + +SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer* buf); + +// Setters +void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password); + +void slix_set_privacy_mode(SlixData* data, bool set); + +void slix_increment_counter(SlixData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_listener.c b/lib/nfc/protocols/slix/slix_listener.c new file mode 100644 index 0000000000..204be5ab91 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener.c @@ -0,0 +1,79 @@ +#include "slix_listener_i.h" + +#include +#include + +#define TAG "SlixListener" + +#define SLIX_LISTENER_BUF_SIZE (64U) + +static SlixListener* slix_listener_alloc(Iso15693_3Listener* iso15693_3_listener, SlixData* data) { + furi_assert(iso15693_3_listener); + + SlixListener* instance = malloc(sizeof(SlixListener)); + instance->iso15693_3_listener = iso15693_3_listener; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(SLIX_LISTENER_BUF_SIZE); + + instance->slix_event.data = &instance->slix_event_data; + instance->generic_event.protocol = NfcProtocolSlix; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->slix_event; + + slix_listener_init_iso15693_3_extensions(instance); + + return instance; +} + +static void slix_listener_free(SlixListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +static void + slix_listener_set_callback(SlixListener* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +static const SlixData* slix_listener_get_data(SlixListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static NfcCommand slix_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso15693_3); + furi_assert(event.event_data); + + SlixListener* instance = context; + Iso15693_3ListenerEvent* iso15693_3_event = event.event_data; + BitBuffer* rx_buffer = iso15693_3_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { + const SlixError error = slix_listener_process_request(instance, rx_buffer); + if(error == SlixErrorWrongPassword) { + command = NfcCommandStop; + } + } + + return command; +} + +const NfcListenerBase nfc_listener_slix = { + .alloc = (NfcListenerAlloc)slix_listener_alloc, + .free = (NfcListenerFree)slix_listener_free, + .set_callback = (NfcListenerSetCallback)slix_listener_set_callback, + .get_data = (NfcListenerGetData)slix_listener_get_data, + .run = (NfcListenerRun)slix_listener_run, +}; diff --git a/lib/nfc/protocols/slix/slix_listener.h b/lib/nfc/protocols/slix/slix_listener.h new file mode 100644 index 0000000000..7b8518a5bb --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "slix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SlixListener SlixListener; + +typedef enum { + SlixListenerEventTypeFieldOff, + SlixListenerEventTypeCustomCommand, +} SlixListenerEventType; + +typedef struct { + BitBuffer* buffer; +} SlixListenerEventData; + +typedef struct { + SlixListenerEventType type; + SlixListenerEventData* data; +} SlixListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_listener_defs.h b/lib/nfc/protocols/slix/slix_listener_defs.h new file mode 100644 index 0000000000..0598090c47 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_slix; diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c new file mode 100644 index 0000000000..dfcb6c8801 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -0,0 +1,617 @@ +#include "slix_listener_i.h" + +#include + +#include + +#define TAG "SlixListener" + +typedef SlixError (*SlixRequestHandler)( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags); + +// Helper functions + +static bool + slix_listener_is_password_lock_enabled(SlixListener* instance, SlixPasswordType password_type) { + return !instance->session_state.password_match[password_type]; +} + +static SlixPasswordType slix_listener_get_password_type_by_id(uint8_t id) { + uint32_t type; + + for(type = 0; type < SlixPasswordTypeCount; ++type) { + if(id >> type == 0x01U) break; + } + + return type; +} + +static SlixPassword + slix_listener_unxor_password(const SlixPassword password_xored, uint16_t random) { + return password_xored ^ ((SlixPassword)random << 16 | random); +} + +static SlixError slix_listener_set_password( + SlixListener* instance, + SlixPasswordType password_type, + SlixPassword password) { + SlixError error = SlixErrorNone; + + do { + if(password_type >= SlixPasswordTypeCount) { + error = SlixErrorInternal; + break; + } + + SlixData* slix_data = instance->data; + + if(!slix_type_supports_password(slix_get_type(slix_data), password_type)) { + error = SlixErrorNotSupported; + break; + } + + SlixListenerSessionState* session_state = &instance->session_state; + session_state->password_match[password_type] = + (password == slix_get_password(slix_data, password_type)); + + if(!session_state->password_match[password_type]) { + error = SlixErrorWrongPassword; + break; + } + } while(false); + + return error; +} + +static SlixError slix_listener_write_password( + SlixListener* instance, + SlixPasswordType password_type, + SlixPassword password) { + SlixError error = SlixErrorNone; + + do { + if(password_type >= SlixPasswordTypeCount) { + error = SlixErrorInternal; + break; + } + + SlixData* slix_data = instance->data; + + if(!slix_type_supports_password(slix_get_type(slix_data), password_type)) { + error = SlixErrorNotSupported; + break; + } + + SlixListenerSessionState* session_state = &instance->session_state; + + if(session_state->password_match[password_type]) { + // TODO FL-3634: check for password lock + slix_set_password(slix_data, password_type, password); + // Require another SET_PASSWORD command with the new password + session_state->password_match[password_type] = false; + } else { + error = SlixErrorWrongPassword; + break; + } + } while(false); + + return error; +} + +// Custom SLIX request handlers +static SlixError slix_listener_default_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(instance); + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + // Empty placeholder handler + return SlixErrorNotSupported; +} + +static SlixError slix_listener_get_nxp_system_info_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + const SlixData* slix_data = instance->data; + const Iso15693_3Data* iso15693_data = instance->data->iso15693_3_data; + + const SlixProtection* protection = &slix_data->system_info.protection; + bit_buffer_append_byte(instance->tx_buffer, protection->pointer); + bit_buffer_append_byte(instance->tx_buffer, protection->condition); + + uint8_t lock_bits = 0; + if(iso15693_data->settings.lock_bits.dsfid) { + lock_bits |= SLIX_LOCK_BITS_DSFID; + } + if(iso15693_data->settings.lock_bits.afi) { + lock_bits |= SLIX_LOCK_BITS_AFI; + } + if(slix_data->system_info.lock_bits.eas) { + lock_bits |= SLIX_LOCK_BITS_EAS; + } + if(slix_data->system_info.lock_bits.ppl) { + lock_bits |= SLIX_LOCK_BITS_PPL; + } + bit_buffer_append_byte(instance->tx_buffer, lock_bits); + + const uint32_t feature_flags = SLIX2_FEATURE_FLAGS; + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&feature_flags, sizeof(uint32_t)); + + return SlixErrorNone; +} + +static SlixError slix_listener_get_random_number_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + SlixListenerSessionState* session_state = &instance->session_state; + session_state->random = furi_hal_random_get(); + bit_buffer_append_bytes( + instance->tx_buffer, (uint8_t*)&session_state->random, sizeof(uint16_t)); + + return SlixErrorNone; +} + +static SlixError slix_listener_set_password_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { +#pragma pack(push, 1) + typedef struct { + uint8_t password_id; + SlixPassword password_xored; + } SlixSetPasswordRequestLayout; +#pragma pack(pop) + + if(data_size != sizeof(SlixSetPasswordRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixSetPasswordRequestLayout* request = (const SlixSetPasswordRequestLayout*)data; + const SlixPasswordType password_type = + slix_listener_get_password_type_by_id(request->password_id); + const SlixPassword password_received = + slix_listener_unxor_password(request->password_xored, instance->session_state.random); + + error = slix_listener_set_password(instance, password_type, password_received); + if(error != SlixErrorNone) break; + + if(password_type == SlixPasswordTypePrivacy) { + slix_set_privacy_mode(instance->data, false); + } + } while(false); + + return error; +} + +static SlixError slix_listener_write_password_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { +#pragma pack(push, 1) + typedef struct { + uint8_t password_id; + SlixPassword password; + } SlixWritePasswordRequestLayout; +#pragma pack(pop) + + if(data_size != sizeof(SlixWritePasswordRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixWritePasswordRequestLayout* request = + (const SlixWritePasswordRequestLayout*)data; + const SlixPasswordType password_type = + slix_listener_get_password_type_by_id(request->password_id); + + error = slix_listener_write_password(instance, password_type, request->password); + if(error != SlixErrorNone) break; + + } while(false); + + return error; +} + +static SlixError slix_listener_protect_page_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { + typedef struct { + uint8_t pointer; + uint8_t condition; + } SlixProtectPageRequestLayout; + + if(data_size != sizeof(SlixProtectPageRequestLayout)) { + error = SlixErrorFormat; + break; + } + + SlixData* slix_data = instance->data; + + if(slix_data->system_info.lock_bits.ppl) { + error = SlixErrorInternal; + break; + } + + const SlixListenerSessionState* session_state = &instance->session_state; + if(!session_state->password_match[SlixPasswordTypeRead] || + !session_state->password_match[SlixPasswordTypeWrite]) { + error = SlixErrorInternal; + break; + } + + const SlixProtectPageRequestLayout* request = (const SlixProtectPageRequestLayout*)data; + + if(request->pointer >= SLIX_COUNTER_BLOCK_NUM) { + error = SlixErrorInternal; + break; + } + + SlixProtection* protection = &slix_data->system_info.protection; + + protection->pointer = request->pointer; + protection->condition = request->condition; + } while(false); + + return error; +} + +static SlixError slix_listener_enable_privacy_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { + typedef struct { + SlixPassword password_xored; + } SlixEnablePrivacyRequestLayout; + + if(data_size != sizeof(SlixEnablePrivacyRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixEnablePrivacyRequestLayout* request = + (const SlixEnablePrivacyRequestLayout*)data; + + const SlixPassword password_received = + slix_listener_unxor_password(request->password_xored, instance->session_state.random); + + error = slix_listener_set_password(instance, SlixPasswordTypePrivacy, password_received); + if(error != SlixErrorNone) break; + + slix_set_privacy_mode(instance->data, true); + } while(false); + + return error; +} + +static SlixError slix_listener_read_signature_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + bit_buffer_append_bytes(instance->tx_buffer, instance->data->signature, sizeof(SlixSignature)); + return SlixErrorNone; +} + +// Custom SLIX commands handler table +static const SlixRequestHandler slix_request_handler_table[SLIX_CMD_CUSTOM_COUNT] = { + slix_listener_default_handler, // SLIX_CMD_SET_EAS (0xA2U) + slix_listener_default_handler, // SLIX_CMD_RESET_EAS (0xA3U) + slix_listener_default_handler, // SLIX_CMD_LOCK_EAS (0xA4U) + slix_listener_default_handler, // SLIX_CMD_EAS_ALARM (0xA5U) + slix_listener_default_handler, // SLIX_CMD_PASSWORD_PROTECT_EAS_AFI (0xA6U) + slix_listener_default_handler, // SLIX_CMD_WRITE_EAS_ID (0xA7U) + slix_listener_default_handler, // UNUSED (0xA8U) + slix_listener_default_handler, // UNUSED (0xA9U) + slix_listener_default_handler, // UNUSED (0xAAU) + slix_listener_get_nxp_system_info_handler, + slix_listener_default_handler, // UNUSED (0xACU) + slix_listener_default_handler, // UNUSED (0xADU) + slix_listener_default_handler, // UNUSED (0xAEU) + slix_listener_default_handler, // UNUSED (0xAFU) + slix_listener_default_handler, // SLIX_CMD_INVENTORY_PAGE_READ (0xB0U) + slix_listener_default_handler, // SLIX_CMD_INVENTORY_PAGE_READ_FAST (0xB1U) + slix_listener_get_random_number_handler, + slix_listener_set_password_handler, + slix_listener_write_password_handler, + slix_listener_default_handler, // SLIX_CMD_64_BIT_PASSWORD_PROTECTION (0xB5U) + slix_listener_protect_page_handler, + slix_listener_default_handler, // SLIX_CMD_LOCK_PAGE_PROTECTION_CONDITION (0xB7U) + slix_listener_default_handler, // UNUSED (0xB8U) + slix_listener_default_handler, // SLIX_CMD_DESTROY (0xB9U) + slix_listener_enable_privacy_handler, + slix_listener_default_handler, // UNUSED (0xBBU) + slix_listener_default_handler, // SLIX_CMD_STAY_QUIET_PERSISTENT (0xBCU) + slix_listener_read_signature_handler, +}; + +// ISO15693-3 Protocol extension handlers + +static Iso15693_3Error + slix_listener_iso15693_3_inventory_extension_handler(SlixListener* instance, va_list args) { + UNUSED(args); + + return instance->data->privacy ? Iso15693_3ErrorIgnore : Iso15693_3ErrorNone; +} + +static Iso15693_3Error + slix_iso15693_3_read_block_extension_handler(SlixListener* instance, va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint32_t block_num = va_arg(args, uint32_t); + // SLIX Counter has no read protection + if(block_num == SLIX_COUNTER_BLOCK_NUM) break; + + if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + } while(false); + + return error; +} + +static Iso15693_3Error + slix_listener_iso15693_3_write_block_extension_handler(SlixListener* instance, va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint32_t block_num = va_arg(args, uint32_t); + + if(block_num == SLIX_COUNTER_BLOCK_NUM) { + const uint32_t counter = *(va_arg(args, uint32_t*)); + if(counter == 0x00000001U) { + if(slix_is_counter_increment_protected(instance->data) && + slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + slix_increment_counter(instance->data); + error = Iso15693_3ErrorFullyHandled; + break; + } + } else if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + if(slix_is_block_protected(instance->data, SlixPasswordTypeWrite, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeWrite)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + } while(false); + + return error; +} + +static Iso15693_3Error + slix_listener_iso15693_3_lock_block_extension_handler(SlixListener* instance, va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint32_t block_num = va_arg(args, uint32_t); + + // SLIX counter cannot be locked + if(block_num == SLIX_COUNTER_BLOCK_NUM) { + error = Iso15693_3ErrorInternal; + break; + } + + if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + if(slix_is_block_protected(instance->data, SlixPasswordTypeWrite, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeWrite)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + } while(false); + + return error; +} + +static Iso15693_3Error slix_listener_iso15693_3_read_multi_block_extension_handler( + SlixListener* instance, + va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + const uint32_t block_index_start = va_arg(args, uint32_t); + const uint32_t block_index_end = va_arg(args, uint32_t); + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + // SLIX Counter has no read protection + if(i == SLIX_COUNTER_BLOCK_NUM) continue; + + if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, i)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + } + + return error; +} + +static Iso15693_3Error slix_listener_iso15693_3_write_multi_block_extension_handler( + SlixListener* instance, + va_list args) { + UNUSED(instance); + UNUSED(args); + // No mention of this command in SLIX docs, assuming not supported + return Iso15693_3ErrorNotSupported; +} + +static Iso15693_3Error slix_listener_iso15693_3_write_lock_afi_extension_handler( + SlixListener* instance, + va_list args) { + UNUSED(args); + + return slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeEasAfi) ? + Iso15693_3ErrorInternal : + Iso15693_3ErrorNone; +} + +// Extended ISO15693-3 standard commands handler table (NULL = no extension) +static const Iso15693_3ExtensionHandlerTable slix_iso15693_extension_handler_table = { + .mandatory = + { + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_inventory_extension_handler, + (Iso15693_3ExtensionHandler)NULL // ISO15693_3_CMD_STAY_QUIET (0x02U) + }, + .optional = + { + (Iso15693_3ExtensionHandler)slix_iso15693_3_read_block_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_write_block_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_lock_block_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_read_multi_block_extension_handler, + (Iso15693_3ExtensionHandler) + slix_listener_iso15693_3_write_multi_block_extension_handler, + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_SELECT (0x25U) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_RESET_TO_READY (0x26U) + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_write_lock_afi_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_write_lock_afi_extension_handler, + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_WRITE_DSFID (0x29U) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_LOCK_DSFID (0x2AU) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_GET_SYS_INFO (0x2BU) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_GET_BLOCKS_SECURITY (0x2CU) + }, +}; + +SlixError slix_listener_init_iso15693_3_extensions(SlixListener* instance) { + iso15693_3_listener_set_extension_handler_table( + instance->iso15693_3_listener, &slix_iso15693_extension_handler_table, instance); + return SlixErrorNone; +} + +SlixError slix_listener_process_request(SlixListener* instance, const BitBuffer* rx_buffer) { + SlixError error = SlixErrorNone; + + do { + typedef struct { + uint8_t flags; + uint8_t command; + uint8_t manufacturer; + uint8_t data[]; + } SlixRequestLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer); + + if(buf_size < sizeof(SlixRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixRequestLayout* request = + (const SlixRequestLayout*)bit_buffer_get_data(rx_buffer); + + const bool addressed_mode = instance->iso15693_3_listener->session_state.addressed; + + const size_t uid_field_size = addressed_mode ? ISO15693_3_UID_SIZE : 0; + const size_t buf_size_min = sizeof(SlixRequestLayout) + uid_field_size; + + if(buf_size < buf_size_min) { + error = SlixErrorFormat; + break; + } + + if(addressed_mode) { + if(!iso15693_3_is_equal_uid(instance->data->iso15693_3_data, request->data)) { + error = SlixErrorUidMismatch; + break; + } + } + + const uint8_t command = request->command; + const bool is_valid_slix_command = command >= SLIX_CMD_CUSTOM_START && + command < SLIX_CMD_CUSTOM_END; + if(!is_valid_slix_command) { + error = SlixErrorNotSupported; + break; + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_NONE); + + const uint8_t* request_data = &request->data[uid_field_size]; + const size_t request_data_size = buf_size - buf_size_min; + + SlixRequestHandler handler = slix_request_handler_table[command - SLIX_CMD_CUSTOM_START]; + error = handler(instance, request_data, request_data_size, request->flags); + + // It's a trick! Send no reply. + if(error == SlixErrorFormat || error == SlixErrorWrongPassword || + error == SlixErrorNotSupported) + break; + + if(error != SlixErrorNone) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_ERROR); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_ERROR_UNKNOWN); + } + + const Iso15693_3Error iso15693_error = + iso15693_3_listener_send_frame(instance->iso15693_3_listener, instance->tx_buffer); + error = slix_process_iso15693_3_error(iso15693_error); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/slix/slix_listener_i.h b/lib/nfc/protocols/slix/slix_listener_i.h new file mode 100644 index 0000000000..ce059c5a67 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener_i.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "slix_listener.h" +#include "slix_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t random; + bool password_match[SlixPasswordTypeCount]; +} SlixListenerSessionState; + +struct SlixListener { + Iso15693_3Listener* iso15693_3_listener; + SlixData* data; + SlixListenerSessionState session_state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + SlixListenerEvent slix_event; + SlixListenerEventData slix_event_data; + NfcGenericCallback callback; + void* context; +}; + +SlixError slix_listener_init_iso15693_3_extensions(SlixListener* instance); + +SlixError slix_listener_process_request(SlixListener* instance, const BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c new file mode 100644 index 0000000000..9731bfc6b8 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -0,0 +1,159 @@ +#include "slix_poller_i.h" + +#include + +#include + +#define TAG "SlixPoller" + +#define SLIX_POLLER_BUF_SIZE (64U) + +typedef NfcCommand (*SlixPollerStateHandler)(SlixPoller* instance); + +const SlixData* slix_poller_get_data(SlixPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static SlixPoller* slix_poller_alloc(Iso15693_3Poller* iso15693_3_poller) { + SlixPoller* instance = malloc(sizeof(SlixPoller)); + instance->iso15693_3_poller = iso15693_3_poller; + instance->data = slix_alloc(); + instance->tx_buffer = bit_buffer_alloc(SLIX_POLLER_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(SLIX_POLLER_BUF_SIZE); + + instance->slix_event.data = &instance->slix_event_data; + + instance->general_event.protocol = NfcProtocolSlix; + instance->general_event.event_data = &instance->slix_event; + instance->general_event.instance = instance; + + return instance; +} + +static void slix_poller_free(SlixPoller* instance) { + furi_assert(instance); + + slix_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { + iso15693_3_copy( + instance->data->iso15693_3_data, iso15693_3_poller_get_data(instance->iso15693_3_poller)); + + instance->poller_state = SlixPollerStateGetNxpSysInfo; + return NfcCommandContinue; +} + +static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { + instance->error = + slix_poller_async_get_nxp_system_info(instance, &instance->data->system_info); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReadSignature; + } else { + instance->poller_state = SlixPollerStateError; + } + + return NfcCommandContinue; +} + +static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { + instance->error = slix_poller_async_read_signature(instance, &instance->data->signature); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReady; + } else { + instance->poller_state = SlixPollerStateError; + } + + return NfcCommandContinue; +} + +static NfcCommand slix_poller_handler_error(SlixPoller* instance) { + instance->slix_event_data.error = instance->error; + instance->slix_event.type = SlixPollerEventTypeError; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->poller_state = SlixPollerStateIdle; + return command; +} + +static NfcCommand slix_poller_handler_ready(SlixPoller* instance) { + instance->slix_event.type = SlixPollerEventTypeReady; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const SlixPollerStateHandler slix_poller_state_handler[SlixPollerStateNum] = { + [SlixPollerStateIdle] = slix_poller_handler_idle, + [SlixPollerStateError] = slix_poller_handler_error, + [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nfc_system_info, + [SlixPollerStateReadSignature] = slix_poller_handler_read_signature, + [SlixPollerStateReady] = slix_poller_handler_ready, +}; + +static void + slix_poller_set_callback(SlixPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand slix_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso15693_3); + + SlixPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + furi_assert(iso15693_3_event); + + NfcCommand command = NfcCommandContinue; + + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + command = slix_poller_state_handler[instance->poller_state](instance); + } else if(iso15693_3_event->type == Iso15693_3PollerEventTypeError) { + instance->slix_event.type = SlixPollerEventTypeError; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool slix_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso15693_3); + + SlixPoller* instance = context; + furi_assert(instance); + + const Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + furi_assert(iso15693_3_event); + iso15693_3_copy( + instance->data->iso15693_3_data, iso15693_3_poller_get_data(instance->iso15693_3_poller)); + + bool protocol_detected = false; + + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + if(slix_get_type(instance->data) < SlixTypeCount) { + SlixSystemInfo system_info = {}; + SlixError error = slix_poller_async_get_nxp_system_info(instance, &system_info); + protocol_detected = (error == SlixErrorNone); + } + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_slix = { + .alloc = (NfcPollerAlloc)slix_poller_alloc, + .free = (NfcPollerFree)slix_poller_free, + .set_callback = (NfcPollerSetCallback)slix_poller_set_callback, + .run = (NfcPollerRun)slix_poller_run, + .detect = (NfcPollerDetect)slix_poller_detect, + .get_data = (NfcPollerGetData)slix_poller_get_data, +}; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h new file mode 100644 index 0000000000..f4c7214de4 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include "slix.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SlixPoller SlixPoller; + +typedef enum { + SlixPollerEventTypeError, + SlixPollerEventTypeReady, +} SlixPollerEventType; + +typedef struct { + SlixError error; +} SlixPollerEventData; + +typedef struct { + SlixPollerEventType type; + SlixPollerEventData* data; +} SlixPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_poller_defs.h b/lib/nfc/protocols/slix/slix_poller_defs.h new file mode 100644 index 0000000000..df8f298f0a --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_slix; diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c new file mode 100644 index 0000000000..a36e7694a4 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -0,0 +1,69 @@ +#include "slix_poller_i.h" + +#include + +#include "slix_i.h" + +#define TAG "SlixPoller" + +static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte( + instance->tx_buffer, + ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI | + ISO15693_3_REQ_FLAG_T4_ADDRESSED); + bit_buffer_append_byte(instance->tx_buffer, command); + bit_buffer_append_byte(instance->tx_buffer, SLIX_NXP_MANUFACTURER_CODE); + + iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); +} + +SlixError slix_poller_send_frame( + SlixPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const Iso15693_3Error iso15693_3_error = + iso15693_3_poller_send_frame(instance->iso15693_3_poller, tx_buffer, rx_buffer, fwt); + return slix_process_iso15693_3_error(iso15693_3_error); +} + +SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data) { + furi_assert(instance); + furi_assert(data); + + slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(error != SlixErrorNone) break; + error = slix_get_nxp_system_info_response_parse(instance->data, instance->rx_buffer); + } while(false); + + return error; +} + +SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data) { + furi_assert(instance); + furi_assert(data); + + slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC * 2); + if(error != SlixErrorNone) break; + error = slix_read_signature_response_parse(instance->data->signature, instance->rx_buffer); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h new file mode 100644 index 0000000000..c6a8a3c33e --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "slix_poller.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SlixPollerStateIdle, + SlixPollerStateGetNxpSysInfo, + SlixPollerStateReadSignature, + SlixPollerStateReady, + SlixPollerStateError, + SlixPollerStateNum, +} SlixPollerState; + +struct SlixPoller { + Iso15693_3Poller* iso15693_3_poller; + SlixData* data; + SlixPollerState poller_state; + SlixError error; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + SlixPollerEventData slix_event_data; + SlixPollerEvent slix_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +SlixError slix_poller_send_frame( + SlixPoller* instance, + const BitBuffer* tx_data, + BitBuffer* rx_data, + uint32_t fwt); + +SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data); + +SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb.c b/lib/nfc/protocols/st25tb/st25tb.c new file mode 100644 index 0000000000..d3fac7eeac --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb.c @@ -0,0 +1,234 @@ +#include "st25tb.h" + +#include "core/string.h" +#include "flipper_format.h" +#include + +#include +#include + +#define ST25TB_PROTOCOL_NAME "ST25TB" +#define ST25TB_TYPE_KEY "ST25TB Type" +#define ST25TB_BLOCK_KEY "Block %d" +#define ST25TB_SYSTEM_BLOCK_KEY "System OTP Block" + +typedef struct { + uint8_t blocks_total; + bool has_otp; + const char* full_name; + const char* type_name; +} St25tbFeatures; + +static const St25tbFeatures st25tb_features[St25tbTypeNum] = { + [St25tbType512At] = + { + .blocks_total = 16, + .has_otp = false, + .full_name = "ST25TB512-AT/SRI512", + .type_name = "512AT", + }, + [St25tbType512Ac] = + { + .blocks_total = 16, + .has_otp = true, + .full_name = "ST25TB512-AC/SRT512", + .type_name = "512AC", + }, + [St25tbTypeX512] = + { + .blocks_total = 16, + .has_otp = true, + .full_name = "SRIX512", + .type_name = "X512", + }, + [St25tbType02k] = + { + .blocks_total = 64, + .has_otp = true, + .full_name = "ST25TB02K/SRI2K", + .type_name = "2K", + }, + [St25tbType04k] = + { + .blocks_total = 128, + .has_otp = true, + .full_name = "ST25TB04K/SRI4K", + .type_name = "4K", + }, + [St25tbTypeX4k] = + { + .blocks_total = 128, + .has_otp = true, + .full_name = "SRIX4K", + .type_name = "X4K", + }, +}; + +const NfcDeviceBase nfc_device_st25tb = { + .protocol_name = ST25TB_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)st25tb_alloc, + .free = (NfcDeviceFree)st25tb_free, + .reset = (NfcDeviceReset)st25tb_reset, + .copy = (NfcDeviceCopy)st25tb_copy, + .verify = (NfcDeviceVerify)st25tb_verify, + .load = (NfcDeviceLoad)st25tb_load, + .save = (NfcDeviceSave)st25tb_save, + .is_equal = (NfcDeviceEqual)st25tb_is_equal, + .get_name = (NfcDeviceGetName)st25tb_get_device_name, + .get_uid = (NfcDeviceGetUid)st25tb_get_uid, + .set_uid = (NfcDeviceSetUid)st25tb_set_uid, + .get_base_data = (NfcDeviceGetBaseData)st25tb_get_base_data, +}; + +St25tbData* st25tb_alloc() { + St25tbData* data = malloc(sizeof(St25tbData)); + return data; +} + +void st25tb_free(St25tbData* data) { + furi_assert(data); + + free(data); +} + +void st25tb_reset(St25tbData* data) { + memset(data, 0, sizeof(St25tbData)); +} + +void st25tb_copy(St25tbData* data, const St25tbData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool st25tb_verify(St25tbData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, ST25TB_PROTOCOL_NAME); +} + +bool st25tb_load(St25tbData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + FuriString* temp_str = furi_string_alloc(); + + do { + if(version < NFC_UNIFIED_FORMAT_VERSION) break; + if(!flipper_format_read_string(ff, ST25TB_TYPE_KEY, temp_str)) break; + + bool type_parsed = false; + for(size_t i = 0; i < St25tbTypeNum; i++) { + if(furi_string_equal_str(temp_str, st25tb_features[i].type_name)) { + data->type = i; + type_parsed = true; + } + } + if(!type_parsed) break; + + bool blocks_parsed = true; + for(uint8_t block = 0; block < st25tb_features[data->type].blocks_total; block++) { + furi_string_printf(temp_str, ST25TB_BLOCK_KEY, block); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(temp_str), (uint8_t*)&data->blocks[block], 4)) { + blocks_parsed = false; + break; + } + } + if(!blocks_parsed) break; + + if(!flipper_format_read_hex( + ff, ST25TB_SYSTEM_BLOCK_KEY, (uint8_t*)&data->system_otp_block, 4)) + break; + + parsed = true; + } while(false); + + furi_string_free(temp_str); + + return parsed; +} + +bool st25tb_save(const St25tbData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, ST25TB_PROTOCOL_NAME " specific data")) break; + if(!flipper_format_write_string_cstr( + ff, ST25TB_TYPE_KEY, st25tb_features[data->type].type_name)) + break; + + bool blocks_saved = true; + for(uint8_t block = 0; block < st25tb_features[data->type].blocks_total; block++) { + furi_string_printf(temp_str, ST25TB_BLOCK_KEY, block); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), (uint8_t*)&data->blocks[block], 4)) { + blocks_saved = false; + break; + } + } + if(!blocks_saved) break; + + if(!flipper_format_write_hex( + ff, ST25TB_SYSTEM_BLOCK_KEY, (uint8_t*)&data->system_otp_block, 4)) + break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + return saved; +} + +bool st25tb_is_equal(const St25tbData* data, const St25tbData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(St25tbData)) == 0; +} + +uint8_t st25tb_get_block_count(St25tbType type) { + return st25tb_features[type].blocks_total; +} + +const char* st25tb_get_device_name(const St25tbData* data, NfcDeviceNameType name_type) { + furi_assert(data); + furi_assert(data->type < St25tbTypeNum); + + if(name_type == NfcDeviceNameTypeFull) { + return st25tb_features[data->type].full_name; + } else { + return st25tb_features[data->type].type_name; + } +} + +const uint8_t* st25tb_get_uid(const St25tbData* data, size_t* uid_len) { + furi_assert(data); + + if(uid_len) { + *uid_len = ST25TB_UID_SIZE; + } + + return data->uid; +} + +bool st25tb_set_uid(St25tbData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + const bool uid_valid = uid_len == ST25TB_UID_SIZE; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + } + + return uid_valid; +} + +St25tbData* st25tb_get_base_data(const St25tbData* data) { + UNUSED(data); + furi_crash("No base data"); +} diff --git a/lib/nfc/protocols/st25tb/st25tb.h b/lib/nfc/protocols/st25tb/st25tb.h new file mode 100644 index 0000000000..1edb296ca1 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ST25TB_UID_SIZE (8U) + +//#define ST25TB_FDT_FC (4205U) +#define ST25TB_FDT_FC (8494U) +#define ST25TB_GUARD_TIME_US (5000U) +#define ST25TB_POLL_POLL_MIN_US (1280U) + +#define ST25TB_MAX_BLOCKS (128U) +#define ST25TB_SYSTEM_OTP_BLOCK (0xFFU) +#define ST25TB_BLOCK_SIZE (4U) + +typedef enum { + St25tbErrorNone, + St25tbErrorNotPresent, + St25tbErrorColResFailed, + St25tbErrorBufferOverflow, + St25tbErrorCommunication, + St25tbErrorFieldOff, + St25tbErrorWrongCrc, + St25tbErrorTimeout, +} St25tbError; + +typedef enum { + St25tbType512At, + St25tbType512Ac, + St25tbTypeX512, + St25tbType02k, + St25tbType04k, + St25tbTypeX4k, + St25tbTypeNum, +} St25tbType; + +typedef struct { + uint8_t uid[ST25TB_UID_SIZE]; + St25tbType type; + uint32_t blocks[ST25TB_MAX_BLOCKS]; + uint32_t system_otp_block; + uint8_t chip_id; +} St25tbData; + +extern const NfcDeviceBase nfc_device_st25tb; + +St25tbData* st25tb_alloc(); + +void st25tb_free(St25tbData* data); + +void st25tb_reset(St25tbData* data); + +void st25tb_copy(St25tbData* data, const St25tbData* other); + +bool st25tb_verify(St25tbData* data, const FuriString* device_type); + +bool st25tb_load(St25tbData* data, FlipperFormat* ff, uint32_t version); + +bool st25tb_save(const St25tbData* data, FlipperFormat* ff); + +bool st25tb_is_equal(const St25tbData* data, const St25tbData* other); + +uint8_t st25tb_get_block_count(St25tbType type); + +const char* st25tb_get_device_name(const St25tbData* data, NfcDeviceNameType name_type); + +const uint8_t* st25tb_get_uid(const St25tbData* data, size_t* uid_len); + +bool st25tb_set_uid(St25tbData* data, const uint8_t* uid, size_t uid_len); + +St25tbData* st25tb_get_base_data(const St25tbData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c new file mode 100644 index 0000000000..df659a2051 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -0,0 +1,123 @@ +#include "protocols/nfc_protocol.h" +#include "protocols/st25tb/st25tb.h" +#include "st25tb_poller_i.h" + +#include + +#include + +#define TAG "ST25TBPoller" + +const St25tbData* st25tb_poller_get_data(St25tbPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static St25tbPoller* st25tb_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + St25tbPoller* instance = malloc(sizeof(St25tbPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); + + // RF configuration is the same as 14b + nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443b); + nfc_set_guard_time_us(instance->nfc, ST25TB_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ST25TB_FDT_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ST25TB_POLL_POLL_MIN_US); + instance->data = st25tb_alloc(); + + instance->st25tb_event.data = &instance->st25tb_event_data; + instance->general_event.protocol = NfcProtocolSt25tb; + instance->general_event.event_data = &instance->st25tb_event; + instance->general_event.instance = instance; + + return instance; +} + +static void st25tb_poller_free(St25tbPoller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + st25tb_free(instance->data); + free(instance); +} + +static void + st25tb_poller_set_callback(St25tbPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + St25tbPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != St25tbPollerStateActivated) { + St25tbError error = st25tb_poller_async_activate(instance, instance->data); + + if(error == St25tbErrorNone) { + instance->st25tb_event.type = St25tbPollerEventTypeReady; + instance->st25tb_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->st25tb_event.type = St25tbPollerEventTypeError; + instance->st25tb_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->st25tb_event.type = St25tbPollerEventTypeReady; + instance->st25tb_event_data.error = St25tbErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool st25tb_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + St25tbPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == St25tbPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + St25tbError error = st25tb_poller_async_initiate(instance, NULL); + protocol_detected = (error == St25tbErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_st25tb = { + .alloc = (NfcPollerAlloc)st25tb_poller_alloc, + .free = (NfcPollerFree)st25tb_poller_free, + .set_callback = (NfcPollerSetCallback)st25tb_poller_set_callback, + .run = (NfcPollerRun)st25tb_poller_run, + .detect = (NfcPollerDetect)st25tb_poller_detect, + .get_data = (NfcPollerGetData)st25tb_poller_get_data, +}; diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.h b/lib/nfc/protocols/st25tb/st25tb_poller.h new file mode 100644 index 0000000000..a521b6d5b4 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller.h @@ -0,0 +1,30 @@ +#pragma once + +#include "st25tb.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct St25tbPoller St25tbPoller; + +typedef enum { + St25tbPollerEventTypeError, + St25tbPollerEventTypeReady, +} St25tbPollerEventType; + +typedef struct { + St25tbError error; +} St25tbPollerEventData; + +typedef struct { + St25tbPollerEventType type; + St25tbPollerEventData* data; +} St25tbPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_defs.h b/lib/nfc/protocols/st25tb/st25tb_poller_defs.h new file mode 100644 index 0000000000..86b68024f3 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase nfc_poller_st25tb; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.c b/lib/nfc/protocols/st25tb/st25tb_poller_i.c new file mode 100644 index 0000000000..bcbc69382a --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.c @@ -0,0 +1,297 @@ +#include "st25tb_poller_i.h" + +#include "bit_buffer.h" +#include "core/core_defines.h" +#include "protocols/st25tb/st25tb.h" +#include + +#define TAG "ST25TBPoller" + +static St25tbError st25tb_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return St25tbErrorNone; + case NfcErrorTimeout: + return St25tbErrorTimeout; + default: + return St25tbErrorNotPresent; + } +} + +static St25tbError st25tb_poller_prepare_trx(St25tbPoller* instance) { + furi_assert(instance); + + if(instance->state == St25tbPollerStateIdle) { + return st25tb_poller_async_activate(instance, NULL); + } + + return St25tbErrorNone; +} + +static St25tbError st25tb_poller_frame_exchange( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert( + tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO14443_CRC_SIZE); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeB, instance->tx_buffer); + + St25tbError ret = St25tbErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + FURI_LOG_D(TAG, "error during trx: %d", error); + ret = st25tb_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeB, instance->rx_buffer)) { + ret = St25tbErrorWrongCrc; + break; + } + + iso14443_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +St25tbType st25tb_get_type_from_uid(const uint8_t uid[ST25TB_UID_SIZE]) { + switch(uid[2] >> 2) { + case 0x0: + case 0x3: + return St25tbTypeX4k; + case 0x4: + return St25tbTypeX512; + case 0x6: + return St25tbType512Ac; + case 0x7: + return St25tbType04k; + case 0xc: + return St25tbType512At; + case 0xf: + return St25tbType02k; + default: + furi_crash("unsupported st25tb type"); + } +} + +St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id) { + // Send Initiate() + furi_assert(instance); + furi_assert(instance->nfc); + + instance->state = St25tbPollerStateInitiateInProgress; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + bit_buffer_append_byte(instance->tx_buffer, 0x06); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + St25tbError ret; + do { + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { + FURI_LOG_D(TAG, "Unexpected Initiate response size"); + ret = St25tbErrorCommunication; + break; + } + if(chip_id) { + *chip_id = bit_buffer_get_byte(instance->rx_buffer, 0); + } + } while(false); + + return ret; +} + +St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + st25tb_reset(data); + + St25tbError ret; + + do { + ret = st25tb_poller_async_initiate(instance, &data->chip_id); + if(ret != St25tbErrorNone) { + break; + } + + instance->state = St25tbPollerStateActivationInProgress; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Select(Chip_ID), let's just assume that collisions won't ever happen :D + bit_buffer_append_byte(instance->tx_buffer, 0x0E); + bit_buffer_append_byte(instance->tx_buffer, data->chip_id); + + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + instance->state = St25tbPollerStateActivationFailed; + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { + FURI_LOG_D(TAG, "Unexpected Select response size"); + instance->state = St25tbPollerStateActivationFailed; + ret = St25tbErrorCommunication; + break; + } + + if(bit_buffer_get_byte(instance->rx_buffer, 0) != data->chip_id) { + FURI_LOG_D(TAG, "ChipID mismatch"); + instance->state = St25tbPollerStateActivationFailed; + ret = St25tbErrorColResFailed; + break; + } + instance->state = St25tbPollerStateActivated; + + ret = st25tb_poller_async_get_uid(instance, data->uid); + if(ret != St25tbErrorNone) { + instance->state = St25tbPollerStateActivationFailed; + break; + } + data->type = st25tb_get_type_from_uid(data->uid); + + bool read_blocks = true; + for(uint8_t i = 0; i < st25tb_get_block_count(data->type); i++) { + ret = st25tb_poller_async_read_block(instance, &data->blocks[i], i); + if(ret != St25tbErrorNone) { + read_blocks = false; + break; + } + } + if(!read_blocks) { + break; + } + ret = st25tb_poller_async_read_block( + instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + } while(false); + + return ret; +} + +St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]) { + furi_assert(instance); + furi_assert(instance->nfc); + + St25tbError ret; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte(instance->tx_buffer, 0x0B); + + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_UID_SIZE) { + FURI_LOG_D(TAG, "Unexpected Get_UID() response size"); + instance->state = St25tbPollerStateActivationFailed; + ret = St25tbErrorCommunication; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, uid, ST25TB_UID_SIZE); + FURI_SWAP(uid[0], uid[7]); + FURI_SWAP(uid[1], uid[6]); + FURI_SWAP(uid[2], uid[5]); + FURI_SWAP(uid[3], uid[4]); + } while(false); + return ret; +} + +St25tbError + st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(block); + furi_assert( + (block_number <= st25tb_get_block_count(instance->data->type)) || + block_number == ST25TB_SYSTEM_OTP_BLOCK); + FURI_LOG_D(TAG, "reading block %d", block_number); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Read_block(Addr) + bit_buffer_append_byte(instance->tx_buffer, 0x08); + bit_buffer_append_byte(instance->tx_buffer, block_number); + St25tbError ret; + do { + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_BLOCK_SIZE) { + FURI_LOG_D(TAG, "Unexpected Read_block(Addr) response size"); + ret = St25tbErrorCommunication; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, block, ST25TB_BLOCK_SIZE); + FURI_LOG_D(TAG, "read result: %08lX", *block); + } while(false); + + return ret; +} + +St25tbError st25tb_poller_halt(St25tbPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Completion() + bit_buffer_append_byte(instance->tx_buffer, 0x0F); + + St25tbError ret; + + do { + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorTimeout) { + break; + } + + instance->state = St25tbPollerStateIdle; + } while(false); + + return ret; +} + +St25tbError st25tb_poller_send_frame( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + St25tbError ret; + + do { + ret = st25tb_poller_prepare_trx(instance); + if(ret != St25tbErrorNone) break; + + ret = st25tb_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.h b/lib/nfc/protocols/st25tb/st25tb_poller_i.h new file mode 100644 index 0000000000..7f38f2d45b --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.h @@ -0,0 +1,56 @@ +#pragma once + +#include "protocols/st25tb/st25tb.h" +#include "st25tb_poller.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ST25TB_POLLER_MAX_BUFFER_SIZE (16U) + +typedef enum { + St25tbPollerStateIdle, + St25tbPollerStateInitiateInProgress, + St25tbPollerStateInitiateFailed, + St25tbPollerStateActivationInProgress, + St25tbPollerStateActivationFailed, + St25tbPollerStateActivated, +} St25tbPollerState; + +struct St25tbPoller { + Nfc* nfc; + St25tbPollerState state; + St25tbData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + St25tbPollerEvent st25tb_event; + St25tbPollerEventData st25tb_event_data; + NfcGenericCallback callback; + void* context; +}; + +const St25tbData* st25tb_poller_get_data(St25tbPoller* instance); + +St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id); + +St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data); + +St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]); + +St25tbError + st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); + +St25tbError st25tb_poller_halt(St25tbPoller* instance); + +St25tbError st25tb_poller_send_frame( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/signal_reader/SConscript b/lib/signal_reader/SConscript new file mode 100644 index 0000000000..ea73144201 --- /dev/null +++ b/lib/signal_reader/SConscript @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/signal_reader", + ], + SDK_HEADERS=[ + File("signal_reader.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="signal_reader") +libenv.ApplyLibFlags() +libenv.Append(CCFLAGS=["-O3", "-funroll-loops", "-Ofast"]) + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c new file mode 100644 index 0000000000..a398516c92 --- /dev/null +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -0,0 +1,300 @@ +#include "iso15693_parser.h" + +#include + +#include + +#define ISO15693_PARSER_SIGNAL_READER_BUFF_SIZE (2) +#define ISO15693_PARSER_BITSTREAM_BUFF_SIZE (32) +#define ISO15693_PARSER_BITRATE_F64MHZ (603U) + +#define TAG "Iso15693Parser" + +typedef enum { + Iso15693ParserStateParseSoF, + Iso15693ParserStateParseFrame, + Iso15693ParserStateFail, +} Iso15693ParserState; + +typedef enum { + Iso15693ParserMode1OutOf4, + Iso15693ParserMode1OutOf256, + + Iso15693ParserModeNum, +} Iso15693ParserMode; + +struct Iso15693Parser { + Iso15693ParserState state; + Iso15693ParserMode mode; + + SignalReader* signal_reader; + + uint8_t bitstream_buff[ISO15693_PARSER_BITSTREAM_BUFF_SIZE]; + size_t bitstream_idx; + uint8_t last_byte; + + bool signal_detected; + bool bit_offset_calculated; + uint8_t bit_offset; + size_t byte_idx; + size_t bytes_to_process; + + uint8_t next_byte; + uint16_t next_byte_part; + bool zero_found; + + BitBuffer* parsed_frame; + bool eof_received; + bool frame_parsed; + + Iso15693ParserCallback callback; + void* context; +}; + +typedef enum { + Iso15693ParserCommandProcessed, + Iso15693ParserCommandWaitData, + Iso15693ParserCommandFail, + Iso15693ParserCommandSuccess, +} Iso15693ParserCommand; + +typedef Iso15693ParserCommand (*Iso15693ParserStateHandler)(Iso15693Parser* instance); + +Iso15693Parser* iso15693_parser_alloc(const GpioPin* pin, size_t max_frame_size) { + Iso15693Parser* instance = malloc(sizeof(Iso15693Parser)); + instance->parsed_frame = bit_buffer_alloc(max_frame_size); + + instance->signal_reader = signal_reader_alloc(pin, ISO15693_PARSER_SIGNAL_READER_BUFF_SIZE); + signal_reader_set_sample_rate( + instance->signal_reader, SignalReaderTimeUnit64Mhz, ISO15693_PARSER_BITRATE_F64MHZ); + signal_reader_set_pull(instance->signal_reader, GpioPullDown); + signal_reader_set_polarity(instance->signal_reader, SignalReaderPolarityInverted); + signal_reader_set_trigger(instance->signal_reader, SignalReaderTriggerRisingFallingEdge); + + return instance; +} + +void iso15693_parser_free(Iso15693Parser* instance) { + furi_assert(instance); + + bit_buffer_free(instance->parsed_frame); + signal_reader_free(instance->signal_reader); + free(instance); +} + +void iso15693_parser_reset(Iso15693Parser* instance) { + furi_assert(instance); + + instance->state = Iso15693ParserStateParseSoF; + instance->mode = Iso15693ParserMode1OutOf4; + memset(instance->bitstream_buff, 0x00, sizeof(instance->bitstream_buff)); + instance->bitstream_idx = 0; + + instance->next_byte = 0; + instance->next_byte_part = 0; + + instance->bit_offset = 0; + instance->byte_idx = 0; + instance->bytes_to_process = 0; + instance->signal_detected = false; + instance->bit_offset_calculated = false; + + instance->last_byte = 0x00; + instance->zero_found = false; + instance->eof_received = false; + + bit_buffer_reset(instance->parsed_frame); + instance->frame_parsed = false; +} + +static void signal_reader_callback(SignalReaderEvent event, void* context) { + furi_assert(context); + furi_assert(event.data->data); + furi_assert(event.data->len == ISO15693_PARSER_SIGNAL_READER_BUFF_SIZE / 2); + + Iso15693Parser* instance = context; + furi_assert(instance->callback); + + const uint8_t sof_1_out_of_4 = 0x21; + const uint8_t sof_1_out_of_256 = 0x81; + const uint8_t eof_single = 0x01; + const uint8_t eof = 0x04; + + if(instance->state == Iso15693ParserStateParseSoF) { + if(event.data->data[0] == sof_1_out_of_4) { + instance->mode = Iso15693ParserMode1OutOf4; + instance->state = Iso15693ParserStateParseFrame; + } else if(event.data->data[0] == sof_1_out_of_256) { + instance->mode = Iso15693ParserMode1OutOf256; + instance->state = Iso15693ParserStateParseFrame; + } else if(event.data->data[0] == eof_single) { + instance->eof_received = true; + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } else { + instance->state = Iso15693ParserStateFail; + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } + } else { + if(instance->mode == Iso15693ParserMode1OutOf4) { + if(event.data->data[0] == eof) { + instance->eof_received = true; + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } else { + instance->bitstream_buff[instance->bytes_to_process] = event.data->data[0]; + instance->bytes_to_process++; + if(instance->bytes_to_process == ISO15693_PARSER_BITSTREAM_BUFF_SIZE) { + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } + } + } else { + instance->bitstream_buff[instance->bytes_to_process] = event.data->data[0]; + instance->bytes_to_process++; + if(instance->bytes_to_process == ISO15693_PARSER_BITSTREAM_BUFF_SIZE) { + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } + } + } +} + +static void iso15693_parser_start_signal_reader(Iso15693Parser* instance) { + iso15693_parser_reset(instance); + signal_reader_start(instance->signal_reader, signal_reader_callback, instance); +} + +void iso15693_parser_start( + Iso15693Parser* instance, + Iso15693ParserCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; + iso15693_parser_start_signal_reader(instance); +} + +void iso15693_parser_stop(Iso15693Parser* instance) { + furi_assert(instance); + + signal_reader_stop(instance->signal_reader); +} + +static Iso15693ParserCommand iso15693_parser_parse_1_out_of_4(Iso15693Parser* instance) { + Iso15693ParserCommand command = Iso15693ParserCommandWaitData; + const uint8_t bit_patterns_1_out_of_4[] = {0x02, 0x08, 0x20, 0x80}; + + for(size_t i = 0; i < instance->bytes_to_process; i++) { + // Check next pattern + size_t j = 0; + for(j = 0; j < COUNT_OF(bit_patterns_1_out_of_4); j++) { + if(instance->bitstream_buff[i] == bit_patterns_1_out_of_4[j]) { + instance->next_byte |= j << (instance->next_byte_part * 2); + instance->next_byte_part++; + if(instance->next_byte_part == 4) { + instance->next_byte_part = 0; + bit_buffer_append_byte(instance->parsed_frame, instance->next_byte); + instance->next_byte = 0; + } + break; + } + } + if(j == COUNT_OF(bit_patterns_1_out_of_4)) { + command = Iso15693ParserCommandFail; + break; + } + } + + if(command != Iso15693ParserCommandFail) { + if(instance->eof_received) { + command = Iso15693ParserCommandSuccess; + instance->frame_parsed = true; + } + } + + instance->bytes_to_process = 0; + + return command; +} + +static Iso15693ParserCommand iso15693_parser_parse_1_out_of_256(Iso15693Parser* instance) { + Iso15693ParserCommand command = Iso15693ParserCommandWaitData; + const uint8_t eof = 0x04; + + for(size_t i = instance->byte_idx; i < instance->bytes_to_process; i++) { + // Check EoF + if(instance->next_byte_part == 0) { + if(instance->bitstream_buff[i] == eof) { + instance->frame_parsed = true; + command = Iso15693ParserCommandSuccess; + break; + } + } + + if(instance->zero_found) { + if(instance->bitstream_buff[i] != 0x00) { + command = Iso15693ParserCommandFail; + break; + } + } else { + if(instance->bitstream_buff[i] != 0x00) { + for(size_t j = 0; j < 8; j++) { + if(FURI_BIT(instance->bitstream_buff[i], j) == 1) { + bit_buffer_append_byte( + instance->parsed_frame, instance->next_byte_part * 4 + j / 2); + } + } + } + } + instance->next_byte_part = (instance->next_byte_part + 1) % 64; + } + instance->bytes_to_process = 0; + instance->byte_idx = 0; + + return command; +} + +static const Iso15693ParserStateHandler iso15693_parser_state_handlers[Iso15693ParserModeNum] = { + [Iso15693ParserMode1OutOf4] = iso15693_parser_parse_1_out_of_4, + [Iso15693ParserMode1OutOf256] = iso15693_parser_parse_1_out_of_256, +}; + +bool iso15693_parser_run(Iso15693Parser* instance) { + if(instance->state == Iso15693ParserStateFail) { + iso15693_parser_stop(instance); + iso15693_parser_start_signal_reader(instance); + } else if((instance->state == Iso15693ParserStateParseSoF) && (instance->eof_received)) { + instance->frame_parsed = true; + } else if(instance->bytes_to_process) { + Iso15693ParserCommand command = Iso15693ParserCommandProcessed; + while(command == Iso15693ParserCommandProcessed) { + command = iso15693_parser_state_handlers[instance->mode](instance); + } + + if(command == Iso15693ParserCommandFail) { + iso15693_parser_stop(instance); + iso15693_parser_start_signal_reader(instance); + FURI_LOG_D(TAG, "Frame parse failed"); + } + } + + return instance->frame_parsed; +} + +size_t iso15693_parser_get_data_size_bytes(Iso15693Parser* instance) { + furi_assert(instance); + + return bit_buffer_get_size_bytes(instance->parsed_frame); +} + +void iso15693_parser_get_data( + Iso15693Parser* instance, + uint8_t* buff, + size_t buff_size, + size_t* data_bits) { + furi_assert(instance); + furi_assert(buff); + furi_assert(data_bits); + + bit_buffer_write_bytes(instance->parsed_frame, buff, buff_size); + *data_bits = bit_buffer_get_size(instance->parsed_frame); +} diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.h b/lib/signal_reader/parsers/iso15693/iso15693_parser.h new file mode 100644 index 0000000000..3017a96d79 --- /dev/null +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../../signal_reader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693Parser Iso15693Parser; + +typedef enum { + Iso15693ParserEventDataReceived, +} Iso15693ParserEvent; + +typedef void (*Iso15693ParserCallback)(Iso15693ParserEvent event, void* context); + +Iso15693Parser* iso15693_parser_alloc(const GpioPin* pin, size_t max_frame_size); + +void iso15693_parser_free(Iso15693Parser* instance); + +void iso15693_parser_reset(Iso15693Parser* instance); + +void iso15693_parser_start( + Iso15693Parser* instance, + Iso15693ParserCallback callback, + void* context); + +void iso15693_parser_stop(Iso15693Parser* instance); + +bool iso15693_parser_run(Iso15693Parser* instance); + +size_t iso15693_parser_get_data_size_bytes(Iso15693Parser* instance); + +void iso15693_parser_get_data( + Iso15693Parser* instance, + uint8_t* buff, + size_t buff_size, + size_t* data_bits); + +#ifdef __cplusplus +} +#endif diff --git a/lib/signal_reader/signal_reader.c b/lib/signal_reader/signal_reader.c new file mode 100644 index 0000000000..7c4d0bae7e --- /dev/null +++ b/lib/signal_reader/signal_reader.c @@ -0,0 +1,317 @@ +#include "signal_reader.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define SIGNAL_READER_DMA DMA2 + +#define SIGNAL_READER_CAPTURE_TIM (TIM16) +#define SIGNAL_READER_CAPTURE_TIM_CHANNEL LL_TIM_CHANNEL_CH1 + +#define SIGNAL_READER_DMA_GPIO LL_DMA_CHANNEL_2 +#define SIGNAL_READER_DMA_GPIO_IRQ FuriHalInterruptIdDma2Ch2 +#define SIGNAL_READER_DMA_GPIO_DEF SIGNAL_READER_DMA, SIGNAL_READER_DMA_GPIO + +#define SIGNAL_READER_DMA_TRIGGER LL_DMA_CHANNEL_3 +#define SIGNAL_READER_DMA_TRIGGER_IRQ FuriHalInterruptIdDma2Ch3 +#define SIGNAL_READER_DMA_TRIGGER_DEF SIGNAL_READER_DMA, SIGNAL_READER_DMA_TRIGGER + +#define SIGNAL_READER_DMA_CNT_SYNC LL_DMA_CHANNEL_5 +#define SIGNAL_READER_DMA_CNT_SYNC_IRQ FuriHalInterruptIdDma2Ch5 +#define SIGNAL_READER_DMA_CNT_SYNC_DEF SIGNAL_READER_DMA, SIGNAL_READER_DMA_CNT_SYNC + +struct SignalReader { + size_t buffer_size; + const GpioPin* pin; + GpioPull pull; + SignalReaderPolarity polarity; + SignalReaderTrigger trigger; + + uint16_t* gpio_buffer; + uint8_t* bitstream_buffer; + uint32_t cnt_en; + + uint32_t tim_cnt_compensation; + uint32_t tim_arr; + + SignalReaderEvent event; + SignalReaderEventData event_data; + + SignalReaderCallback callback; + void* context; +}; + +#define GPIO_PIN_MAP(pin, prefix) \ + (((pin) == (LL_GPIO_PIN_0)) ? prefix##0 : \ + ((pin) == (LL_GPIO_PIN_1)) ? prefix##1 : \ + ((pin) == (LL_GPIO_PIN_2)) ? prefix##2 : \ + ((pin) == (LL_GPIO_PIN_3)) ? prefix##3 : \ + ((pin) == (LL_GPIO_PIN_4)) ? prefix##4 : \ + ((pin) == (LL_GPIO_PIN_5)) ? prefix##5 : \ + ((pin) == (LL_GPIO_PIN_6)) ? prefix##6 : \ + ((pin) == (LL_GPIO_PIN_7)) ? prefix##7 : \ + ((pin) == (LL_GPIO_PIN_8)) ? prefix##8 : \ + ((pin) == (LL_GPIO_PIN_9)) ? prefix##9 : \ + ((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \ + ((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \ + ((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \ + ((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \ + ((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \ + prefix##15) + +#define GET_DMAMUX_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_DMAMUX_REQ_GEN_EXTI_LINE) + +SignalReader* signal_reader_alloc(const GpioPin* gpio_pin, uint32_t size) { + SignalReader* instance = malloc(sizeof(SignalReader)); + + instance->pin = gpio_pin; + instance->pull = GpioPullNo; + + instance->buffer_size = size; + instance->gpio_buffer = malloc(sizeof(uint16_t) * size * 8); + instance->bitstream_buffer = malloc(size); + + instance->event.data = &instance->event_data; + + return instance; +} + +void signal_reader_free(SignalReader* instance) { + furi_assert(instance); + furi_assert(instance->gpio_buffer); + furi_assert(instance->bitstream_buffer); + + free(instance->gpio_buffer); + free(instance->bitstream_buffer); + free(instance); +} + +void signal_reader_set_pull(SignalReader* instance, GpioPull pull) { + furi_assert(instance); + + instance->pull = pull; +} + +void signal_reader_set_polarity(SignalReader* instance, SignalReaderPolarity polarity) { + furi_assert(instance); + + instance->polarity = polarity; +} + +void signal_reader_set_sample_rate( + SignalReader* instance, + SignalReaderTimeUnit time_unit, + uint32_t time) { + furi_assert(instance); + UNUSED(time_unit); + + instance->tim_arr = time; +} + +void signal_reader_set_trigger(SignalReader* instance, SignalReaderTrigger trigger) { + furi_assert(instance); + + instance->trigger = trigger; +} + +static void furi_hal_sw_digital_pin_dma_rx_isr(void* context) { + SignalReader* instance = context; + + uint16_t* gpio_buff_start = NULL; + uint8_t* bitstream_buff_start = NULL; + + if(LL_DMA_IsActiveFlag_HT2(SIGNAL_READER_DMA)) { + LL_DMA_ClearFlag_HT2(SIGNAL_READER_DMA); + instance->event.type = SignalReaderEventTypeHalfBufferFilled; + gpio_buff_start = instance->gpio_buffer; + bitstream_buff_start = instance->bitstream_buffer; + + if(instance->callback) { + furi_assert(gpio_buff_start); + furi_assert(bitstream_buff_start); + + for(size_t i = 0; i < instance->buffer_size * 4; i++) { + if((i % 8) == 0) { + bitstream_buff_start[i / 8] = 0; + } + uint8_t bit = 0; + if(instance->polarity == SignalReaderPolarityNormal) { + bit = (gpio_buff_start[i] & instance->pin->pin) == instance->pin->pin; + } else { + bit = (gpio_buff_start[i] & instance->pin->pin) == 0; + } + bitstream_buff_start[i / 8] |= bit << (i % 8); + } + instance->event_data.data = bitstream_buff_start; + instance->event_data.len = instance->buffer_size / 2; + instance->callback(instance->event, instance->context); + } + } + if(LL_DMA_IsActiveFlag_TC2(SIGNAL_READER_DMA)) { + LL_DMA_ClearFlag_TC2(SIGNAL_READER_DMA); + instance->event.type = SignalReaderEventTypeFullBufferFilled; + gpio_buff_start = &instance->gpio_buffer[instance->buffer_size * 4]; + bitstream_buff_start = &instance->bitstream_buffer[instance->buffer_size / 2]; + + if(instance->callback) { + furi_assert(gpio_buff_start); + furi_assert(bitstream_buff_start); + + for(size_t i = 0; i < instance->buffer_size * 4; i++) { + if((i % 8) == 0) { + bitstream_buff_start[i / 8] = 0; + } + uint8_t bit = 0; + if(instance->polarity == SignalReaderPolarityNormal) { + bit = (gpio_buff_start[i] & instance->pin->pin) == instance->pin->pin; + } else { + bit = (gpio_buff_start[i] & instance->pin->pin) == 0; + } + bitstream_buff_start[i / 8] |= bit << (i % 8); + } + instance->event_data.data = bitstream_buff_start; + instance->event_data.len = instance->buffer_size / 2; + instance->callback(instance->event, instance->context); + } + } +} + +void signal_reader_start(SignalReader* instance, SignalReaderCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; + + // EXTI delay compensation + instance->tim_cnt_compensation = 9; + instance->cnt_en = SIGNAL_READER_CAPTURE_TIM->CR1; + instance->cnt_en |= TIM_CR1_CEN; + + furi_hal_bus_enable(FuriHalBusTIM16); + + // Capture timer config + LL_TIM_SetPrescaler(SIGNAL_READER_CAPTURE_TIM, 0); + LL_TIM_SetCounterMode(SIGNAL_READER_CAPTURE_TIM, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(SIGNAL_READER_CAPTURE_TIM, instance->tim_arr); + LL_TIM_SetClockDivision(SIGNAL_READER_CAPTURE_TIM, LL_TIM_CLOCKDIVISION_DIV1); + + LL_TIM_DisableARRPreload(SIGNAL_READER_CAPTURE_TIM); + LL_TIM_SetClockSource(SIGNAL_READER_CAPTURE_TIM, LL_TIM_CLOCKSOURCE_INTERNAL); + + // Configure TIM channel CC1 + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {}; + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_FROZEN; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE; + TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE; + TIM_OC_InitStruct.CompareValue = (instance->tim_arr / 2); + TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; + LL_TIM_OC_Init( + SIGNAL_READER_CAPTURE_TIM, SIGNAL_READER_CAPTURE_TIM_CHANNEL, &TIM_OC_InitStruct); + LL_TIM_OC_DisableFast(SIGNAL_READER_CAPTURE_TIM, SIGNAL_READER_CAPTURE_TIM_CHANNEL); + + LL_TIM_SetTriggerOutput(SIGNAL_READER_CAPTURE_TIM, LL_TIM_TRGO_RESET); + LL_TIM_DisableMasterSlaveMode(SIGNAL_READER_CAPTURE_TIM); + + // Start + LL_TIM_GenerateEvent_UPDATE(SIGNAL_READER_CAPTURE_TIM); + + /* We need the EXTI to be configured as interrupt generating line, but no ISR registered */ + furi_hal_gpio_init( + instance->pin, GpioModeInterruptRiseFall, instance->pull, GpioSpeedVeryHigh); + + /* Set DMAMUX request generation signal ID on specified DMAMUX channel */ + LL_DMAMUX_SetRequestSignalID( + DMAMUX1, LL_DMAMUX_REQ_GEN_0, GET_DMAMUX_EXTI_LINE(instance->pin->pin)); + /* Set the polarity of the signal on which the DMA request is generated */ + LL_DMAMUX_SetRequestGenPolarity(DMAMUX1, LL_DMAMUX_REQ_GEN_0, LL_DMAMUX_REQ_GEN_POL_RISING); + /* Set the number of DMA requests that will be authorized after a generation event */ + LL_DMAMUX_SetGenRequestNb(DMAMUX1, LL_DMAMUX_REQ_GEN_0, 1); + + // Configure DMA Sync + LL_DMA_SetMemoryAddress( + SIGNAL_READER_DMA_CNT_SYNC_DEF, (uint32_t)&instance->tim_cnt_compensation); + LL_DMA_SetPeriphAddress( + SIGNAL_READER_DMA_CNT_SYNC_DEF, (uint32_t) & (SIGNAL_READER_CAPTURE_TIM->CNT)); + LL_DMA_ConfigTransfer( + SIGNAL_READER_DMA_CNT_SYNC_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_NOINCREMENT | LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD | + LL_DMA_PRIORITY_VERYHIGH); + LL_DMA_SetDataLength(SIGNAL_READER_DMA_CNT_SYNC_DEF, 1); + LL_DMA_SetPeriphRequest(SIGNAL_READER_DMA_CNT_SYNC_DEF, LL_DMAMUX_REQ_GENERATOR0); + + // Configure DMA Sync + LL_DMA_SetMemoryAddress(SIGNAL_READER_DMA_TRIGGER_DEF, (uint32_t)&instance->cnt_en); + LL_DMA_SetPeriphAddress( + SIGNAL_READER_DMA_TRIGGER_DEF, (uint32_t) & (SIGNAL_READER_CAPTURE_TIM->CR1)); + LL_DMA_ConfigTransfer( + SIGNAL_READER_DMA_TRIGGER_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_NOINCREMENT | + LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD | LL_DMA_PRIORITY_VERYHIGH); + LL_DMA_SetDataLength(SIGNAL_READER_DMA_TRIGGER_DEF, 1); + LL_DMA_SetPeriphRequest(SIGNAL_READER_DMA_TRIGGER_DEF, LL_DMAMUX_REQ_GENERATOR0); + + // Configure DMA Rx pin + LL_DMA_SetMemoryAddress(SIGNAL_READER_DMA_GPIO_DEF, (uint32_t)instance->gpio_buffer); + LL_DMA_SetPeriphAddress(SIGNAL_READER_DMA_GPIO_DEF, (uint32_t) & (instance->pin->port->IDR)); + LL_DMA_ConfigTransfer( + SIGNAL_READER_DMA_GPIO_DEF, + LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD | + LL_DMA_PRIORITY_HIGH); + LL_DMA_SetDataLength(SIGNAL_READER_DMA_GPIO_DEF, instance->buffer_size * 8); + LL_DMA_SetPeriphRequest(SIGNAL_READER_DMA_GPIO_DEF, LL_DMAMUX_REQ_TIM16_CH1); + + // Configure DMA Channel CC1 + LL_TIM_EnableDMAReq_CC1(SIGNAL_READER_CAPTURE_TIM); + LL_TIM_CC_EnableChannel(SIGNAL_READER_CAPTURE_TIM, SIGNAL_READER_CAPTURE_TIM_CHANNEL); + + // Start DMA irq, higher priority than normal + furi_hal_interrupt_set_isr_ex( + SIGNAL_READER_DMA_GPIO_IRQ, 14, furi_hal_sw_digital_pin_dma_rx_isr, instance); + + // Start DMA Sync timer + LL_DMA_EnableChannel(SIGNAL_READER_DMA_CNT_SYNC_DEF); + + // Start DMA Rx pin + LL_DMA_EnableChannel(SIGNAL_READER_DMA_GPIO_DEF); + // Strat timer + LL_TIM_SetCounter(SIGNAL_READER_CAPTURE_TIM, 0); + if(instance->trigger == SignalReaderTriggerNone) { + LL_TIM_EnableCounter(SIGNAL_READER_CAPTURE_TIM); + } else { + LL_DMA_EnableChannel(SIGNAL_READER_DMA_TRIGGER_DEF); + } + + LL_DMAMUX_EnableRequestGen(DMAMUX1, LL_DMAMUX_REQ_GEN_0); + // Need to clear flags before enabling DMA !!!! + if(LL_DMA_IsActiveFlag_TC2(SIGNAL_READER_DMA)) LL_DMA_ClearFlag_TC1(SIGNAL_READER_DMA); + if(LL_DMA_IsActiveFlag_TE2(SIGNAL_READER_DMA)) LL_DMA_ClearFlag_TE1(SIGNAL_READER_DMA); + LL_DMA_EnableIT_TC(SIGNAL_READER_DMA_GPIO_DEF); + LL_DMA_EnableIT_HT(SIGNAL_READER_DMA_GPIO_DEF); +} + +void signal_reader_stop(SignalReader* instance) { + furi_assert(instance); + + furi_hal_interrupt_set_isr(SIGNAL_READER_DMA_GPIO_IRQ, NULL, NULL); + + // Deinit DMA Rx pin + LL_DMA_DeInit(SIGNAL_READER_DMA_GPIO_DEF); + // Deinit DMA Sync timer + LL_DMA_DeInit(SIGNAL_READER_DMA_CNT_SYNC_DEF); + // Deinit DMA Trigger timer + LL_DMA_DeInit(SIGNAL_READER_DMA_TRIGGER_DEF); + + furi_hal_bus_disable(FuriHalBusTIM16); +} diff --git a/lib/signal_reader/signal_reader.h b/lib/signal_reader/signal_reader.h new file mode 100644 index 0000000000..be18f295a4 --- /dev/null +++ b/lib/signal_reader/signal_reader.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SignalReaderEventTypeHalfBufferFilled, + SignalReaderEventTypeFullBufferFilled, +} SignalReaderEventType; + +typedef struct { + uint8_t* data; + size_t len; +} SignalReaderEventData; + +typedef struct { + SignalReaderEventType type; + SignalReaderEventData* data; +} SignalReaderEvent; + +typedef enum { + SignalReaderTimeUnit64Mhz, +} SignalReaderTimeUnit; + +typedef enum { + SignalReaderPolarityNormal, + SignalReaderPolarityInverted, +} SignalReaderPolarity; + +typedef enum { + SignalReaderTriggerNone, + SignalReaderTriggerRisingFallingEdge, +} SignalReaderTrigger; + +typedef void (*SignalReaderCallback)(SignalReaderEvent event, void* context); + +typedef struct SignalReader SignalReader; + +SignalReader* signal_reader_alloc(const GpioPin* gpio_pin, uint32_t size); + +void signal_reader_free(SignalReader* instance); + +void signal_reader_set_pull(SignalReader* instance, GpioPull pull); + +void signal_reader_set_polarity(SignalReader* instance, SignalReaderPolarity polarity); + +void signal_reader_set_sample_rate( + SignalReader* instance, + SignalReaderTimeUnit time_unit, + uint32_t time); + +void signal_reader_set_trigger(SignalReader* instance, SignalReaderTrigger trigger); + +void signal_reader_start(SignalReader* instance, SignalReaderCallback callback, void* context); + +void signal_reader_stop(SignalReader* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 03f4c5bd4b..80a34c55f7 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -9,6 +9,8 @@ #include #include +#include + #define TAG "SubGhzProtocolBinRaw" //change very carefully, RAM ends at the most inopportune moment diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 04fd8567fc..de77f0ccf2 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -31,6 +31,8 @@ env.Append( File("protocols/protocol_dict.h"), File("pretty_format.h"), File("hex.h"), + File("simple_array.h"), + File("bit_buffer.h"), ], ) diff --git a/lib/toolbox/bit_buffer.c b/lib/toolbox/bit_buffer.c new file mode 100644 index 0000000000..1943ee833d --- /dev/null +++ b/lib/toolbox/bit_buffer.c @@ -0,0 +1,336 @@ +#include "bit_buffer.h" + +#include + +#define BITS_IN_BYTE (8) + +struct BitBuffer { + uint8_t* data; + uint8_t* parity; + size_t capacity_bytes; + size_t size_bits; +}; + +BitBuffer* bit_buffer_alloc(size_t capacity_bytes) { + furi_assert(capacity_bytes); + + BitBuffer* buf = malloc(sizeof(BitBuffer)); + + buf->data = malloc(capacity_bytes); + size_t parity_buf_size = (capacity_bytes + BITS_IN_BYTE - 1) / BITS_IN_BYTE; + buf->parity = malloc(parity_buf_size); + buf->capacity_bytes = capacity_bytes; + buf->size_bits = 0; + + return buf; +} + +void bit_buffer_free(BitBuffer* buf) { + furi_assert(buf); + + free(buf->data); + free(buf->parity); + free(buf); +} + +void bit_buffer_reset(BitBuffer* buf) { + furi_assert(buf); + + memset(buf->data, 0, buf->capacity_bytes); + size_t parity_buf_size = (buf->capacity_bytes + BITS_IN_BYTE - 1) / BITS_IN_BYTE; + memset(buf->parity, 0, parity_buf_size); + buf->size_bits = 0; +} + +void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other) { + furi_assert(buf); + furi_assert(other); + + if(buf == other) return; + + furi_assert(buf->capacity_bytes * BITS_IN_BYTE >= other->size_bits); + + memcpy(buf->data, other->data, bit_buffer_get_size_bytes(other)); + buf->size_bits = other->size_bits; +} + +void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index) { + furi_assert(buf); + furi_assert(other); + furi_assert(bit_buffer_get_size_bytes(other) > start_index); + furi_assert(buf->capacity_bytes >= bit_buffer_get_size_bytes(other) - start_index); + + memcpy(buf->data, other->data + start_index, bit_buffer_get_size_bytes(other) - start_index); + buf->size_bits = other->size_bits - start_index * BITS_IN_BYTE; +} + +void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index) { + furi_assert(buf); + furi_assert(other); + furi_assert(bit_buffer_get_capacity_bytes(buf) >= end_index); + furi_assert(bit_buffer_get_size_bytes(other) >= end_index); + + memcpy(buf->data, other->data, end_index); + buf->size_bits = end_index * BITS_IN_BYTE; +} + +void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes) { + furi_assert(buf); + furi_assert(data); + furi_assert(buf->capacity_bytes >= size_bytes); + + memcpy(buf->data, data, size_bytes); + buf->size_bits = size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits) { + furi_assert(buf); + furi_assert(data); + furi_assert(buf->capacity_bytes * BITS_IN_BYTE >= size_bits); + + size_t size_bytes = (size_bits + BITS_IN_BYTE - 1) / BITS_IN_BYTE; + memcpy(buf->data, data, size_bytes); + buf->size_bits = size_bits; +} + +void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits) { + furi_assert(buf); + furi_assert(data); + + size_t bits_processed = 0; + size_t curr_byte = 0; + + if(size_bits < BITS_IN_BYTE + 1) { + buf->size_bits = size_bits; + buf->data[0] = data[0]; + } else { + furi_assert(size_bits % (BITS_IN_BYTE + 1) == 0); + while(bits_processed < size_bits) { + buf->data[curr_byte] = data[bits_processed / BITS_IN_BYTE] >> + (bits_processed % BITS_IN_BYTE); + buf->data[curr_byte] |= data[bits_processed / BITS_IN_BYTE + 1] + << (BITS_IN_BYTE - bits_processed % BITS_IN_BYTE); + uint8_t bit = + FURI_BIT(data[bits_processed / BITS_IN_BYTE + 1], bits_processed % BITS_IN_BYTE); + + if(bits_processed % BITS_IN_BYTE) { + buf->parity[curr_byte / BITS_IN_BYTE] = bit; + } else { + buf->parity[curr_byte / BITS_IN_BYTE] |= bit << (bits_processed % BITS_IN_BYTE); + } + bits_processed += BITS_IN_BYTE + 1; + curr_byte++; + } + buf->size_bits = curr_byte * BITS_IN_BYTE; + } +} + +void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes) { + furi_assert(buf); + furi_assert(dest); + furi_assert(bit_buffer_get_size_bytes(buf) <= size_bytes); + + memcpy(dest, buf->data, bit_buffer_get_size_bytes(buf)); +} + +void bit_buffer_write_bytes_with_parity( + const BitBuffer* buf, + void* dest, + size_t size_bytes, + size_t* bits_written) { + furi_assert(buf); + furi_assert(dest); + furi_assert(bits_written); + + size_t buf_size_bytes = bit_buffer_get_size_bytes(buf); + size_t buf_size_with_parity_bytes = + (buf_size_bytes * (BITS_IN_BYTE + 1) + BITS_IN_BYTE) / BITS_IN_BYTE; + furi_assert(buf_size_with_parity_bytes <= size_bytes); + + uint8_t next_par_bit = 0; + uint16_t curr_bit_pos = 0; + uint8_t* bitstream = dest; + + for(size_t i = 0; i < buf_size_bytes; i++) { + next_par_bit = FURI_BIT(buf->parity[i / BITS_IN_BYTE], i % BITS_IN_BYTE); + if(curr_bit_pos % BITS_IN_BYTE == 0) { + bitstream[curr_bit_pos / BITS_IN_BYTE] = buf->data[i]; + curr_bit_pos += BITS_IN_BYTE; + bitstream[curr_bit_pos / BITS_IN_BYTE] = next_par_bit; + curr_bit_pos++; + } else { + bitstream[curr_bit_pos / BITS_IN_BYTE] |= buf->data[i] + << (curr_bit_pos % BITS_IN_BYTE); + bitstream[curr_bit_pos / BITS_IN_BYTE + 1] = + buf->data[i] >> (BITS_IN_BYTE - curr_bit_pos % BITS_IN_BYTE); + bitstream[curr_bit_pos / BITS_IN_BYTE + 1] |= next_par_bit + << (curr_bit_pos % BITS_IN_BYTE); + curr_bit_pos += BITS_IN_BYTE + 1; + } + } + + *bits_written = curr_bit_pos; +} + +void bit_buffer_write_bytes_mid( + const BitBuffer* buf, + void* dest, + size_t start_index, + size_t size_bytes) { + furi_assert(buf); + furi_assert(dest); + furi_assert(start_index + size_bytes <= bit_buffer_get_size_bytes(buf)); + + memcpy(dest, buf->data + start_index, size_bytes); +} + +bool bit_buffer_has_partial_byte(const BitBuffer* buf) { + furi_assert(buf); + + return (buf->size_bits % BITS_IN_BYTE) != 0; +} + +bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte) { + furi_assert(buf); + + return bit_buffer_get_size_bytes(buf) && (buf->data[0] == byte); +} + +size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf) { + furi_assert(buf); + + return buf->capacity_bytes; +} + +size_t bit_buffer_get_size(const BitBuffer* buf) { + furi_assert(buf); + + return buf->size_bits; +} + +size_t bit_buffer_get_size_bytes(const BitBuffer* buf) { + furi_assert(buf); + + return (buf->size_bits / BITS_IN_BYTE) + (buf->size_bits % BITS_IN_BYTE ? 1 : 0); +} + +uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index) { + furi_assert(buf); + furi_assert(buf->capacity_bytes > index); + + return buf->data[index]; +} + +uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits) { + furi_assert(buf); + furi_assert(buf->capacity_bytes * BITS_IN_BYTE > index_bits); + + const size_t byte_index = index_bits / BITS_IN_BYTE; + const size_t bit_offset = index_bits % BITS_IN_BYTE; + + const uint8_t lo = buf->data[byte_index] >> bit_offset; + const uint8_t hi = buf->data[byte_index + 1] << (BITS_IN_BYTE - bit_offset); + + return lo | hi; +} + +const uint8_t* bit_buffer_get_data(const BitBuffer* buf) { + furi_assert(buf); + + return buf->data; +} + +const uint8_t* bit_buffer_get_parity(const BitBuffer* buf) { + furi_assert(buf); + + return buf->parity; +} + +void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte) { + furi_assert(buf); + + const size_t size_bytes = bit_buffer_get_size_bytes(buf); + furi_assert(size_bytes > index); + + buf->data[index] = byte; +} + +void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity) { + furi_assert(buff); + furi_assert(buff->size_bits / BITS_IN_BYTE > index); + + buff->data[index] = byte; + if((index % BITS_IN_BYTE) == 0) { + buff->parity[index / BITS_IN_BYTE] = parity; + } else { + buff->parity[index / BITS_IN_BYTE] |= parity << (index % BITS_IN_BYTE); + } +} + +void bit_buffer_set_size(BitBuffer* buf, size_t new_size) { + furi_assert(buf); + furi_assert(buf->capacity_bytes * BITS_IN_BYTE >= new_size); + + buf->size_bits = new_size; +} + +void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes) { + furi_assert(buf); + furi_assert(buf->capacity_bytes >= new_size_bytes); + + buf->size_bits = new_size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_append(BitBuffer* buf, const BitBuffer* other) { + bit_buffer_append_right(buf, other, 0); +} + +void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index) { + furi_assert(buf); + furi_assert(other); + + const size_t size_bytes = bit_buffer_get_size_bytes(buf); + const size_t other_size_bytes = bit_buffer_get_size_bytes(other) - start_index; + + furi_assert(buf->capacity_bytes >= size_bytes + other_size_bytes); + + memcpy(buf->data + size_bytes, other->data + start_index, other_size_bytes); + buf->size_bits += other->size_bits - start_index * BITS_IN_BYTE; +} + +void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte) { + furi_assert(buf); + + const size_t data_size_bytes = bit_buffer_get_size_bytes(buf); + const size_t new_data_size_bytes = data_size_bytes + 1; + furi_assert(new_data_size_bytes <= buf->capacity_bytes); + + buf->data[data_size_bytes] = byte; + buf->size_bits = new_data_size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes) { + furi_assert(buf); + furi_assert(data); + + const size_t buf_size_bytes = bit_buffer_get_size_bytes(buf); + furi_assert(buf->capacity_bytes >= buf_size_bytes + size_bytes); + + memcpy(&buf->data[buf_size_bytes], data, size_bytes); + buf->size_bits += size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_append_bit(BitBuffer* buf, bool bit) { + furi_assert(buf); + furi_assert( + bit_buffer_get_size_bytes(buf) <= + (buf->capacity_bytes - (bit_buffer_has_partial_byte(buf) ? 0 : 1))); + + if(bit) { + const size_t byte_index = buf->size_bits / BITS_IN_BYTE; + const size_t bit_offset = (buf->size_bits % BITS_IN_BYTE); + buf->data[byte_index] |= 1U << bit_offset; + } + + buf->size_bits++; +} diff --git a/lib/toolbox/bit_buffer.h b/lib/toolbox/bit_buffer.h new file mode 100644 index 0000000000..5c50e729c6 --- /dev/null +++ b/lib/toolbox/bit_buffer.h @@ -0,0 +1,325 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BitBuffer BitBuffer; + +// Construction, deletion, reset + +/** + * Allocate a BitBuffer instance. + * + * @param [in] capacity_bytes maximum buffer capacity, in bytes + * @return pointer to the allocated BitBuffer instance + */ +BitBuffer* bit_buffer_alloc(size_t capacity_bytes); + +/** + * Delete a BitBuffer instance. + * + * @param [in,out] buf pointer to a BitBuffer instance + */ +void bit_buffer_free(BitBuffer* buf); + +/** + * Clear all data from a BitBuffer instance. + * + * @param [in,out] buf pointer to a BitBuffer instance + */ +void bit_buffer_reset(BitBuffer* buf); + +// Copy and write + +/** + * Copy another BitBuffer instance's contents to this one, replacing + * all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] other pointer to a BitBuffer instance to copy from + * @note + */ +void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other); + +/** + * Copy all BitBuffer instance's contents to this one, starting from start_index, + * replacing all of the original data. + * The destination capacity must be no less than the source data size + * counting from start_index. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] other pointer to a BitBuffer instance to copy from + * @param [in] start_index index to begin copying source data from + */ +void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); + +/** + * Copy all BitBuffer instance's contents to this one, ending with end_index, + * replacing all of the original data. + * The destination capacity must be no less than the source data size + * counting to end_index. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] other pointer to a BitBuffer instance to copy from + * @param [in] end_index index to end copying source data at + */ +void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index); + +/** + * Copy a byte array to a BitBuffer instance, replacing all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] data pointer to the byte array to be copied + * @param [in] size_bytes size of the data to be copied, in bytes + */ +void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); + +/** + * Copy a byte array to a BitBuffer instance, replacing all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] data pointer to the byte array to be copied + * @param [in] size_bits size of the data to be copied, in bits + */ +void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits); + +/** + * Copy a byte with parity array to a BitBuffer instance, replacing all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] data pointer to the byte array to be copied + * @param [in] size_bitss size of the data to be copied, in bits + */ +void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits); + +/** + * Write a BitBuffer instance's entire contents to an arbitrary memory location. + * The destination memory must be allocated. Additionally, the destination + * capacity must be no less than the source data size. + * + * @param [in] buf pointer to a BitBuffer instance to write from + * @param [out] dest pointer to the destination memory location + * @param [in] size_bytes maximum destination data size, in bytes + */ +void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes); + +/** + * Write a BitBuffer instance's entire contents to an arbitrary memory location. + * Additionally, place a parity bit after each byte. + * The destination memory must be allocated. Additionally, the destination + * capacity must be no less than the source data size plus parity. + * + * @param [in] buf pointer to a BitBuffer instance to write from + * @param [out] dest pointer to the destination memory location + * @param [in] size_bytes maximum destination data size, in bytes + * @param [out] bits_written actual number of bits writen, in bits + */ +void bit_buffer_write_bytes_with_parity( + const BitBuffer* buf, + void* dest, + size_t size_bytes, + size_t* bits_written); + +/** + * Write a slice of BitBuffer instance's contents to an arbitrary memory location. + * The destination memory must be allocated. Additionally, the destination + * capacity must be no less than the requested slice size. + * + * @param [in] buf pointer to a BitBuffer instance to write from + * @param [out] dest pointer to the destination memory location + * @param [in] start_index index to begin copying source data from + * @param [in] size_bytes data slice size, in bytes + */ +void bit_buffer_write_bytes_mid( + const BitBuffer* buf, + void* dest, + size_t start_index, + size_t size_bytes); + +// Checks + +/** + * Check whether a BitBuffer instance contains a partial byte (i.e. the bit count + * is not divisible by 8). + * + * @param [in] buf pointer to a BitBuffer instance to be checked + * @return true if the instance contains a partial byte, false otherwise + */ +bool bit_buffer_has_partial_byte(const BitBuffer* buf); + +/** + * Check whether a BitBuffer instance's contents start with the designated byte. + * + * @param [in] buf pointer to a BitBuffer instance to be checked + * @param [in] byte byte value to be checked against + * @return true if data starts with designated byte, false otherwise + */ +bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte); + +// Getters + +/** + * Get a BitBuffer instance's capacity (i.e. the maximum possible amount of data), in bytes. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return capacity, in bytes + */ +size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf); + +/** + * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bits. + * Might be not divisible by 8 (see bit_buffer_is_partial_byte). + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return data size, in bits. + */ +size_t bit_buffer_get_size(const BitBuffer* buf); + +/** + * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bytes. + * If a partial byte is present, it is also counted. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return data size, in bytes. + */ +size_t bit_buffer_get_size_bytes(const BitBuffer* buf); + +/** + * Get a byte value at a specified index in a BitBuffer instance. + * The index must be valid (i.e. less than the instance's data size in bytes). + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @param [in] index index of the byte in question + */ +uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index); + +/** + * Get a byte value starting from the specified bit index in a BitBuffer instance. + * The resulting byte might correspond to a single byte (if the index is a multiple + * of 8), or two overlapping bytes combined. + * The index must be valid (i.e. less than the instance's data size in bits). + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @param [in] index bit index of the byte in question + */ +uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits); + +/** + * Get the pointer to a BitBuffer instance's underlying data. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return pointer to the underlying data + */ +const uint8_t* bit_buffer_get_data(const BitBuffer* buf); + +/** + * Get the pointer to a BitBuffer instance's underlying data. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return pointer to the underlying data + */ +const uint8_t* bit_buffer_get_parity(const BitBuffer* buf); + +// Setters + +/** + * Set byte value at a specified index in a BitBuffer instance. + * The index must be valid (i.e. less than the instance's data size in bytes). + * + * @param [in,out] buf pointer to a BitBuffer instance to be modified + * @param [in] index index of the byte in question + * @param [in] byte byte value to be set at index + */ +void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte); + +/** + * Set byte and parity bit value at a specified index in a BitBuffer instance. + * The index must be valid (i.e. less than the instance's data size in bytes). + * + * @param [in,out] buf pointer to a BitBuffer instance to be modified + * @param [in] index index of the byte in question + * @param [in] byte byte value to be set at index + * @param [in] parity parity bit value to be set at index + */ +void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity); + +/** + * Resize a BitBuffer instance to a new size, in bits. + * @warning May cause bugs. Use only if absolutely necessary. + * + * @param [in,out] buf pointer to a BitBuffer instance to be resized + * @param [in] new_size the new size of the buffer, in bits + */ +void bit_buffer_set_size(BitBuffer* buf, size_t new_size); + +/** + * Resize a BitBuffer instance to a new size, in bytes. + * @warning May cause bugs. Use only if absolutely necessary. + * + * @param [in,out] buf pointer to a BitBuffer instance to be resized + * @param [in] new_size_bytes the new size of the buffer, in bytes + */ +void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes); + +// Modification + +/** + * Append all BitBuffer's instance contents to this one. The destination capacity + * must be no less than its original data size plus source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] other pointer to a BitBuffer instance to be appended + */ +void bit_buffer_append(BitBuffer* buf, const BitBuffer* other); + +/** + * Append a BitBuffer's instance contents to this one, starting from start_index. + * The destination capacity must be no less than the source data size + * counting from start_index. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] other pointer to a BitBuffer instance to be appended + * @param [in] start_index index to begin copying source data from + */ +void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); + +/** + * Append a byte to a BitBuffer instance. + * The destination capacity must be no less its original data size plus one. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] byte byte value to be appended + */ +void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte); + +/** + * Append a byte array to a BitBuffer instance. + * The destination capacity must be no less its original data size plus source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] data pointer to the byte array to be appended + * @param [in] size_bytes size of the data to be appended, in bytes + */ +void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); + +/** + * Append a bit to a BitBuffer instance. + * The destination capacity must be sufficient to accomodate the additional bit. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] bit bit value to be appended + */ +void bit_buffer_append_bit(BitBuffer* buf, bool bit); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/simple_array.c b/lib/toolbox/simple_array.c new file mode 100644 index 0000000000..7aed8e34be --- /dev/null +++ b/lib/toolbox/simple_array.c @@ -0,0 +1,127 @@ +#include "simple_array.h" + +#include + +struct SimpleArray { + const SimpleArrayConfig* config; + SimpleArrayElement* data; + uint32_t count; +}; + +SimpleArray* simple_array_alloc(const SimpleArrayConfig* config) { + SimpleArray* instance = malloc(sizeof(SimpleArray)); + instance->config = config; + return instance; +} + +void simple_array_free(SimpleArray* instance) { + furi_assert(instance); + + simple_array_reset(instance); + free(instance); +} + +void simple_array_init(SimpleArray* instance, uint32_t count) { + furi_assert(instance); + furi_assert(count > 0); + + simple_array_reset(instance); + + instance->data = malloc(count * instance->config->type_size); + instance->count = count; + + SimpleArrayInit init = instance->config->init; + if(init) { + for(uint32_t i = 0; i < instance->count; ++i) { + init(simple_array_get(instance, i)); + } + } +} + +void simple_array_reset(SimpleArray* instance) { + furi_assert(instance); + + if(instance->data) { + SimpleArrayReset reset = instance->config->reset; + + if(reset) { + for(uint32_t i = 0; i < instance->count; ++i) { + reset(simple_array_get(instance, i)); + } + } + + free(instance->data); + + instance->count = 0; + instance->data = NULL; + } +} + +void simple_array_copy(SimpleArray* instance, const SimpleArray* other) { + furi_assert(instance); + furi_assert(other); + furi_assert(instance->config == other->config); + + simple_array_reset(instance); + + if(other->count == 0) { + return; + } + + simple_array_init(instance, other->count); + + SimpleArrayCopy copy = instance->config->copy; + if(copy) { + for(uint32_t i = 0; i < other->count; ++i) { + copy(simple_array_get(instance, i), simple_array_cget(other, i)); + } + } else { + memcpy(instance->data, other->data, other->count * instance->config->type_size); + } +} + +bool simple_array_is_equal(const SimpleArray* instance, const SimpleArray* other) { + furi_assert(instance); + furi_assert(other); + + // Equal if the same object + if(instance == other) return true; + + return (instance->config == other->config) && (instance->count == other->count) && + ((instance->data == other->data) || (instance->data == NULL) || (other->data == NULL) || + (memcmp(instance->data, other->data, other->count) == 0)); +} + +uint32_t simple_array_get_count(const SimpleArray* instance) { + furi_assert(instance); + return instance->count; +} + +SimpleArrayElement* simple_array_get(SimpleArray* instance, uint32_t index) { + furi_assert(instance); + furi_assert(index < instance->count); + + return instance->data + index * instance->config->type_size; +} + +const SimpleArrayElement* simple_array_cget(const SimpleArray* instance, uint32_t index) { + return simple_array_get((SimpleArrayElement*)instance, index); +} + +SimpleArrayData* simple_array_get_data(SimpleArray* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +const SimpleArrayData* simple_array_cget_data(const SimpleArray* instance) { + return simple_array_get_data((SimpleArray*)instance); +} + +const SimpleArrayConfig simple_array_config_uint8_t = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(uint8_t), +}; diff --git a/lib/toolbox/simple_array.h b/lib/toolbox/simple_array.h new file mode 100644 index 0000000000..a608db08d8 --- /dev/null +++ b/lib/toolbox/simple_array.h @@ -0,0 +1,148 @@ +/** + * @file simple_array.h + * + * @brief This file provides a simple (non-type safe) array for elements with + * (optional) in-place construction support. + * + * No append, remove or take operations are supported. + * Instead, the user must call simple_array_init() in order to reserve the memory for n elements. + * In case if init() is specified in the configuration, then the elements are constructed in-place. + * + * To clear all elements from the array, call simple_array_reset(), which will also call reset() + * for each element in case if it was specified in the configuration. This is useful if a custom + * destructor is required for a particular type. Calling simple_array_free() will also result in a + * simple_array_reset() call automatically. + * + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SimpleArray SimpleArray; + +typedef void SimpleArrayData; +typedef void SimpleArrayElement; + +typedef void (*SimpleArrayInit)(SimpleArrayElement* elem); +typedef void (*SimpleArrayReset)(SimpleArrayElement* elem); +typedef void (*SimpleArrayCopy)(SimpleArrayElement* elem, const SimpleArrayElement* other); + +/** Simple Array configuration structure. Defined per type. */ +typedef struct { + SimpleArrayInit init; /**< Initialisation (in-place constructor) method. */ + SimpleArrayReset reset; /**< Reset (custom destructor) method. */ + SimpleArrayCopy copy; /**< Copy (custom copy-constructor) method. */ + const size_t type_size; /** Type size, in bytes. */ +} SimpleArrayConfig; + +/** + * Allocate a SimpleArray instance with the given configuration. + * + * @param [in] config Pointer to the type-specific configuration + * @return Pointer to the allocated SimpleArray instance + */ +SimpleArray* simple_array_alloc(const SimpleArrayConfig* config); + +/** + * Free a SimpleArray instance and release its contents. + * + * @param [in] instance Pointer to the SimpleArray instance to be freed + */ +void simple_array_free(SimpleArray* instance); + +/** + * Initialise a SimpleArray instance by allocating additional space to contain + * the requested number of elements. + * If init() is specified in the config, then it is called for each element, + * otherwise the data is filled with zeroes. + * + * @param [in] instance Pointer to the SimpleArray instance to be init'd + * @param [in] count Number of elements to be allocated and init'd + */ +void simple_array_init(SimpleArray* instance, uint32_t count); + +/** + * Reset a SimpleArray instance and delete all of its elements. + * If reset() is specified in the config, then it is called for each element, + * otherwise the data is simply free()'d. + * + * @param [in] instance Pointer to the SimpleArray instance to be reset + */ +void simple_array_reset(SimpleArray* instance); + +/** + * Copy (duplicate) another SimpleArray instance to this one. + * If copy() is specified in the config, then it is called for each element, + * otherwise the data is simply memcpy()'d. + * + * @param [in] instance Pointer to the SimpleArray instance to copy to + * @param [in] other Pointer to the SimpleArray instance to copy from + */ +void simple_array_copy(SimpleArray* instance, const SimpleArray* other); + +/** + * Check if another SimpleArray instance is equal (the same object or holds the + * same data) to this one. + * + * @param [in] instance Pointer to the SimpleArray instance to be compared + * @param [in] other Pointer to the SimpleArray instance to be compared + * @return True if instances are considered equal, false otherwise + */ +bool simple_array_is_equal(const SimpleArray* instance, const SimpleArray* other); + +/** + * Get the count of elements currently contained in a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to query the count from + * @return Count of elements contained in the instance + */ +uint32_t simple_array_get_count(const SimpleArray* instance); + +/** + * Get a pointer to an element contained in a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get an element from + * @param [in] index Index of the element in question. MUST be less than total element count + * @return Pointer to the element specified by index + */ +SimpleArrayElement* simple_array_get(SimpleArray* instance, uint32_t index); + +/** + * Get a const pointer to an element contained in a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get an element from + * @param [in] index Index of the element in question. MUST be less than total element count + * @return Const pointer to the element specified by index + */ +const SimpleArrayElement* simple_array_cget(const SimpleArray* instance, uint32_t index); + +/** + * Get a pointer to the internal data of a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get the data of + * @return Pointer to the instance's internal data + */ +SimpleArrayData* simple_array_get_data(SimpleArray* instance); + +/** + * Get a constant pointer to the internal data of a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get the data of + * @return Constant pointer to the instance's internal data + */ +const SimpleArrayData* simple_array_cget_data(const SimpleArray* instance); + +// Standard preset configurations + +// Preset configuration for a byte(uint8_t) array. +extern const SimpleArrayConfig simple_array_config_uint8_t; + +#ifdef __cplusplus +} +#endif From 92969ecc1f93e79cd385ad2ecfbfe067557d9f47 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 24 Oct 2023 16:04:55 +0300 Subject: [PATCH 005/111] remove mf classic uid add manually temp --- .../main/nfc/scenes/nfc_scene_set_type.c | 4 - .../nfc/scenes/nfc_scene_set_type_mf_uid.c | 103 ------------------ .../main/nfc/scenes/nfc_scene_set_uid.c | 15 --- 3 files changed, 122 deletions(-) delete mode 100644 applications/main/nfc/scenes/nfc_scene_set_type_mf_uid.c diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index b66cc42d05..e336600807 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -53,10 +53,6 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; - } else if(event.event == SubmenuIndexMFClassicCustomUID) { - nfc_device_clear(nfc->dev); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetTypeMfUid); - consumed = true; } else { nfc_data_generator_fill_data(event.event, instance->nfc_device); scene_manager_set_scene_state( diff --git a/applications/main/nfc/scenes/nfc_scene_set_type_mf_uid.c b/applications/main/nfc/scenes/nfc_scene_set_type_mf_uid.c deleted file mode 100644 index 55919500a8..0000000000 --- a/applications/main/nfc/scenes/nfc_scene_set_type_mf_uid.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "../nfc_i.h" -#include "lib/nfc/helpers/nfc_generators.h" - -enum SubmenuIndex { - SubmenuIndexMFC1k4b, - SubmenuIndexMFC4k4b, - SubmenuIndexMFC1k7b, - SubmenuIndexMFC4k7b, - SubmenuIndexMFCMini, -}; - -static const NfcGenerator ganeator_gag = { - .name = "Mifare Classic Custom UID", - .generator_func = NULL, -}; - -void nfc_scene_set_type_mf_uid_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_set_type_mf_uid_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, - "Mifare Classic 1k 4byte UID", - SubmenuIndexMFC1k4b, - nfc_scene_set_type_mf_uid_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Mifare Classic 4k 4byte UID", - SubmenuIndexMFC4k4b, - nfc_scene_set_type_mf_uid_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Mifare Classic 1k 7byte UID", - SubmenuIndexMFC1k7b, - nfc_scene_set_type_mf_uid_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Mifare Classic 4k 7byte UID", - SubmenuIndexMFC4k7b, - nfc_scene_set_type_mf_uid_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Mifare Classic Mini", - SubmenuIndexMFCMini, - nfc_scene_set_type_mf_uid_submenu_callback, - nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_set_type_mf_uid_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - bool correct_index = false; - MfClassicType mf_type = MfClassicType1k; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexMFC1k4b) { - nfc->dev->dev_data.nfc_data.uid_len = 4; - mf_type = MfClassicType1k; - correct_index = true; - } else if(event.event == SubmenuIndexMFC1k7b) { - nfc->dev->dev_data.nfc_data.uid_len = 7; - mf_type = MfClassicType1k; - correct_index = true; - } else if(event.event == SubmenuIndexMFC4k4b) { - nfc->dev->dev_data.nfc_data.uid_len = 4; - mf_type = MfClassicType4k; - correct_index = true; - } else if(event.event == SubmenuIndexMFC4k7b) { - nfc->dev->dev_data.nfc_data.uid_len = 7; - mf_type = MfClassicType4k; - correct_index = true; - } else if(event.event == SubmenuIndexMFCMini) { - nfc->dev->dev_data.nfc_data.uid_len = 4; - mf_type = MfClassicTypeMini; - correct_index = true; - } - if(correct_index) { - nfc->generator = &ganeator_gag; - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSetTypeMfUid, mf_type); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetUid); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_set_type_mf_uid_on_exit(void* context) { - Nfc* nfc = context; - - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 3ccecba922..df8a4dc72c 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -44,21 +44,6 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); consumed = true; } - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetTypeMfUid)) { - MfClassicType mf_type = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSetTypeMfUid); - if(mf_type > MfClassicTypeMini) { - furi_crash("Nfc unknown type"); - } - nfc_generate_mf_classic_ext( - &nfc->dev->dev_data, - nfc->dev_edit_data.uid_len, - mf_type, - false, - nfc->dev_edit_data.uid); - scene_manager_next_scene(nfc->scene_manager, NfcSceneGenerateInfo); - consumed = true; - } else { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; From 844e0f10e59ef53a6be2b71106b9d143fafeab01 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:56:49 +0300 Subject: [PATCH 006/111] [FL-3639] Fix MF DESFire record file handling (#3167) --- .../nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c | 1 + lib/nfc/protocols/mf_desfire/mf_desfire.h | 1 + lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index 883ea720b0..b5ca0d1383 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -197,6 +197,7 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, "size %lu\n", record_size); break; case MfDesfireFileTypeValue: + record_size = MF_DESFIRE_VALUE_SIZE; furi_string_cat_printf( str, "lo %lu hi %lu\n", settings->value.lo_limit, settings->value.hi_limit); furi_string_cat_printf( diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index 4d09dc8510..8505b792aa 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -29,6 +29,7 @@ extern "C" { #define MF_DESFIRE_UID_SIZE (7) #define MF_DESFIRE_BATCH_SIZE (5) #define MF_DESFIRE_APP_ID_SIZE (3) +#define MF_DESFIRE_VALUE_SIZE (4) typedef struct { uint8_t hw_vendor; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index e86cb4c68c..17377d66e3 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -353,7 +353,7 @@ MfDesfireError mf_desfire_poller_async_read_file_records( furi_assert(instance); bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_RECORDS); bit_buffer_append_byte(instance->input_buffer, id); bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); From 3d872cf37a2c4ec616021e1a6da4115610a6637a Mon Sep 17 00:00:00 2001 From: gornekich Date: Sat, 28 Oct 2023 18:22:07 +0400 Subject: [PATCH 007/111] [FL-3637] NFC RC fixes (#3165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * firmware: remove nfc lib build settings section * furi hal nfc: fix nfc irq gpio deinit * lib nfc: remove deprecated exception from sources * nfc: use ASK demodulator in transparent mode * mf ultralight: add upper page bound for NTAGI2C1K * furi hal nfc: set event if nfc event was started * nfc: fix PVS warnings * lib signal reader: remove gpio pull setting in alloc * furi: added math.h include for compatibility with existing apps * nfc: remove resolved TODO in mf desfire poller * bump api symbol version Co-authored-by: hedger Co-authored-by: あく --- firmware.scons | 9 --------- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 2 +- firmware/targets/f7/furi_hal/furi_hal_nfc_event.c | 5 +++-- firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c | 2 +- furi/furi.h | 3 +++ lib/nfc/SConscript | 2 +- lib/nfc/protocols/mf_ultralight/mf_ultralight.c | 2 +- .../protocols/mf_ultralight/mf_ultralight_listener_i.c | 2 ++ lib/signal_reader/parsers/iso15693/iso15693_parser.c | 2 +- 11 files changed, 15 insertions(+), 18 deletions(-) diff --git a/firmware.scons b/firmware.scons index d8e96ad7d0..82f775d719 100644 --- a/firmware.scons +++ b/firmware.scons @@ -71,15 +71,6 @@ env = ENV.Clone( "FURI_DEBUG" if ENV["DEBUG"] else "FURI_NDEBUG", ], }, - "nfc": { - "CCFLAGS": [ - "-Og", - ], - "CPPDEFINES": [ - "NDEBUG", - "FURI_DEBUG", - ], - }, }, FW_API_TABLE=None, _APP_ICONS=None, diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 8e15030a94..4789d316d6 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.0,, +Version,+,40.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 3ae099b5f1..038a22eae4 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.0,, +Version,+,40.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c index cce16c5dc9..266b606fd1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c @@ -25,9 +25,10 @@ FuriHalNfcError furi_hal_nfc_event_stop() { void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event) { furi_assert(furi_hal_nfc_event); - furi_assert(furi_hal_nfc_event->thread); - furi_thread_flags_set(furi_hal_nfc_event->thread, event); + if(furi_hal_nfc_event->thread) { + furi_thread_flags_set(furi_hal_nfc_event->thread, event); + } } FuriHalNfcError furi_hal_nfc_abort() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c index 63dc354155..170d8dee61 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c @@ -23,6 +23,6 @@ void furi_hal_nfc_init_gpio_isr() { } void furi_hal_nfc_deinit_gpio_isr() { - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); furi_hal_gpio_remove_int_callback(&gpio_nfc_irq_rfid_pull); + furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c index 19ac6dc034..cc936644b4 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -48,7 +48,7 @@ static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener_alloc() { instance->signal = iso15693_signal_alloc(&gpio_spi_r_mosi); instance->parser = - iso15693_parser_alloc(&gpio_spi_r_miso, FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE); + iso15693_parser_alloc(&gpio_nfc_irq_rfid_pull, FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE); return instance; } diff --git a/furi/furi.h b/furi/furi.h index cfdeb2c0f4..b1299c9a95 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -24,6 +24,9 @@ // FreeRTOS timer, REMOVE AFTER REFACTORING #include +// Workaround for math.h leaking through HAL in older versions +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 0e09b69392..6c55cf5d2b 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -51,7 +51,7 @@ env.Append( libenv = env.Clone(FW_LIB_NAME="nfc") libenv.ApplyLibFlags() -sources = libenv.GlobRecursive("*.c*", exclude="deprecated/*c") +sources = libenv.GlobRecursive("*.c*") lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index 40d9976863..fd845f3c08 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -297,7 +297,7 @@ bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t vers uint32_t pages_total = 0; if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break; uint32_t pages_read = 0; - if(data_format_version < mf_ultralight_data_format_version) { + if(data_format_version < mf_ultralight_data_format_version) { //-V547 pages_read = pages_total; } else { if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c index 3d6b9a94fd..64647492de 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c @@ -518,6 +518,8 @@ static uint16_t mf_ultralight_get_upper_page_bound(MfUltralightType type) { upper_page_bound = 511; else if(type == MfUltralightTypeNTAGI2C2K) upper_page_bound = 479; + else if(type == MfUltralightTypeNTAGI2C1K) + upper_page_bound = 225; else { upper_page_bound = mf_ultralight_get_config_page_num(type) - 2; } diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c index a398516c92..c5326a354d 100644 --- a/lib/signal_reader/parsers/iso15693/iso15693_parser.c +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -68,7 +68,7 @@ Iso15693Parser* iso15693_parser_alloc(const GpioPin* pin, size_t max_frame_size) signal_reader_set_sample_rate( instance->signal_reader, SignalReaderTimeUnit64Mhz, ISO15693_PARSER_BITRATE_F64MHZ); signal_reader_set_pull(instance->signal_reader, GpioPullDown); - signal_reader_set_polarity(instance->signal_reader, SignalReaderPolarityInverted); + signal_reader_set_polarity(instance->signal_reader, SignalReaderPolarityNormal); signal_reader_set_trigger(instance->signal_reader, SignalReaderTriggerRisingFallingEdge); return instance; From cfaf7455232db493f81ce5c036100029fd18bad2 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:29:14 +0300 Subject: [PATCH 008/111] [FL-3643] Fix crash when reading files > 64B (#3166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Increase MF DESFire result buffer * Ignore chunks that do not fit into the result buffer and show warning * Display information about partial files Co-authored-by: あく --- .../mf_desfire/mf_desfire_render.c | 18 +++++++++++++++++- .../protocols/mf_desfire/mf_desfire_poller.c | 11 ++++++----- .../protocols/mf_desfire/mf_desfire_poller_i.c | 10 +++++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index b5ca0d1383..94b333f552 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -190,6 +190,8 @@ void nfc_render_mf_desfire_file_settings_data( uint32_t record_count = 1; uint32_t record_size = 0; + const uint32_t total_size = simple_array_get_count(data->data); + switch(settings->type) { case MfDesfireFileTypeStandard: case MfDesfireFileTypeBackup: @@ -220,9 +222,20 @@ void nfc_render_mf_desfire_file_settings_data( } for(uint32_t rec = 0; rec < record_count; rec++) { - furi_string_cat_printf(str, "record %lu\n", rec); + const uint32_t size_offset = rec * record_size; + const uint32_t size_remaining = total_size > size_offset ? total_size - size_offset : 0; + + if(size_remaining < record_size) { + furi_string_cat_printf( + str, "record %lu (partial %lu of %lu)\n", rec, size_remaining, record_size); + record_size = size_remaining; + } else { + furi_string_cat_printf(str, "record %lu\n", rec); + } + for(uint32_t ch = 0; ch < record_size; ch += 4) { furi_string_cat_printf(str, "%03lx|", ch); + for(uint32_t i = 0; i < 4; i++) { if(ch + i < record_size) { const uint32_t data_index = rec * record_size + ch + i; @@ -233,6 +246,7 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, " "); } } + for(uint32_t i = 0; i < 4 && ch + i < record_size; i++) { const uint32_t data_index = rec * record_size + ch + i; const uint8_t data_byte = @@ -243,8 +257,10 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, "."); } } + furi_string_push_back(str, '\n'); } + furi_string_push_back(str, '\n'); } } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index c74bbc89da..11db021d57 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -6,7 +6,8 @@ #define TAG "MfDesfirePoller" -#define MF_DESFIRE_BUF_SIZE_MAX (64U) +#define MF_DESFIRE_BUF_SIZE (64U) +#define MF_DESFIRE_RESULT_BUF_SIZE (512U) typedef NfcCommand (*MfDesfirePollerReadHandler)(MfDesfirePoller* instance); @@ -20,10 +21,10 @@ static MfDesfirePoller* mf_desfire_poller_alloc(Iso14443_4aPoller* iso14443_4a_p MfDesfirePoller* instance = malloc(sizeof(MfDesfirePoller)); instance->iso14443_4a_poller = iso14443_4a_poller; instance->data = mf_desfire_alloc(); - instance->tx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); - instance->rx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); - instance->input_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); - instance->result_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->tx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE); + instance->input_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(MF_DESFIRE_RESULT_BUF_SIZE); instance->mf_desfire_event.data = &instance->mf_desfire_event_data; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 17377d66e3..38ae2f466d 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -59,7 +59,15 @@ MfDesfireError mf_desfire_send_chunks( break; } - bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + const size_t rx_size = bit_buffer_get_size_bytes(instance->rx_buffer); + const size_t rx_capacity_remaining = + bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); + + if(rx_size <= rx_capacity_remaining) { + bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size); + } } } while(false); From 0fe93fcfa48ea08479b72c6650fbf4ccf95d445d Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Sat, 28 Oct 2023 16:45:08 +0200 Subject: [PATCH 009/111] fix crash after st25tb save (#3170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/nfc/helpers/protocol_support/st25tb/st25tb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index eef723fed3..32b2f47757 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -95,6 +95,11 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_scene_saved_menu_on_event_st25tb, }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, .scene_emulate = { .on_enter = nfc_protocol_support_common_on_enter_empty, From 176fb21f5fee9cb41dee6e1d54dba42c526256f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 30 Oct 2023 23:51:51 +0900 Subject: [PATCH 010/111] Storage: speedup write_chunk cli command (#3173) * Storage: speedup write_chunk cli command * Storage: handle disconnect on write_chunk correctly --- applications/services/storage/storage_cli.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 74bcf2d92a..59489d459c 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -333,11 +333,9 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args if(buffer_size) { uint8_t* buffer = malloc(buffer_size); - for(uint32_t i = 0; i < buffer_size; i++) { - buffer[i] = cli_getc(cli); - } + size_t read_bytes = cli_read(cli, buffer, buffer_size); - uint16_t written_size = storage_file_write(file, buffer, buffer_size); + uint16_t written_size = storage_file_write(file, buffer, read_bytes); if(written_size != buffer_size) { storage_cli_print_error(storage_file_get_error(file)); From 917410a0a84fbfc0064cb51757972bce743b3980 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 30 Oct 2023 19:17:30 +0400 Subject: [PATCH 011/111] [FL-3629] fbt: reworked assets & resources handling (#3160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: reworking targets & assets handling WIP * fbt: dist fixes * fbt: moved SD card resources to owning apps * unit_tests: moved resources to app folder * github: updated unit_tests paths * github: packaging fixes * unit_tests: fixes * fbt: assets: internal cleanup * fbt: reworked assets handling * github: unit_tests: reintroducing fixes * minor cleanup * fbt: naming changes to reflect private nature of scons tools * fbt: resources: fixed dist archive paths * docs: updated paths * docs: updated more paths * docs: included "resources" parameter in app manifest docs; updated assets readme * updated gitignore for assets * github: updated action versions * unit_tests: restored timeout; scripts: assets: logging changes * gh: don't upload desktop animations for unit test run Co-authored-by: あく --- .github/CODEOWNERS | 4 +- .github/workflows/build.yml | 8 +- .github/workflows/build_compact.yml | 4 +- .../workflows/lint_and_submodule_check.yml | 2 +- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 +- .github/workflows/unit_tests.yml | 8 +- .github/workflows/updater_test.yml | 4 +- SConstruct | 34 +++--- applications/debug/unit_tests/application.fam | 1 + .../debug/unit_tests/manifest/manifest.c | 2 +- .../resources/unit_tests/Manifest_test | 0 .../unit_tests/infrared/test_kaseikyo.irtest | 0 .../unit_tests/infrared/test_nec.irtest | 0 .../unit_tests/infrared/test_nec42.irtest | 0 .../unit_tests/infrared/test_nec42ext.irtest | 0 .../unit_tests/infrared/test_necext.irtest | 0 .../unit_tests/infrared/test_rc5.irtest | 0 .../unit_tests/infrared/test_rc5x.irtest | 0 .../unit_tests/infrared/test_rc6.irtest | 0 .../unit_tests/infrared/test_rca.irtest | 0 .../unit_tests/infrared/test_samsung32.irtest | 0 .../unit_tests/infrared/test_sirc.irtest | 0 .../unit_tests/nfc/Ntag213_locked.nfc | 0 .../resources}/unit_tests/nfc/Ntag215.nfc | 0 .../resources}/unit_tests/nfc/Ntag216.nfc | 0 .../unit_tests/nfc/Ultralight_11.nfc | 0 .../unit_tests/nfc/Ultralight_21.nfc | 0 .../unit_tests/nfc/nfc_nfca_signal_long.nfc | 0 .../unit_tests/nfc/nfc_nfca_signal_short.nfc | 0 .../resources}/unit_tests/storage/md5.txt | 0 .../unit_tests/subghz/alutech_at_4n_raw.sub | 0 .../resources}/unit_tests/subghz/ansonic.sub | 0 .../unit_tests/subghz/ansonic_raw.sub | 0 .../resources}/unit_tests/subghz/bett.sub | 0 .../resources}/unit_tests/subghz/bett_raw.sub | 0 .../resources}/unit_tests/subghz/came.sub | 0 .../unit_tests/subghz/came_atomo_raw.sub | 0 .../resources}/unit_tests/subghz/came_raw.sub | 0 .../unit_tests/subghz/came_twee.sub | 0 .../unit_tests/subghz/came_twee_raw.sub | 0 .../unit_tests/subghz/cenmax_raw.sub | 0 .../resources}/unit_tests/subghz/clemsa.sub | 0 .../unit_tests/subghz/clemsa_raw.sub | 0 .../resources}/unit_tests/subghz/doitrand.sub | 0 .../unit_tests/subghz/doitrand_raw.sub | 0 .../resources}/unit_tests/subghz/doorhan.sub | 0 .../unit_tests/subghz/doorhan_raw.sub | 0 .../resources}/unit_tests/subghz/dooya.sub | 0 .../unit_tests/subghz/dooya_raw.sub | 0 .../unit_tests/subghz/faac_slh_raw.sub | 0 .../resources}/unit_tests/subghz/gate_tx.sub | 0 .../unit_tests/subghz/gate_tx_raw.sub | 0 .../resources}/unit_tests/subghz/holtek.sub | 0 .../unit_tests/subghz/holtek_ht12x.sub | 0 .../unit_tests/subghz/holtek_ht12x_raw.sub | 0 .../unit_tests/subghz/holtek_raw.sub | 0 .../unit_tests/subghz/honeywell_wdb.sub | 0 .../unit_tests/subghz/honeywell_wdb_raw.sub | 0 .../unit_tests/subghz/hormann_hsm_raw.sub | 0 .../unit_tests/subghz/ido_117_111_raw.sub | 0 .../unit_tests/subghz/intertechno_v3.sub | 0 .../unit_tests/subghz/intertechno_v3_raw.sub | 0 .../unit_tests/subghz/kia_seed_raw.sub | 0 .../subghz/kinggates_stylo4k_raw.sub | 0 .../resources}/unit_tests/subghz/linear.sub | 0 .../unit_tests/subghz/linear_delta3.sub | 0 .../unit_tests/subghz/linear_delta3_raw.sub | 0 .../unit_tests/subghz/linear_raw.sub | 0 .../resources}/unit_tests/subghz/magellan.sub | 0 .../unit_tests/subghz/magellan_raw.sub | 0 .../resources}/unit_tests/subghz/marantec.sub | 0 .../unit_tests/subghz/marantec_raw.sub | 0 .../resources}/unit_tests/subghz/megacode.sub | 0 .../unit_tests/subghz/megacode_raw.sub | 0 .../unit_tests/subghz/nero_radio_raw.sub | 0 .../unit_tests/subghz/nero_sketch_raw.sub | 0 .../resources}/unit_tests/subghz/nice_flo.sub | 0 .../unit_tests/subghz/nice_flo_raw.sub | 0 .../unit_tests/subghz/nice_flor_s_raw.sub | 0 .../unit_tests/subghz/nice_one_raw.sub | 0 .../unit_tests/subghz/phoenix_v2.sub | 0 .../unit_tests/subghz/phoenix_v2_raw.sub | 0 .../unit_tests/subghz/power_smart.sub | 0 .../unit_tests/subghz/power_smart_raw.sub | 0 .../unit_tests/subghz/princeton.sub | 0 .../unit_tests/subghz/princeton_raw.sub | 0 .../subghz/scher_khan_magic_code.sub | 0 .../unit_tests/subghz/security_pls_1_0.sub | 0 .../subghz/security_pls_1_0_raw.sub | 0 .../unit_tests/subghz/security_pls_2_0.sub | 0 .../subghz/security_pls_2_0_raw.sub | 0 .../resources}/unit_tests/subghz/smc5326.sub | 0 .../unit_tests/subghz/smc5326_raw.sub | 0 .../unit_tests/subghz/somfy_keytis_raw.sub | 0 .../unit_tests/subghz/somfy_telis_raw.sub | 0 .../unit_tests/subghz/test_random_raw.sub | 0 applications/main/bad_usb/application.fam | 1 + .../badusb/Install_qFlipper_macOS.txt | 0 .../badusb/Install_qFlipper_windows.txt | 0 .../resources/badusb/assets/layouts/ba-BA.kl | Bin .../resources/badusb/assets/layouts/cz_CS.kl | Bin .../resources/badusb/assets/layouts/da-DA.kl | Bin .../resources/badusb/assets/layouts/de-CH.kl | Bin .../resources/badusb/assets/layouts/de-DE.kl | Bin .../resources/badusb/assets/layouts/dvorak.kl | Bin .../resources/badusb/assets/layouts/en-UK.kl | Bin .../resources/badusb/assets/layouts/en-US.kl | Bin .../resources/badusb/assets/layouts/es-ES.kl | Bin .../resources/badusb/assets/layouts/fr-BE.kl | Bin .../resources/badusb/assets/layouts/fr-CA.kl | Bin .../resources/badusb/assets/layouts/fr-CH.kl | Bin .../badusb/assets/layouts/fr-FR-mac.kl | Bin .../resources/badusb/assets/layouts/fr-FR.kl | Bin .../resources/badusb/assets/layouts/hr-HR.kl | Bin .../resources/badusb/assets/layouts/hu-HU.kl | Bin .../resources/badusb/assets/layouts/it-IT.kl | Bin .../resources/badusb/assets/layouts/nb-NO.kl | Bin .../resources/badusb/assets/layouts/nl-NL.kl | Bin .../resources/badusb/assets/layouts/pt-BR.kl | Bin .../resources/badusb/assets/layouts/pt-PT.kl | Bin .../resources/badusb/assets/layouts/si-SI.kl | Bin .../resources/badusb/assets/layouts/sk-SK.kl | Bin .../resources/badusb/assets/layouts/sv-SE.kl | Bin .../resources/badusb/assets/layouts/tr-TR.kl | Bin .../bad_usb}/resources/badusb/demo_macos.txt | 0 .../resources/badusb/demo_windows.txt | 0 applications/main/infrared/application.fam | 1 + .../infrared}/resources/infrared/assets/ac.ir | 0 .../resources/infrared/assets/audio.ir | 0 .../resources/infrared/assets/projector.ir | 0 .../infrared}/resources/infrared/assets/tv.ir | 0 applications/main/nfc/application.fam | 1 + .../main/nfc}/resources/nfc/assets/aid.nfc | 0 .../resources/nfc/assets/country_code.nfc | 0 .../resources/nfc/assets/currency_code.nfc | 0 .../resources/nfc/assets/mf_classic_dict.nfc | 0 applications/main/subghz/application.fam | 1 + .../resources/subghz/assets/alutech_at_4n | 0 .../resources/subghz/assets/came_atomo | 0 .../resources/subghz/assets/keeloq_mfcodes | 0 .../subghz/assets/keeloq_mfcodes_user.example | 0 .../resources/subghz/assets/nice_flor_s | 0 .../subghz/assets/setting_user.example | 0 applications/main/u2f/application.fam | 1 + .../main/u2f}/resources/u2f/assets/cert.der | Bin .../u2f}/resources/u2f/assets/cert_key.u2f | 0 applications/services/loader/loader.c | 2 +- applications/services/loader/loader.h | 4 +- .../system/snake_game/application.fam | 1 - assets/.gitignore | 4 - assets/ReadMe.md | 3 - assets/SConscript | 54 +++------- documentation/AppManifests.md | 1 + documentation/AppsOnSDCard.md | 2 +- documentation/HardwareTargets.md | 2 +- documentation/UnitTests.md | 13 ++- documentation/UniversalRemotes.md | 6 +- .../file_formats/InfraredFileFormats.md | 8 +- firmware.scons | 38 ++++--- scripts/ReadMe.md | 4 +- scripts/assets.py | 10 +- scripts/fbt/appmanifest.py | 15 ++- scripts/fbt_tools/fbt_assets.py | 90 +++++++++++----- scripts/fbt_tools/fbt_dist.py | 21 +++- scripts/fbt_tools/fbt_extapps.py | 93 +++++++--------- scripts/fbt_tools/fbt_hwtarget.py | 23 ++-- scripts/fbt_tools/fbt_resources.py | 98 +++++++++++++++++ scripts/fbt_tools/fbt_sdk.py | 24 ++--- scripts/fbt_tools/fbt_version.py | 4 +- scripts/fbt_tools/pvsstudio.py | 4 +- scripts/flipper/assets/dolphin.py | 2 +- scripts/meta.py | 60 ----------- .../app_template/.github/workflows/build.yml | 4 +- site_scons/extapps.scons | 4 - {firmware => targets}/ReadMe.md | 0 {firmware => targets}/SConscript | 0 .../targets => targets}/f18/api_symbols.csv | 90 ++++++++-------- .../f18/furi_hal/furi_hal.c | 0 .../f18/furi_hal/furi_hal_power_config.c | 0 .../f18/furi_hal/furi_hal_resources.c | 0 .../f18/furi_hal/furi_hal_resources.h | 0 .../f18/furi_hal/furi_hal_spi_config.c | 0 .../f18/furi_hal/furi_hal_spi_config.h | 0 .../f18/furi_hal/furi_hal_target_hw.h | 0 .../f18/furi_hal/furi_hal_version_device.c | 0 {firmware/targets => targets}/f18/target.json | 0 .../targets => targets}/f7/api_symbols.csv | 100 +++++++++--------- .../targets => targets}/f7/application_ext.ld | 0 .../f7/ble_glue/app_common.h | 0 .../f7/ble_glue/app_conf.h | 0 .../f7/ble_glue/app_debug.c | 0 .../f7/ble_glue/app_debug.h | 0 .../targets => targets}/f7/ble_glue/ble_app.c | 0 .../targets => targets}/f7/ble_glue/ble_app.h | 0 .../f7/ble_glue/ble_conf.h | 0 .../f7/ble_glue/ble_const.h | 0 .../f7/ble_glue/ble_dbg_conf.h | 0 .../f7/ble_glue/ble_glue.c | 0 .../f7/ble_glue/ble_glue.h | 0 .../f7/ble_glue/compiler.h | 0 .../targets => targets}/f7/ble_glue/gap.c | 0 .../targets => targets}/f7/ble_glue/gap.h | 0 .../f7/ble_glue/hsem_map.h | 0 .../targets => targets}/f7/ble_glue/hw_ipcc.c | 0 .../targets => targets}/f7/ble_glue/osal.h | 0 .../f7/ble_glue/services/battery_service.c | 0 .../f7/ble_glue/services/battery_service.h | 0 .../f7/ble_glue/services/dev_info_service.c | 0 .../f7/ble_glue/services/dev_info_service.h | 0 .../services/dev_info_service_uuid.inc | 0 .../f7/ble_glue/services/gatt_char.c | 0 .../f7/ble_glue/services/gatt_char.h | 0 .../f7/ble_glue/services/hid_service.c | 0 .../f7/ble_glue/services/hid_service.h | 0 .../f7/ble_glue/services/serial_service.c | 0 .../f7/ble_glue/services/serial_service.h | 0 .../ble_glue/services/serial_service_uuid.inc | 0 .../f7/ble_glue/tl_dbg_conf.h | 0 .../targets => targets}/f7/fatfs/fatfs.c | 0 .../targets => targets}/f7/fatfs/fatfs.h | 0 .../targets => targets}/f7/fatfs/ffconf.h | 0 .../f7/fatfs/sector_cache.c | 0 .../f7/fatfs/sector_cache.h | 0 .../f7/fatfs/user_diskio.c | 0 .../f7/fatfs/user_diskio.h | 0 .../f7/furi_hal/furi_hal.c | 0 .../f7/furi_hal/furi_hal_bt.c | 0 .../f7/furi_hal/furi_hal_bt_hid.c | 0 .../f7/furi_hal/furi_hal_bt_serial.c | 0 .../f7/furi_hal/furi_hal_bus.c | 0 .../f7/furi_hal/furi_hal_bus.h | 0 .../f7/furi_hal/furi_hal_clock.c | 0 .../f7/furi_hal/furi_hal_clock.h | 0 .../f7/furi_hal/furi_hal_console.c | 0 .../f7/furi_hal/furi_hal_console.h | 0 .../f7/furi_hal/furi_hal_cortex.c | 0 .../f7/furi_hal/furi_hal_crypto.c | 0 .../f7/furi_hal/furi_hal_debug.c | 0 .../f7/furi_hal/furi_hal_dma.c | 0 .../f7/furi_hal/furi_hal_dma.h | 0 .../f7/furi_hal/furi_hal_flash.c | 0 .../f7/furi_hal/furi_hal_flash.h | 0 .../f7/furi_hal/furi_hal_gpio.c | 0 .../f7/furi_hal/furi_hal_gpio.h | 0 .../f7/furi_hal/furi_hal_i2c.c | 0 .../f7/furi_hal/furi_hal_i2c_config.c | 0 .../f7/furi_hal/furi_hal_i2c_config.h | 0 .../f7/furi_hal/furi_hal_i2c_types.h | 0 .../f7/furi_hal/furi_hal_ibutton.c | 0 .../f7/furi_hal/furi_hal_ibutton.h | 0 .../f7/furi_hal/furi_hal_idle_timer.h | 0 .../f7/furi_hal/furi_hal_info.c | 0 .../f7/furi_hal/furi_hal_infrared.c | 0 .../f7/furi_hal/furi_hal_interrupt.c | 0 .../f7/furi_hal/furi_hal_interrupt.h | 0 .../f7/furi_hal/furi_hal_light.c | 0 .../f7/furi_hal/furi_hal_memory.c | 0 .../f7/furi_hal/furi_hal_mpu.c | 0 .../f7/furi_hal/furi_hal_nfc.c | 0 .../f7/furi_hal/furi_hal_nfc_event.c | 0 .../f7/furi_hal/furi_hal_nfc_felica.c | 0 .../f7/furi_hal/furi_hal_nfc_i.h | 0 .../f7/furi_hal/furi_hal_nfc_irq.c | 0 .../f7/furi_hal/furi_hal_nfc_iso14443a.c | 0 .../f7/furi_hal/furi_hal_nfc_iso14443b.c | 0 .../f7/furi_hal/furi_hal_nfc_iso15693.c | 0 .../f7/furi_hal/furi_hal_nfc_tech_i.h | 0 .../f7/furi_hal/furi_hal_nfc_timer.c | 0 .../f7/furi_hal/furi_hal_os.c | 0 .../f7/furi_hal/furi_hal_os.h | 0 .../f7/furi_hal/furi_hal_power.c | 0 .../f7/furi_hal/furi_hal_power_config.c | 0 .../f7/furi_hal/furi_hal_pwm.c | 0 .../f7/furi_hal/furi_hal_pwm.h | 0 .../f7/furi_hal/furi_hal_random.c | 0 .../f7/furi_hal/furi_hal_region.c | 0 .../f7/furi_hal/furi_hal_resources.c | 0 .../f7/furi_hal/furi_hal_resources.h | 0 .../f7/furi_hal/furi_hal_rfid.c | 0 .../f7/furi_hal/furi_hal_rfid.h | 0 .../f7/furi_hal/furi_hal_rtc.c | 0 .../f7/furi_hal/furi_hal_sd.c | 0 .../f7/furi_hal/furi_hal_speaker.c | 0 .../f7/furi_hal/furi_hal_spi.c | 0 .../f7/furi_hal/furi_hal_spi_config.c | 0 .../f7/furi_hal/furi_hal_spi_config.h | 0 .../f7/furi_hal/furi_hal_spi_types.h | 0 .../f7/furi_hal/furi_hal_subghz.c | 0 .../f7/furi_hal/furi_hal_subghz.h | 0 .../f7/furi_hal/furi_hal_target_hw.h | 0 .../f7/furi_hal/furi_hal_uart.c | 0 .../f7/furi_hal/furi_hal_uart.h | 0 .../f7/furi_hal/furi_hal_usb.c | 0 .../f7/furi_hal/furi_hal_usb_ccid.c | 0 .../f7/furi_hal/furi_hal_usb_cdc.c | 0 .../f7/furi_hal/furi_hal_usb_cdc.h | 0 .../f7/furi_hal/furi_hal_usb_hid.c | 0 .../f7/furi_hal/furi_hal_usb_i.h | 0 .../f7/furi_hal/furi_hal_usb_u2f.c | 0 .../f7/furi_hal/furi_hal_version.c | 0 .../f7/furi_hal/furi_hal_version_device.c | 0 .../f7/furi_hal/furi_hal_vibro.c | 0 .../f7/inc/FreeRTOSConfig.h | 0 .../targets => targets}/f7/inc/alt_boot.h | 0 {firmware/targets => targets}/f7/inc/stm32.h | 0 .../targets => targets}/f7/inc/stm32_assert.h | 0 .../f7/platform_specific/intrinsic_export.h | 0 .../f7/platform_specific/math_wrapper.h | 0 {firmware/targets => targets}/f7/src/dfu.c | 0 {firmware/targets => targets}/f7/src/main.c | 0 .../targets => targets}/f7/src/recovery.c | 0 .../f7/src/system_stm32wbxx.c | 0 {firmware/targets => targets}/f7/src/update.c | 0 .../f7/startup_stm32wb55xx_cm4.s | 0 .../f7/stm32wb55xx_flash.ld | 0 .../f7/stm32wb55xx_ram_fw.ld | 0 {firmware/targets => targets}/f7/target.json | 0 .../furi_hal_include/furi_hal.h | 0 .../furi_hal_include/furi_hal_bt.h | 0 .../furi_hal_include/furi_hal_bt_hid.h | 0 .../furi_hal_include/furi_hal_bt_serial.h | 0 .../furi_hal_include/furi_hal_cortex.h | 0 .../furi_hal_include/furi_hal_crypto.h | 0 .../furi_hal_include/furi_hal_debug.h | 0 .../furi_hal_include/furi_hal_i2c.h | 0 .../furi_hal_include/furi_hal_info.h | 0 .../furi_hal_include/furi_hal_infrared.h | 0 .../furi_hal_include/furi_hal_light.h | 0 .../furi_hal_include/furi_hal_memory.h | 0 .../furi_hal_include/furi_hal_mpu.h | 0 .../furi_hal_include/furi_hal_nfc.h | 0 .../furi_hal_include/furi_hal_power.h | 0 .../furi_hal_include/furi_hal_random.h | 0 .../furi_hal_include/furi_hal_region.h | 0 .../furi_hal_include/furi_hal_rtc.h | 0 .../furi_hal_include/furi_hal_sd.h | 0 .../furi_hal_include/furi_hal_speaker.h | 0 .../furi_hal_include/furi_hal_spi.h | 0 .../furi_hal_include/furi_hal_usb.h | 0 .../furi_hal_include/furi_hal_usb_ccid.h | 0 .../furi_hal_include/furi_hal_usb_hid.h | 0 .../furi_hal_include/furi_hal_usb_hid_u2f.h | 0 .../furi_hal_include/furi_hal_version.h | 0 .../furi_hal_include/furi_hal_vibro.h | 0 345 files changed, 466 insertions(+), 394 deletions(-) rename assets/unit_tests/Manifest => applications/debug/unit_tests/resources/unit_tests/Manifest_test (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_kaseikyo.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_nec.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_nec42.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_nec42ext.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_necext.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rc5.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rc5x.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rc6.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rca.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_samsung32.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_sirc.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ntag213_locked.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ntag215.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ntag216.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ultralight_11.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ultralight_21.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/nfc_nfca_signal_long.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/nfc_nfca_signal_short.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/storage/md5.txt (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/alutech_at_4n_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/ansonic.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/ansonic_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/bett.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/bett_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_atomo_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_twee.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_twee_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/cenmax_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/clemsa.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/clemsa_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doitrand.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doitrand_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doorhan.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doorhan_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/dooya.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/dooya_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/faac_slh_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/gate_tx.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/gate_tx_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek_ht12x.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek_ht12x_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/honeywell_wdb.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/honeywell_wdb_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/hormann_hsm_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/ido_117_111_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/intertechno_v3.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/intertechno_v3_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/kia_seed_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/kinggates_stylo4k_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear_delta3.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear_delta3_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/magellan.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/magellan_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/marantec.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/marantec_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/megacode.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/megacode_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nero_radio_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nero_sketch_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_flo.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_flo_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_flor_s_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_one_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/phoenix_v2.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/phoenix_v2_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/power_smart.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/power_smart_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/princeton.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/princeton_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/scher_khan_magic_code.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_1_0.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_1_0_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_2_0.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_2_0_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/smc5326.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/smc5326_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/somfy_keytis_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/somfy_telis_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/test_random_raw.sub (100%) rename {assets => applications/main/bad_usb}/resources/badusb/Install_qFlipper_macOS.txt (100%) rename {assets => applications/main/bad_usb}/resources/badusb/Install_qFlipper_windows.txt (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/ba-BA.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/cz_CS.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/da-DA.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/de-CH.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/de-DE.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/dvorak.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/en-UK.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/en-US.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/es-ES.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-BE.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-CA.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-CH.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-FR-mac.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-FR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/hr-HR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/hu-HU.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/it-IT.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/nb-NO.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/nl-NL.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/pt-BR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/pt-PT.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/si-SI.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/sk-SK.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/sv-SE.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/tr-TR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/demo_macos.txt (100%) rename {assets => applications/main/bad_usb}/resources/badusb/demo_windows.txt (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/ac.ir (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/audio.ir (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/projector.ir (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/tv.ir (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/aid.nfc (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/country_code.nfc (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/currency_code.nfc (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/mf_classic_dict.nfc (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/alutech_at_4n (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/came_atomo (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/keeloq_mfcodes (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/keeloq_mfcodes_user.example (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/nice_flor_s (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/setting_user.example (100%) rename {assets => applications/main/u2f}/resources/u2f/assets/cert.der (100%) rename {assets => applications/main/u2f}/resources/u2f/assets/cert_key.u2f (100%) create mode 100644 scripts/fbt_tools/fbt_resources.py delete mode 100755 scripts/meta.py rename {firmware => targets}/ReadMe.md (100%) rename {firmware => targets}/SConscript (100%) rename {firmware/targets => targets}/f18/api_symbols.csv (98%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_power_config.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_resources.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_resources.h (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_spi_config.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_spi_config.h (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_target_hw.h (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_version_device.c (100%) rename {firmware/targets => targets}/f18/target.json (100%) rename {firmware/targets => targets}/f7/api_symbols.csv (98%) rename {firmware/targets => targets}/f7/application_ext.ld (100%) rename {firmware/targets => targets}/f7/ble_glue/app_common.h (100%) rename {firmware/targets => targets}/f7/ble_glue/app_conf.h (100%) rename {firmware/targets => targets}/f7/ble_glue/app_debug.c (100%) rename {firmware/targets => targets}/f7/ble_glue/app_debug.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_app.c (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_app.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_conf.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_const.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_dbg_conf.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_glue.c (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_glue.h (100%) rename {firmware/targets => targets}/f7/ble_glue/compiler.h (100%) rename {firmware/targets => targets}/f7/ble_glue/gap.c (100%) rename {firmware/targets => targets}/f7/ble_glue/gap.h (100%) rename {firmware/targets => targets}/f7/ble_glue/hsem_map.h (100%) rename {firmware/targets => targets}/f7/ble_glue/hw_ipcc.c (100%) rename {firmware/targets => targets}/f7/ble_glue/osal.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/battery_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/battery_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/dev_info_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/dev_info_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/dev_info_service_uuid.inc (100%) rename {firmware/targets => targets}/f7/ble_glue/services/gatt_char.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/gatt_char.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/hid_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/hid_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/serial_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/serial_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/serial_service_uuid.inc (100%) rename {firmware/targets => targets}/f7/ble_glue/tl_dbg_conf.h (100%) rename {firmware/targets => targets}/f7/fatfs/fatfs.c (100%) rename {firmware/targets => targets}/f7/fatfs/fatfs.h (100%) rename {firmware/targets => targets}/f7/fatfs/ffconf.h (100%) rename {firmware/targets => targets}/f7/fatfs/sector_cache.c (100%) rename {firmware/targets => targets}/f7/fatfs/sector_cache.h (100%) rename {firmware/targets => targets}/f7/fatfs/user_diskio.c (100%) rename {firmware/targets => targets}/f7/fatfs/user_diskio.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bt.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bt_hid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bt_serial.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bus.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bus.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_clock.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_clock.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_console.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_console.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_cortex.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_crypto.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_debug.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_dma.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_dma.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_flash.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_flash.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_gpio.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_gpio.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c_config.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c_config.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c_types.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_ibutton.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_ibutton.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_idle_timer.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_info.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_infrared.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_interrupt.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_interrupt.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_light.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_memory.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_mpu.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_event.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_felica.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_i.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_irq.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_iso14443a.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_iso14443b.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_iso15693.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_tech_i.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_timer.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_os.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_os.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_power.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_power_config.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_pwm.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_pwm.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_random.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_region.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_resources.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_resources.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_rfid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_rfid.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_rtc.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_sd.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_speaker.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi_config.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi_config.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi_types.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_subghz.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_subghz.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_target_hw.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_uart.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_uart.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_ccid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_cdc.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_cdc.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_hid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_i.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_u2f.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_version.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_version_device.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_vibro.c (100%) rename {firmware/targets => targets}/f7/inc/FreeRTOSConfig.h (100%) rename {firmware/targets => targets}/f7/inc/alt_boot.h (100%) rename {firmware/targets => targets}/f7/inc/stm32.h (100%) rename {firmware/targets => targets}/f7/inc/stm32_assert.h (100%) rename {firmware/targets => targets}/f7/platform_specific/intrinsic_export.h (100%) rename {firmware/targets => targets}/f7/platform_specific/math_wrapper.h (100%) rename {firmware/targets => targets}/f7/src/dfu.c (100%) rename {firmware/targets => targets}/f7/src/main.c (100%) rename {firmware/targets => targets}/f7/src/recovery.c (100%) rename {firmware/targets => targets}/f7/src/system_stm32wbxx.c (100%) rename {firmware/targets => targets}/f7/src/update.c (100%) rename {firmware/targets => targets}/f7/startup_stm32wb55xx_cm4.s (100%) rename {firmware/targets => targets}/f7/stm32wb55xx_flash.ld (100%) rename {firmware/targets => targets}/f7/stm32wb55xx_ram_fw.ld (100%) rename {firmware/targets => targets}/f7/target.json (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_bt.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_bt_hid.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_bt_serial.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_cortex.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_crypto.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_debug.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_i2c.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_info.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_infrared.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_light.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_memory.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_mpu.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_nfc.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_power.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_random.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_region.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_rtc.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_sd.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_speaker.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_spi.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb_ccid.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb_hid.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb_hid_u2f.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_version.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_vibro.h (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e02c931af9..b72d9ea613 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -42,10 +42,10 @@ /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov # Firmware targets -/firmware/ @skotopes @DrZlo13 @hedger @nminaylov +/targets/ @skotopes @DrZlo13 @hedger @nminaylov # Assets -/assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov +/applications/main/infrared/resources/ @skotopes @DrZlo13 @hedger @gsurkov # Documentation /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c55350665..38b1d7b688 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} @@ -51,11 +51,11 @@ jobs: - name: 'Check API versions for consistency between targets' run: | set -e - N_API_HEADER_SIGNATURES=`ls -1 firmware/targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` + N_API_HEADER_SIGNATURES=`ls -1 targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` if [ $N_API_HEADER_SIGNATURES != 1 ] ; then echo API versions aren\'t matching for available targets. Please update! echo API versions are: - head -n2 firmware/targets/f*/api_symbols.csv + head -n2 targets/f*/api_symbols.csv exit 1 fi @@ -76,7 +76,7 @@ jobs: mkdir artifacts map_analyser_files cp dist/${TARGET}-*/* artifacts/ || true tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \ - -C assets resources + -C build/latest resources tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \ -C dist/${TARGET}-*/apps/Debug . tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \ diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index c63be24a47..f45275204a 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -19,7 +19,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 submodules: false @@ -46,7 +46,7 @@ jobs: echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT - name: Deploy uFBT with SDK - uses: flipperdevices/flipperzero-ufbt-action@v0.1.0 + uses: flipperdevices/flipperzero-ufbt-action@v0.1 with: task: setup sdk-file: ${{ steps.build-fw.outputs.sdk-file }} diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index d24422b7cb..51e2593ecc 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -16,7 +16,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 ref: ${{ github.sha }} diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index fedc4b87fb..90302ce1aa 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -16,7 +16,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 4f650f1b7c..4527e29207 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -21,7 +21,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 2680f520c1..40f72bd0ba 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -17,7 +17,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} @@ -32,7 +32,7 @@ jobs: if: success() timeout-minutes: 10 run: | - ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt resources firmware_latest flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' id: format_ext @@ -50,8 +50,8 @@ jobs: run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/resources /ext - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/unit_tests /ext/unit_tests + rm -rf build/latest/resources/dolphin + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send build/latest/resources /ext python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 9987fdd324..b14b618a6e 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -17,7 +17,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 submodules: false @@ -56,7 +56,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout latest release' - uses: actions/checkout@v3 + uses: actions/checkout@v4 if: failure() with: fetch-depth: 1 diff --git a/SConstruct b/SConstruct index 36e0600235..faccdfa5b3 100644 --- a/SConstruct +++ b/SConstruct @@ -67,22 +67,22 @@ if GetOption("fullenv") or any( # Target for self-update package dist_basic_arguments = [ "--bundlever", - '"${UPDATE_VERSION_STRING}"', + "${UPDATE_VERSION_STRING}", ] dist_radio_arguments = [ "--radio", - '"${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}"', + "${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}", "--radiotype", "${COPRO_STACK_TYPE}", "${COPRO_DISCLAIMER}", "--obdata", - '"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"', + "${ROOT_DIR.abspath}/${COPRO_OB_DATA}", "--stackversion", "${COPRO_CUBE_VERSION}", ] dist_resource_arguments = [ "-r", - '"${ROOT_DIR.abspath}/assets/resources"', + firmware_env.subst("${RESOURCES_ROOT}"), ] dist_splash_arguments = ( [ @@ -95,7 +95,7 @@ if GetOption("fullenv") or any( selfupdate_dist = distenv.DistCommand( "updater_package", - (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]), + (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES_MANIFEST"]), DIST_EXTRA=[ *dist_basic_arguments, *dist_radio_arguments, @@ -128,7 +128,8 @@ if GetOption("fullenv") or any( # Installation over USB & CLI usb_update_package = distenv.AddUsbFlashTarget( - "#build/usbinstall.flag", (firmware_env["FW_RESOURCES"], selfupdate_dist) + "#build/usbinstall.flag", + (firmware_env["FW_RESOURCES_MANIFEST"], selfupdate_dist), ) distenv.Alias("flash_usb_full", usb_update_package) @@ -166,16 +167,23 @@ Depends( list(app_artifact.validator for app_artifact in external_app_list), ) Alias("fap_dist", fap_dist) -# distenv.Default(fap_dist) - -distenv.Depends(firmware_env["FW_RESOURCES"], external_apps_artifacts.resources_dist) # Copy all faps to device fap_deploy = distenv.PhonyTarget( "fap_deploy", - "${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send ${SOURCE} /ext/apps", - source=Dir("#/assets/resources/apps"), + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/storage.py", + "-p", + "${FLIP_PORT}", + "send", + "${SOURCE}", + "/ext/apps", + ] + ], + source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")), ) @@ -314,9 +322,7 @@ 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") diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index ad9a278f38..aa25dab279 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -5,6 +5,7 @@ App( cdefines=["APP_UNIT_TESTS"], requires=["system_settings"], provides=["delay_test"], + resources="resources", order=100, ) diff --git a/applications/debug/unit_tests/manifest/manifest.c b/applications/debug/unit_tests/manifest/manifest.c index 0b24ad1ed6..19370b0e13 100644 --- a/applications/debug/unit_tests/manifest/manifest.c +++ b/applications/debug/unit_tests/manifest/manifest.c @@ -22,7 +22,7 @@ MU_TEST(manifest_iteration_test) { ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(storage); do { // Open manifest file - if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest"))) { + if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest_test"))) { result = false; break; } diff --git a/assets/unit_tests/Manifest b/applications/debug/unit_tests/resources/unit_tests/Manifest_test similarity index 100% rename from assets/unit_tests/Manifest rename to applications/debug/unit_tests/resources/unit_tests/Manifest_test diff --git a/assets/unit_tests/infrared/test_kaseikyo.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_kaseikyo.irtest similarity index 100% rename from assets/unit_tests/infrared/test_kaseikyo.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_kaseikyo.irtest diff --git a/assets/unit_tests/infrared/test_nec.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_nec.irtest similarity index 100% rename from assets/unit_tests/infrared/test_nec.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_nec.irtest diff --git a/assets/unit_tests/infrared/test_nec42.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42.irtest similarity index 100% rename from assets/unit_tests/infrared/test_nec42.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42.irtest diff --git a/assets/unit_tests/infrared/test_nec42ext.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42ext.irtest similarity index 100% rename from assets/unit_tests/infrared/test_nec42ext.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42ext.irtest diff --git a/assets/unit_tests/infrared/test_necext.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_necext.irtest similarity index 100% rename from assets/unit_tests/infrared/test_necext.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_necext.irtest diff --git a/assets/unit_tests/infrared/test_rc5.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rc5.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5.irtest diff --git a/assets/unit_tests/infrared/test_rc5x.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5x.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rc5x.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5x.irtest diff --git a/assets/unit_tests/infrared/test_rc6.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rc6.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rc6.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rc6.irtest diff --git a/assets/unit_tests/infrared/test_rca.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rca.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rca.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rca.irtest diff --git a/assets/unit_tests/infrared/test_samsung32.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_samsung32.irtest similarity index 100% rename from assets/unit_tests/infrared/test_samsung32.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_samsung32.irtest diff --git a/assets/unit_tests/infrared/test_sirc.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_sirc.irtest similarity index 100% rename from assets/unit_tests/infrared/test_sirc.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_sirc.irtest diff --git a/assets/unit_tests/nfc/Ntag213_locked.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ntag213_locked.nfc similarity index 100% rename from assets/unit_tests/nfc/Ntag213_locked.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ntag213_locked.nfc diff --git a/assets/unit_tests/nfc/Ntag215.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ntag215.nfc similarity index 100% rename from assets/unit_tests/nfc/Ntag215.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ntag215.nfc diff --git a/assets/unit_tests/nfc/Ntag216.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ntag216.nfc similarity index 100% rename from assets/unit_tests/nfc/Ntag216.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ntag216.nfc diff --git a/assets/unit_tests/nfc/Ultralight_11.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_11.nfc similarity index 100% rename from assets/unit_tests/nfc/Ultralight_11.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_11.nfc diff --git a/assets/unit_tests/nfc/Ultralight_21.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_21.nfc similarity index 100% rename from assets/unit_tests/nfc/Ultralight_21.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_21.nfc diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_long.nfc similarity index 100% rename from assets/unit_tests/nfc/nfc_nfca_signal_long.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_long.nfc diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_short.nfc similarity index 100% rename from assets/unit_tests/nfc/nfc_nfca_signal_short.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_short.nfc diff --git a/assets/unit_tests/storage/md5.txt b/applications/debug/unit_tests/resources/unit_tests/storage/md5.txt similarity index 100% rename from assets/unit_tests/storage/md5.txt rename to applications/debug/unit_tests/resources/unit_tests/storage/md5.txt diff --git a/assets/unit_tests/subghz/alutech_at_4n_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/alutech_at_4n_raw.sub similarity index 100% rename from assets/unit_tests/subghz/alutech_at_4n_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/alutech_at_4n_raw.sub diff --git a/assets/unit_tests/subghz/ansonic.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/ansonic.sub similarity index 100% rename from assets/unit_tests/subghz/ansonic.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/ansonic.sub diff --git a/assets/unit_tests/subghz/ansonic_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/ansonic_raw.sub similarity index 100% rename from assets/unit_tests/subghz/ansonic_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/ansonic_raw.sub diff --git a/assets/unit_tests/subghz/bett.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/bett.sub similarity index 100% rename from assets/unit_tests/subghz/bett.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/bett.sub diff --git a/assets/unit_tests/subghz/bett_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/bett_raw.sub similarity index 100% rename from assets/unit_tests/subghz/bett_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/bett_raw.sub diff --git a/assets/unit_tests/subghz/came.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came.sub similarity index 100% rename from assets/unit_tests/subghz/came.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came.sub diff --git a/assets/unit_tests/subghz/came_atomo_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_atomo_raw.sub similarity index 100% rename from assets/unit_tests/subghz/came_atomo_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_atomo_raw.sub diff --git a/assets/unit_tests/subghz/came_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_raw.sub similarity index 100% rename from assets/unit_tests/subghz/came_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_raw.sub diff --git a/assets/unit_tests/subghz/came_twee.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_twee.sub similarity index 100% rename from assets/unit_tests/subghz/came_twee.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_twee.sub diff --git a/assets/unit_tests/subghz/came_twee_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_twee_raw.sub similarity index 100% rename from assets/unit_tests/subghz/came_twee_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_twee_raw.sub diff --git a/assets/unit_tests/subghz/cenmax_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/cenmax_raw.sub similarity index 100% rename from assets/unit_tests/subghz/cenmax_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/cenmax_raw.sub diff --git a/assets/unit_tests/subghz/clemsa.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/clemsa.sub similarity index 100% rename from assets/unit_tests/subghz/clemsa.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/clemsa.sub diff --git a/assets/unit_tests/subghz/clemsa_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/clemsa_raw.sub similarity index 100% rename from assets/unit_tests/subghz/clemsa_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/clemsa_raw.sub diff --git a/assets/unit_tests/subghz/doitrand.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doitrand.sub similarity index 100% rename from assets/unit_tests/subghz/doitrand.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doitrand.sub diff --git a/assets/unit_tests/subghz/doitrand_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doitrand_raw.sub similarity index 100% rename from assets/unit_tests/subghz/doitrand_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doitrand_raw.sub diff --git a/assets/unit_tests/subghz/doorhan.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doorhan.sub similarity index 100% rename from assets/unit_tests/subghz/doorhan.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doorhan.sub diff --git a/assets/unit_tests/subghz/doorhan_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doorhan_raw.sub similarity index 100% rename from assets/unit_tests/subghz/doorhan_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doorhan_raw.sub diff --git a/assets/unit_tests/subghz/dooya.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/dooya.sub similarity index 100% rename from assets/unit_tests/subghz/dooya.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/dooya.sub diff --git a/assets/unit_tests/subghz/dooya_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/dooya_raw.sub similarity index 100% rename from assets/unit_tests/subghz/dooya_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/dooya_raw.sub diff --git a/assets/unit_tests/subghz/faac_slh_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/faac_slh_raw.sub similarity index 100% rename from assets/unit_tests/subghz/faac_slh_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/faac_slh_raw.sub diff --git a/assets/unit_tests/subghz/gate_tx.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx.sub similarity index 100% rename from assets/unit_tests/subghz/gate_tx.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx.sub diff --git a/assets/unit_tests/subghz/gate_tx_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx_raw.sub similarity index 100% rename from assets/unit_tests/subghz/gate_tx_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx_raw.sub diff --git a/assets/unit_tests/subghz/holtek.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek.sub similarity index 100% rename from assets/unit_tests/subghz/holtek.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek.sub diff --git a/assets/unit_tests/subghz/holtek_ht12x.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x.sub similarity index 100% rename from assets/unit_tests/subghz/holtek_ht12x.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x.sub diff --git a/assets/unit_tests/subghz/holtek_ht12x_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x_raw.sub similarity index 100% rename from assets/unit_tests/subghz/holtek_ht12x_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x_raw.sub diff --git a/assets/unit_tests/subghz/holtek_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek_raw.sub similarity index 100% rename from assets/unit_tests/subghz/holtek_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek_raw.sub diff --git a/assets/unit_tests/subghz/honeywell_wdb.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb.sub similarity index 100% rename from assets/unit_tests/subghz/honeywell_wdb.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb.sub diff --git a/assets/unit_tests/subghz/honeywell_wdb_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb_raw.sub similarity index 100% rename from assets/unit_tests/subghz/honeywell_wdb_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb_raw.sub diff --git a/assets/unit_tests/subghz/hormann_hsm_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/hormann_hsm_raw.sub similarity index 100% rename from assets/unit_tests/subghz/hormann_hsm_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/hormann_hsm_raw.sub diff --git a/assets/unit_tests/subghz/ido_117_111_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/ido_117_111_raw.sub similarity index 100% rename from assets/unit_tests/subghz/ido_117_111_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/ido_117_111_raw.sub diff --git a/assets/unit_tests/subghz/intertechno_v3.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3.sub similarity index 100% rename from assets/unit_tests/subghz/intertechno_v3.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3.sub diff --git a/assets/unit_tests/subghz/intertechno_v3_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3_raw.sub similarity index 100% rename from assets/unit_tests/subghz/intertechno_v3_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3_raw.sub diff --git a/assets/unit_tests/subghz/kia_seed_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/kia_seed_raw.sub similarity index 100% rename from assets/unit_tests/subghz/kia_seed_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/kia_seed_raw.sub diff --git a/assets/unit_tests/subghz/kinggates_stylo4k_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/kinggates_stylo4k_raw.sub similarity index 100% rename from assets/unit_tests/subghz/kinggates_stylo4k_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/kinggates_stylo4k_raw.sub diff --git a/assets/unit_tests/subghz/linear.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear.sub similarity index 100% rename from assets/unit_tests/subghz/linear.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear.sub diff --git a/assets/unit_tests/subghz/linear_delta3.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3.sub similarity index 100% rename from assets/unit_tests/subghz/linear_delta3.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3.sub diff --git a/assets/unit_tests/subghz/linear_delta3_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3_raw.sub similarity index 100% rename from assets/unit_tests/subghz/linear_delta3_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3_raw.sub diff --git a/assets/unit_tests/subghz/linear_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear_raw.sub similarity index 100% rename from assets/unit_tests/subghz/linear_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear_raw.sub diff --git a/assets/unit_tests/subghz/magellan.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/magellan.sub similarity index 100% rename from assets/unit_tests/subghz/magellan.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/magellan.sub diff --git a/assets/unit_tests/subghz/magellan_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/magellan_raw.sub similarity index 100% rename from assets/unit_tests/subghz/magellan_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/magellan_raw.sub diff --git a/assets/unit_tests/subghz/marantec.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec.sub similarity index 100% rename from assets/unit_tests/subghz/marantec.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/marantec.sub diff --git a/assets/unit_tests/subghz/marantec_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec_raw.sub similarity index 100% rename from assets/unit_tests/subghz/marantec_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/marantec_raw.sub diff --git a/assets/unit_tests/subghz/megacode.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/megacode.sub similarity index 100% rename from assets/unit_tests/subghz/megacode.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/megacode.sub diff --git a/assets/unit_tests/subghz/megacode_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/megacode_raw.sub similarity index 100% rename from assets/unit_tests/subghz/megacode_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/megacode_raw.sub diff --git a/assets/unit_tests/subghz/nero_radio_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nero_radio_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nero_radio_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nero_radio_raw.sub diff --git a/assets/unit_tests/subghz/nero_sketch_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nero_sketch_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nero_sketch_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nero_sketch_raw.sub diff --git a/assets/unit_tests/subghz/nice_flo.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo.sub similarity index 100% rename from assets/unit_tests/subghz/nice_flo.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo.sub diff --git a/assets/unit_tests/subghz/nice_flo_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nice_flo_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo_raw.sub diff --git a/assets/unit_tests/subghz/nice_flor_s_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_flor_s_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nice_flor_s_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_flor_s_raw.sub diff --git a/assets/unit_tests/subghz/nice_one_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_one_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nice_one_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_one_raw.sub diff --git a/assets/unit_tests/subghz/phoenix_v2.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2.sub similarity index 100% rename from assets/unit_tests/subghz/phoenix_v2.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2.sub diff --git a/assets/unit_tests/subghz/phoenix_v2_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2_raw.sub similarity index 100% rename from assets/unit_tests/subghz/phoenix_v2_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2_raw.sub diff --git a/assets/unit_tests/subghz/power_smart.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/power_smart.sub similarity index 100% rename from assets/unit_tests/subghz/power_smart.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/power_smart.sub diff --git a/assets/unit_tests/subghz/power_smart_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/power_smart_raw.sub similarity index 100% rename from assets/unit_tests/subghz/power_smart_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/power_smart_raw.sub diff --git a/assets/unit_tests/subghz/princeton.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/princeton.sub similarity index 100% rename from assets/unit_tests/subghz/princeton.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/princeton.sub diff --git a/assets/unit_tests/subghz/princeton_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/princeton_raw.sub similarity index 100% rename from assets/unit_tests/subghz/princeton_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/princeton_raw.sub diff --git a/assets/unit_tests/subghz/scher_khan_magic_code.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/scher_khan_magic_code.sub similarity index 100% rename from assets/unit_tests/subghz/scher_khan_magic_code.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/scher_khan_magic_code.sub diff --git a/assets/unit_tests/subghz/security_pls_1_0.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_1_0.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0.sub diff --git a/assets/unit_tests/subghz/security_pls_1_0_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0_raw.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_1_0_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0_raw.sub diff --git a/assets/unit_tests/subghz/security_pls_2_0.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_2_0.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0.sub diff --git a/assets/unit_tests/subghz/security_pls_2_0_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0_raw.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_2_0_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0_raw.sub diff --git a/assets/unit_tests/subghz/smc5326.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/smc5326.sub similarity index 100% rename from assets/unit_tests/subghz/smc5326.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/smc5326.sub diff --git a/assets/unit_tests/subghz/smc5326_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/smc5326_raw.sub similarity index 100% rename from assets/unit_tests/subghz/smc5326_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/smc5326_raw.sub diff --git a/assets/unit_tests/subghz/somfy_keytis_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/somfy_keytis_raw.sub similarity index 100% rename from assets/unit_tests/subghz/somfy_keytis_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/somfy_keytis_raw.sub diff --git a/assets/unit_tests/subghz/somfy_telis_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/somfy_telis_raw.sub similarity index 100% rename from assets/unit_tests/subghz/somfy_telis_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/somfy_telis_raw.sub diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/test_random_raw.sub similarity index 100% rename from assets/unit_tests/subghz/test_random_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/test_random_raw.sub diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 5c42c9fa3f..9844e248df 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -6,6 +6,7 @@ App( stack_size=2 * 1024, icon="A_BadUsb_14", order=70, + resources="resources", fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", diff --git a/assets/resources/badusb/Install_qFlipper_macOS.txt b/applications/main/bad_usb/resources/badusb/Install_qFlipper_macOS.txt similarity index 100% rename from assets/resources/badusb/Install_qFlipper_macOS.txt rename to applications/main/bad_usb/resources/badusb/Install_qFlipper_macOS.txt diff --git a/assets/resources/badusb/Install_qFlipper_windows.txt b/applications/main/bad_usb/resources/badusb/Install_qFlipper_windows.txt similarity index 100% rename from assets/resources/badusb/Install_qFlipper_windows.txt rename to applications/main/bad_usb/resources/badusb/Install_qFlipper_windows.txt diff --git a/assets/resources/badusb/assets/layouts/ba-BA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/ba-BA.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/ba-BA.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/ba-BA.kl diff --git a/assets/resources/badusb/assets/layouts/cz_CS.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/cz_CS.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/cz_CS.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/cz_CS.kl diff --git a/assets/resources/badusb/assets/layouts/da-DA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/da-DA.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/da-DA.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/da-DA.kl diff --git a/assets/resources/badusb/assets/layouts/de-CH.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/de-CH.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/de-CH.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/de-CH.kl diff --git a/assets/resources/badusb/assets/layouts/de-DE.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/de-DE.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/de-DE.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/de-DE.kl diff --git a/assets/resources/badusb/assets/layouts/dvorak.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/dvorak.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/dvorak.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/dvorak.kl diff --git a/assets/resources/badusb/assets/layouts/en-UK.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/en-UK.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/en-UK.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/en-UK.kl diff --git a/assets/resources/badusb/assets/layouts/en-US.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/en-US.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/en-US.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/en-US.kl diff --git a/assets/resources/badusb/assets/layouts/es-ES.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/es-ES.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/es-ES.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/es-ES.kl diff --git a/assets/resources/badusb/assets/layouts/fr-BE.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-BE.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-BE.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-BE.kl diff --git a/assets/resources/badusb/assets/layouts/fr-CA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-CA.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-CA.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-CA.kl diff --git a/assets/resources/badusb/assets/layouts/fr-CH.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-CH.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-CH.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-CH.kl diff --git a/assets/resources/badusb/assets/layouts/fr-FR-mac.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR-mac.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-FR-mac.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR-mac.kl diff --git a/assets/resources/badusb/assets/layouts/fr-FR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-FR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR.kl diff --git a/assets/resources/badusb/assets/layouts/hr-HR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/hr-HR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/hr-HR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/hr-HR.kl diff --git a/assets/resources/badusb/assets/layouts/hu-HU.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/hu-HU.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/hu-HU.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/hu-HU.kl diff --git a/assets/resources/badusb/assets/layouts/it-IT.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/it-IT.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/it-IT.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/it-IT.kl diff --git a/assets/resources/badusb/assets/layouts/nb-NO.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/nb-NO.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/nb-NO.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/nb-NO.kl diff --git a/assets/resources/badusb/assets/layouts/nl-NL.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/nl-NL.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/nl-NL.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/nl-NL.kl diff --git a/assets/resources/badusb/assets/layouts/pt-BR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/pt-BR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/pt-BR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/pt-BR.kl diff --git a/assets/resources/badusb/assets/layouts/pt-PT.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/pt-PT.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/pt-PT.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/pt-PT.kl diff --git a/assets/resources/badusb/assets/layouts/si-SI.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/si-SI.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/si-SI.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/si-SI.kl diff --git a/assets/resources/badusb/assets/layouts/sk-SK.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/sk-SK.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/sk-SK.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/sk-SK.kl diff --git a/assets/resources/badusb/assets/layouts/sv-SE.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/sv-SE.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/sv-SE.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/sv-SE.kl diff --git a/assets/resources/badusb/assets/layouts/tr-TR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/tr-TR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/tr-TR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/tr-TR.kl diff --git a/assets/resources/badusb/demo_macos.txt b/applications/main/bad_usb/resources/badusb/demo_macos.txt similarity index 100% rename from assets/resources/badusb/demo_macos.txt rename to applications/main/bad_usb/resources/badusb/demo_macos.txt diff --git a/assets/resources/badusb/demo_windows.txt b/applications/main/bad_usb/resources/badusb/demo_windows.txt similarity index 100% rename from assets/resources/badusb/demo_windows.txt rename to applications/main/bad_usb/resources/badusb/demo_windows.txt diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index b78b088a72..055d6c3e29 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -7,6 +7,7 @@ App( icon="A_Infrared_14", stack_size=3 * 1024, order=40, + resources="resources", fap_libs=["assets"], fap_icon="icon.png", fap_category="Infrared", diff --git a/assets/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir similarity index 100% rename from assets/resources/infrared/assets/ac.ir rename to applications/main/infrared/resources/infrared/assets/ac.ir diff --git a/assets/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir similarity index 100% rename from assets/resources/infrared/assets/audio.ir rename to applications/main/infrared/resources/infrared/assets/audio.ir diff --git a/assets/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir similarity index 100% rename from assets/resources/infrared/assets/projector.ir rename to applications/main/infrared/resources/infrared/assets/projector.ir diff --git a/assets/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir similarity index 100% rename from assets/resources/infrared/assets/tv.ir rename to applications/main/infrared/resources/infrared/assets/tv.ir diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 20ebd4ca08..3c8dab2bf1 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -7,6 +7,7 @@ App( icon="A_NFC_14", stack_size=5 * 1024, order=30, + resources="resources", fap_libs=["assets"], fap_icon="icon.png", fap_category="NFC", diff --git a/assets/resources/nfc/assets/aid.nfc b/applications/main/nfc/resources/nfc/assets/aid.nfc similarity index 100% rename from assets/resources/nfc/assets/aid.nfc rename to applications/main/nfc/resources/nfc/assets/aid.nfc diff --git a/assets/resources/nfc/assets/country_code.nfc b/applications/main/nfc/resources/nfc/assets/country_code.nfc similarity index 100% rename from assets/resources/nfc/assets/country_code.nfc rename to applications/main/nfc/resources/nfc/assets/country_code.nfc diff --git a/assets/resources/nfc/assets/currency_code.nfc b/applications/main/nfc/resources/nfc/assets/currency_code.nfc similarity index 100% rename from assets/resources/nfc/assets/currency_code.nfc rename to applications/main/nfc/resources/nfc/assets/currency_code.nfc diff --git a/assets/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc similarity index 100% rename from assets/resources/nfc/assets/mf_classic_dict.nfc rename to applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 4f21cb6c4e..ba9b16abfd 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -7,6 +7,7 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, + resources="resources", fap_libs=["assets", "hwdrivers"], fap_icon="icon.png", fap_category="Sub-GHz", diff --git a/assets/resources/subghz/assets/alutech_at_4n b/applications/main/subghz/resources/subghz/assets/alutech_at_4n similarity index 100% rename from assets/resources/subghz/assets/alutech_at_4n rename to applications/main/subghz/resources/subghz/assets/alutech_at_4n diff --git a/assets/resources/subghz/assets/came_atomo b/applications/main/subghz/resources/subghz/assets/came_atomo similarity index 100% rename from assets/resources/subghz/assets/came_atomo rename to applications/main/subghz/resources/subghz/assets/came_atomo diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes similarity index 100% rename from assets/resources/subghz/assets/keeloq_mfcodes rename to applications/main/subghz/resources/subghz/assets/keeloq_mfcodes diff --git a/assets/resources/subghz/assets/keeloq_mfcodes_user.example b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example similarity index 100% rename from assets/resources/subghz/assets/keeloq_mfcodes_user.example rename to applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example diff --git a/assets/resources/subghz/assets/nice_flor_s b/applications/main/subghz/resources/subghz/assets/nice_flor_s similarity index 100% rename from assets/resources/subghz/assets/nice_flor_s rename to applications/main/subghz/resources/subghz/assets/nice_flor_s diff --git a/assets/resources/subghz/assets/setting_user.example b/applications/main/subghz/resources/subghz/assets/setting_user.example similarity index 100% rename from assets/resources/subghz/assets/setting_user.example rename to applications/main/subghz/resources/subghz/assets/setting_user.example diff --git a/applications/main/u2f/application.fam b/applications/main/u2f/application.fam index 8167e6277b..bf41eb0f7a 100644 --- a/applications/main/u2f/application.fam +++ b/applications/main/u2f/application.fam @@ -6,6 +6,7 @@ App( stack_size=2 * 1024, icon="A_U2F_14", order=80, + resources="resources", fap_libs=["assets"], fap_category="USB", fap_icon="icon.png", diff --git a/assets/resources/u2f/assets/cert.der b/applications/main/u2f/resources/u2f/assets/cert.der similarity index 100% rename from assets/resources/u2f/assets/cert.der rename to applications/main/u2f/resources/u2f/assets/cert.der diff --git a/assets/resources/u2f/assets/cert_key.u2f b/applications/main/u2f/resources/u2f/assets/cert_key.u2f similarity index 100% rename from assets/resources/u2f/assets/cert_key.u2f rename to applications/main/u2f/resources/u2f/assets/cert_key.u2f diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 53f70a1ec6..29ec86ac66 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -179,7 +179,7 @@ static FlipperInternalApplication const* loader_find_application_by_name_in_list const FlipperInternalApplication* list, const uint32_t n_apps) { for(size_t i = 0; i < n_apps; i++) { - if(strcmp(name, list[i].name) == 0) { + if((strcmp(name, list[i].name) == 0) || (strcmp(name, list[i].appid) == 0)) { return &list[i]; } } diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 3da676e651..cca65628f8 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -29,7 +29,7 @@ typedef struct { /** * @brief Start application * @param[in] instance loader instance - * @param[in] name application name + * @param[in] name application name or id * @param[in] args application arguments * @param[out] error_message detailed error message, can be NULL * @return LoaderStatus @@ -40,7 +40,7 @@ LoaderStatus /** * @brief Start application with GUI error message * @param[in] instance loader instance - * @param[in] name application name + * @param[in] name application name or id * @param[in] args application arguments * @return LoaderStatus */ diff --git a/applications/system/snake_game/application.fam b/applications/system/snake_game/application.fam index 9a99ebac84..9e803f65db 100644 --- a/applications/system/snake_game/application.fam +++ b/applications/system/snake_game/application.fam @@ -5,7 +5,6 @@ App( entry_point="snake_game_app", requires=["gui"], stack_size=1 * 1024, - targets=["f7"], fap_version="1.0", fap_description="Classic Snake Game", fap_icon="snake_10px.png", diff --git a/assets/.gitignore b/assets/.gitignore index a66a6eed4c..ca338d6333 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -1,5 +1 @@ /core2_firmware -/resources/Manifest -/resources/apps/* -/resources/dolphin/* -/resources/apps_data/**/*.fal diff --git a/assets/ReadMe.md b/assets/ReadMe.md index 2d493b4fec..84310e731f 100644 --- a/assets/ReadMe.md +++ b/assets/ReadMe.md @@ -32,10 +32,7 @@ Good starting point: https://docs.unrealengine.com/4.27/en-US/ProductionPipeline Don't include assets that you are not using, compiler is not going to strip unused assets. # Structure -- `compiled` - Output folder made for compiled assets, after building project, in `build` directory. - `dolphin` - Dolphin game assets sources. Goes to `compiled` and `resources` folders in `build` directory. - `icons` - Icons sources. Goes to `compiled` folder in `build` directory. - `protobuf` - Protobuf sources. Goes to `compiled` folder in `build` directory. -- `resources` - Assets that is going to be provisioned to SD card. - `slideshow` - One-time slideshows for desktop -- `unit_tests` - Some pre-defined signals for testing purposes. diff --git a/assets/SConscript b/assets/SConscript index 9bd273626d..c10de78af7 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -1,16 +1,16 @@ -from fbt.version import get_git_commit_unix_timestamp - Import("env") assetsenv = env.Clone( tools=["fbt_assets"], FW_LIB_NAME="assets", - GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), + ASSETS_WORK_DIR=env.Dir("compiled"), + ASSETS_SRC_DIR=env.Dir("#/assets"), ) assetsenv.ApplyLibFlags() icons = assetsenv.CompileIcons( - assetsenv.Dir("compiled"), assetsenv.Dir("#/assets/icons") + assetsenv["ASSETS_WORK_DIR"], + assetsenv["ASSETS_SRC_DIR"].Dir("icons"), ) assetsenv.Alias("icons", icons) @@ -18,7 +18,7 @@ assetsenv.Alias("icons", icons) # Protobuf .proto -> .c + .h proto_src = assetsenv.Glob("protobuf/*.proto", source=True) proto_options = assetsenv.Glob("protobuf/*.options", source=True) -proto = assetsenv.ProtoBuilder(assetsenv.Dir("compiled"), proto_src) +proto = assetsenv.ProtoBuilder(assetsenv["ASSETS_WORK_DIR"], proto_src) assetsenv.Depends(proto, proto_options) # Precious(proto) assetsenv.Alias("proto", proto) @@ -27,8 +27,8 @@ assetsenv.Alias("proto", proto) # Internal animations dolphin_internal = assetsenv.DolphinSymBuilder( - assetsenv.Dir("compiled"), - assetsenv.Dir("#/assets/dolphin"), + assetsenv["ASSETS_WORK_DIR"], + assetsenv["ASSETS_SRC_DIR"].Dir("dolphin"), DOLPHIN_RES_TYPE="internal", ) assetsenv.Alias("dolphin_internal", dolphin_internal) @@ -37,8 +37,8 @@ assetsenv.Alias("dolphin_internal", dolphin_internal) # Blocking animations dolphin_blocking = assetsenv.DolphinSymBuilder( - assetsenv.Dir("compiled"), - assetsenv.Dir("#/assets/dolphin"), + assetsenv["ASSETS_WORK_DIR"], + assetsenv["ASSETS_SRC_DIR"].Dir("dolphin"), DOLPHIN_RES_TYPE="blocking", ) assetsenv.Alias("dolphin_blocking", dolphin_blocking) @@ -46,8 +46,8 @@ assetsenv.Alias("dolphin_blocking", dolphin_blocking) # Protobuf version meta proto_ver = assetsenv.ProtoVerBuilder( - "compiled/protobuf_version.h", - "#/assets/protobuf/Changelog", + "${ASSETS_WORK_DIR}/protobuf_version.h", + assetsenv["ASSETS_SRC_DIR"].File("protobuf/Changelog"), ) assetsenv.Depends(proto_ver, proto) assetsenv.Alias("proto_ver", proto_ver) @@ -61,41 +61,19 @@ assetsenv.Install("${LIB_DIST_DIR}", assetslib) # Resources for SD card -env.SetDefault(FW_RESOURCES=None) if assetsenv["IS_BASE_FIRMWARE"]: + dolphin_external_out_dir = assetsenv["ASSETS_WORK_DIR"].Dir("dolphin") # External dolphin animations dolphin_external = assetsenv.DolphinExtBuilder( - assetsenv.Dir("#/assets/resources/dolphin"), - assetsenv.Dir("#/assets/dolphin"), + dolphin_external_out_dir, + assetsenv["ASSETS_SRC_DIR"].Dir("dolphin"), DOLPHIN_RES_TYPE="external", ) if assetsenv["FORCE"]: assetsenv.AlwaysBuild(dolphin_external) assetsenv.Alias("dolphin_ext", dolphin_external) - assetsenv.Clean(dolphin_external, assetsenv.Dir("#/assets/resources/dolphin")) - - # Resources manifest - resources = assetsenv.Command( - "#/assets/resources/Manifest", - assetsenv.GlobRecursive( - "*", - assetsenv.Dir("resources").srcnode(), - exclude=["Manifest"], - ), - action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}" --timestamp=${GIT_UNIX_TIMESTAMP}', - "${RESMANIFESTCOMSTR}", - ), - ) - assetsenv.Precious(resources) - assetsenv.AlwaysBuild(resources) - assetsenv.Clean( - resources, - assetsenv.Dir("#/assets/resources/apps"), - ) + assetsenv.Clean(dolphin_external, dolphin_external_out_dir) - # Exporting resources node to external environment - env.Replace(FW_RESOURCES=resources) - assetsenv.Alias("resources", resources) + env.Replace(DOLPHIN_EXTERNAL_OUT_DIR=dolphin_external_out_dir) Return("assetslib") diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 72c15ad48f..0b3217c58c 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -41,6 +41,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt - **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ - **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications. - **targets**: list of strings and target names with which this application is compatible. If not specified, the application is built for all targets. The default value is `["all"]`. +- **resources**: name of a folder within the application's source folder to be used for packacking SD card resources for this application. They will only be used if application is included in build configuration. The default value is `""`, meaning no resources are packaged. #### Parameters for external applications diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index f04793b4f4..3f6d51acf0 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -61,7 +61,7 @@ The App Loader allocates memory for the application and copies it to RAM, proces ## API versioning -Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `firmware/targets/` directory. +Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory. **`fbt`** uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. diff --git a/documentation/HardwareTargets.md b/documentation/HardwareTargets.md index 0c3474839b..b3213d4f50 100644 --- a/documentation/HardwareTargets.md +++ b/documentation/HardwareTargets.md @@ -2,7 +2,7 @@ Flipper's firmware is modular and supports different hardware configurations in a common code base. It encapsulates hardware-specific differences in `furi_hal`, board initialization code, linker files, SDK data and other information in a _target definition_. -Target-specific files are placed in a single sub-folder in `firmware/targets`. It must contain a target definition file, `target.json`, and may contain other files if they are referenced by current target's definition. By default, `fbt` gathers all source files in target folder, unless they are explicitly excluded. +Target-specific files are placed in a single sub-folder in `targets`. It must contain a target definition file, `target.json`, and may contain other files if they are referenced by current target's definition. By default, `fbt` gathers all source files in target folder, unless they are explicitly excluded. Targets can inherit most code parts from other targets, to reduce common code duplication. diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 4717daa8ca..9352917cdc 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -15,10 +15,9 @@ Running existing unit tests is useful to ensure that the new code doesn't introd To run the unit tests, follow these steps: -1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. -2. Flash the firmware using your preferred method. -3. Copy the [assets/unit_tests](/assets/unit_tests) folder to the root of your Flipper Zero's SD card. -4. Launch the CLI session and run the `unit_tests` command. +1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests updater_package`. +2. Flash the firmware using your preferred method, including SD card resources (`build/latest/resources`). +3. Launch the CLI session and run the `unit_tests` command. **NOTE:** To run a particular test (and skip all others), specify its name as the command argument. See [test_index.c](/applications/debug/unit_tests/test_index.c) for the complete list of test names. @@ -33,7 +32,7 @@ The common entry point for all tests is the [unit_tests](/applications/debug/uni #### Test assets -Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). +Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). ### Application-specific @@ -42,10 +41,10 @@ Some unit tests require external data in order to function. These files (commonl Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. To add unit tests for your protocol, follow these steps: -1. Create a file named `test_.irtest` in the [assets](/assets/unit_tests/infrared) directory. +1. Create a file named `test_.irtest` in the [assets](/applications/debug/unit_tests/resources/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). 3. Add the test code to [infrared_test.c](/applications/debug/unit_tests/infrared/infrared_test.c). -4. Update the [assets](/assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. +4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass. ##### Test data format diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 325f640d7e..213709afbf 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -13,7 +13,7 @@ Each signal is recorded using the following algorithm: The signal names are self-explanatory. Remember to make sure that every recorded signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [TV universal remote file](/assets/resources/infrared/assets/tv.ir). +If everything checks out, append these signals **to the end** of the [TV universal remote file](/applications/main/infrared/resources/infrared/assets/tv.ir). ## Audio players @@ -23,7 +23,7 @@ The signal names are self-explanatory. On many remotes, the `Play` button doubles as `Pause`. In this case, record it as `Play` omitting the `Pause`. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [audio player universal remote file](/assets/resources/infrared/assets/audio.ir). +If everything checks out, append these signals **to the end** of the [audio player universal remote file](/applications/main/infrared/resources/infrared/assets/audio.ir). ## Projectors @@ -67,7 +67,7 @@ Finally, record the `Off` signal: The resulting remote file should now contain 6 signals. You can omit any of them, but you then won't be able to use their functionality. Test the file against the actual device. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). +If everything checks out, append these signals **to the end** of the [A/C universal remote file](/applications/main/infrared/resources/infrared/assets/ac.ir). ## Final steps diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index c9b6a95365..4d43bd5b8e 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -72,9 +72,9 @@ Known protocols are represented in the `parsed` form, whereas non-recognized sig ### Examples -- [TV Universal Library](/assets/resources/infrared/assets/tv.ir) -- [A/C Universal Library](/assets/resources/infrared/assets/ac.ir) -- [Audio Universal Library](/assets/resources/infrared/assets/audio.ir) +- [TV Universal Library](/applications/main/infrared/resources/infrared/assets/tv.ir) +- [A/C Universal Library](/applications/main/infrared/resources/infrared/assets/ac.ir) +- [Audio Universal Library](/applications/main/infrared/resources/infrared/assets/audio.ir) ### Description @@ -92,7 +92,7 @@ See [Universal Remotes](/documentation/UniversalRemotes.md) for more information ### Examples -See [Infrared Unit Tests](/assets/unit_tests/infrared/) for various examples. +See [Infrared Unit Tests](/applications/debug/unit_tests/resources/unit_tests/infrared/) for various examples. ### Description diff --git a/firmware.scons b/firmware.scons index 82f775d719..e8e50022c7 100644 --- a/firmware.scons +++ b/firmware.scons @@ -1,15 +1,10 @@ -from SCons.Errors import UserError -from SCons.Node import FS - import itertools -from fbt_extra.util import ( - should_gen_cdb_and_link_dir, - link_elf_dir_as_latest, -) - from fbt.sdk.cache import LazySdkVersionLoader - +from fbt.version import get_git_commit_unix_timestamp +from fbt_extra.util import link_elf_dir_as_latest, should_gen_cdb_and_link_dir +from SCons.Errors import UserError +from SCons.Node import FS Import("ENV", "fw_build_meta") @@ -22,12 +17,15 @@ env = ENV.Clone( "pvsstudio", "fbt_hwtarget", "fbt_envhooks", + "fbt_resources", ], COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], IS_BASE_FIRMWARE=fw_build_meta["type"] == "firmware", FW_FLAVOR=fw_build_meta["flavor"], LIB_DIST_DIR=fw_build_meta["build_dir"].Dir("lib"), + RESOURCES_ROOT=fw_build_meta["build_dir"].Dir("resources"), + TARGETS_ROOT=Dir("#/targets"), LINT_SOURCES=[ Dir("applications"), ], @@ -37,7 +35,7 @@ env = ENV.Clone( CPPPATH=[ "#/furi", *(f"#/{app_dir[0]}" for app_dir in ENV["APPDIRS"] if app_dir[1]), - "#/firmware/targets/furi_hal_include", + "#/targets/furi_hal_include", ], # Specific flags for building libraries - always do optimized builds FW_LIB_OPTS={ @@ -104,7 +102,7 @@ lib_targets = env.BuildModules( [ "lib", "assets", - "firmware", + "targets", "furi", ], ) @@ -144,12 +142,26 @@ fwenv.PrepareApplicationsBuild() # Build external apps + configure SDK if env["IS_BASE_FIRMWARE"]: fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT=fwenv["BUILD_DIR"].Dir(".extapps")) - fwenv["FW_EXTAPPS"] = SConscript( + fw_extapps = fwenv["FW_EXTAPPS"] = SConscript( "site_scons/extapps.scons", exports={"ENV": fwenv}, ) - fw_artifacts.append(fwenv["FW_EXTAPPS"].sdk_tree) + fw_artifacts.append(fw_extapps.sdk_tree) + + # Resources for SD card + resources = fwenv.ResourcesDist( + _EXTRA_DIST=[fwenv["DOLPHIN_EXTERNAL_OUT_DIR"]], + ) + + manifest = fwenv.ManifestBuilder( + "${RESOURCES_ROOT}/Manifest", + source=resources, + GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), + ) + fwenv.Replace(FW_RESOURCES_MANIFEST=manifest) + fwenv.Alias("resources", manifest) + # API getter fwenv.Append(FBT_API_VERSION=LazySdkVersionLoader(fwenv.subst("$SDK_DEFINITION"))) fwenv.PhonyTarget( "get_apiversion", diff --git a/scripts/ReadMe.md b/scripts/ReadMe.md index a9feba11b3..359ce472ae 100644 --- a/scripts/ReadMe.md +++ b/scripts/ReadMe.md @@ -52,10 +52,10 @@ ob.py set # Assets delivery -Run in the root folder of the repo: +Build the firmware and run in the root folder of the repo: ```bash -python scripts/storage.py -p send assets/resources /ext +python scripts/storage.py -p send build/latest/resources /ext ``` diff --git a/scripts/assets.py b/scripts/assets.py index bd8b38ae6d..1099f0c330 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os +import shutil from flipper.app import App from flipper.assets.icon import file2image @@ -220,6 +221,7 @@ def manifest(self): if not os.path.isdir(directory_path): self.logger.error(f'"{directory_path}" is not a directory') exit(255) + manifest_file = os.path.join(directory_path, "Manifest") old_manifest = Manifest() if os.path.exists(manifest_file): @@ -234,13 +236,15 @@ def manifest(self): self.logger.info("Comparing new manifest with existing") only_in_old, changed, only_in_new = Manifest.compare(old_manifest, new_manifest) for record in only_in_old: - self.logger.info(f"Only in old: {record}") + self.logger.debug(f"Only in old: {record}") for record in changed: self.logger.info(f"Changed: {record}") for record in only_in_new: - self.logger.info(f"Only in new: {record}") + self.logger.debug(f"Only in new: {record}") if any((only_in_old, changed, only_in_new)): - self.logger.warning("Manifests are different, updating") + self.logger.info( + f"Manifest updated ({len(only_in_new)} new, {len(only_in_old)} removed, {len(changed)} changed)" + ) new_manifest.save(manifest_file) else: self.logger.info("Manifest is up-to-date!") diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 1a6cae9b17..3a3640d425 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -64,6 +64,7 @@ class Library: order: int = 0 sdk_headers: List[str] = field(default_factory=list) targets: List[str] = field(default_factory=lambda: ["all"]) + resources: Optional[str] = None # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) @@ -272,11 +273,15 @@ def __init__( self._check_unsatisfied() # unneeded? self._check_target_match() self._group_plugins() - self.apps = sorted( + self._apps = sorted( list(map(self.appmgr.get, self.appnames)), key=lambda app: app.appid, ) + @property + def apps(self): + return list(self._apps) + def _is_missing_dep(self, dep_name: str): return dep_name not in self.appnames @@ -385,13 +390,13 @@ def _group_plugins(self): def get_apps_cdefs(self): cdefs = set() - for app in self.apps: + for app in self._apps: cdefs.update(app.cdefines) return sorted(list(cdefs)) def get_sdk_headers(self): sdk_headers = [] - for app in self.apps: + for app in self._apps: sdk_headers.extend( [ src._appdir.File(header) @@ -405,14 +410,14 @@ def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): return sorted( filter( lambda app: app.apptype == apptype, - self.appmgr.known_apps.values() if all_known else self.apps, + self.appmgr.known_apps.values() if all_known else self._apps, ), key=lambda app: app.order, ) def get_builtin_apps(self): return list( - filter(lambda app: app.apptype in self.BUILTIN_APP_TYPES, self.apps) + filter(lambda app: app.apptype in self.BUILTIN_APP_TYPES, self._apps) ) def get_builtin_app_folders(self): diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index d923c328f2..dcf391f2d2 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -5,9 +5,10 @@ from SCons.Action import Action from SCons.Builder import Builder from SCons.Errors import StopError +from SCons.Node.FS import File -def icons_emitter(target, source, env): +def _icons_emitter(target, source, env): icons_src = env.GlobRecursive("*.png", env["ICON_SRC_DIR"]) icons_src += env.GlobRecursive("**/frame_rate", env["ICON_SRC_DIR"]) @@ -18,7 +19,7 @@ def icons_emitter(target, source, env): return target, icons_src -def proto_emitter(target, source, env): +def _proto_emitter(target, source, env): target = [] for src in source: basename = os.path.splitext(src.name)[0] @@ -27,7 +28,7 @@ def proto_emitter(target, source, env): return target, source -def dolphin_emitter(target, source, env): +def _dolphin_emitter(target, source, env): res_root_dir = source[0].Dir(env["DOLPHIN_RES_TYPE"]) source = [res_root_dir] source.extend(env.GlobRecursive("*.*", res_root_dir.srcnode())) @@ -38,16 +39,15 @@ def dolphin_emitter(target, source, env): if env["DOLPHIN_RES_TYPE"] == "external": target = [target_base_dir.File("manifest.txt")] ## A detailed list of files to be generated - ## works better if we just leave target the folder - # target = [] - # target.extend( - # map( - # lambda node: target_base_dir.File( - # res_root_dir.rel_path(node).replace(".png", ".bm") - # ), - # filter(lambda node: isinstance(node, SCons.Node.FS.File), source), - # ) - # ) + # Preserve original paths, do .png -> .bm conversion + target.extend( + map( + lambda node: target_base_dir.File( + res_root_dir.rel_path(node).replace(".png", ".bm") + ), + filter(lambda node: isinstance(node, File), source), + ) + ) else: asset_basename = f"assets_dolphin_{env['DOLPHIN_RES_TYPE']}" target = [ @@ -65,7 +65,7 @@ def dolphin_emitter(target, source, env): return target, source -def _invoke_git(args, source_dir): +def __invoke_git(args, source_dir): cmd = ["git"] cmd.extend(args) return ( @@ -75,11 +75,11 @@ def _invoke_git(args, source_dir): ) -def proto_ver_generator(target, source, env): +def _proto_ver_generator(target, source, env): target_file = target[0] src_dir = source[0].dir.abspath try: - _invoke_git( + __invoke_git( ["fetch", "--tags"], source_dir=src_dir, ) @@ -88,7 +88,7 @@ def proto_ver_generator(target, source, env): print(fg.boldred("Git: fetch failed")) try: - git_describe = _invoke_git( + git_describe = __invoke_git( ["describe", "--tags", "--abbrev=0"], source_dir=src_dir, ) @@ -127,7 +127,6 @@ def generate(env): ICONSCOMSTR="\tICONS\t${TARGET}", PROTOCOMSTR="\tPROTO\t${SOURCE}", DOLPHINCOMSTR="\tDOLPHIN\t${DOLPHIN_RES_TYPE}", - RESMANIFESTCOMSTR="\tMANIFEST\t${TARGET}", PBVERCOMSTR="\tPBVER\t${TARGET}", ) @@ -135,37 +134,74 @@ def generate(env): BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} ${ASSETS_COMPILER} icons ${ICON_SRC_DIR} ${TARGET.dir} --filename "${ICON_FILE_NAME}"', + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "icons", + "${ICON_SRC_DIR}", + "${TARGET.dir}", + "--filename", + "${ICON_FILE_NAME}", + ], + ], "${ICONSCOMSTR}", ), - emitter=icons_emitter, + emitter=_icons_emitter, ), "ProtoBuilder": Builder( action=Action( - "${PYTHON3} ${NANOPB_COMPILER} -q -I${SOURCE.dir.posix} -D${TARGET.dir.posix} ${SOURCES.posix}", + [ + [ + "${PYTHON3}", + "${NANOPB_COMPILER}", + "-q", + "-I${SOURCE.dir.posix}", + "-D${TARGET.dir.posix}", + "${SOURCES.posix}", + ], + ], "${PROTOCOMSTR}", ), - emitter=proto_emitter, + emitter=_proto_emitter, suffix=".pb.c", src_suffix=".proto", ), "DolphinSymBuilder": Builder( action=Action( - "${PYTHON3} ${ASSETS_COMPILER} dolphin -s dolphin_${DOLPHIN_RES_TYPE} ${SOURCE} ${_DOLPHIN_OUT_DIR}", + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "dolphin", + "-s", + "dolphin_${DOLPHIN_RES_TYPE}", + "${SOURCE}", + "${_DOLPHIN_OUT_DIR}", + ], + ], "${DOLPHINCOMSTR}", ), - emitter=dolphin_emitter, + emitter=_dolphin_emitter, ), "DolphinExtBuilder": Builder( action=Action( - "${PYTHON3} ${ASSETS_COMPILER} dolphin ${SOURCE} ${_DOLPHIN_OUT_DIR}", + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "dolphin", + "${SOURCE}", + "${_DOLPHIN_OUT_DIR}", + ], + ], "${DOLPHINCOMSTR}", ), - emitter=dolphin_emitter, + emitter=_dolphin_emitter, ), "ProtoVerBuilder": Builder( action=Action( - proto_ver_generator, + _proto_ver_generator, "${PBVERCOMSTR}", ), ), diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index fdf66c0a71..bf586b8fbb 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -96,7 +96,21 @@ def DistCommand(env, name, source, **kw): command = env.Command( target, source, - '@${PYTHON3} "${DIST_SCRIPT}" copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', + action=Action( + [ + [ + "${PYTHON3}", + "${DIST_SCRIPT}", + "copy", + "-p", + "${DIST_PROJECTS}", + "-s", + "${DIST_SUFFIX}", + "${DIST_EXTRA}", + ] + ], + "${DISTCOMSTR}", + ), **kw, ) env.Pseudo(target) @@ -106,7 +120,10 @@ def DistCommand(env, name, source, **kw): def generate(env): if not env["VERBOSE"]: - env.SetDefault(COPROCOMSTR="\tCOPRO\t${TARGET}") + env.SetDefault( + COPROCOMSTR="\tCOPRO\t${TARGET}", + DISTCOMSTR="\tDIST\t${TARGET}", + ) env.AddMethod(AddFwProject) env.AddMethod(DistCommand) env.AddMethod(AddFwFlashTarget) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 963429f245..94307539a7 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -1,7 +1,5 @@ import itertools -import os import pathlib -import shutil from dataclasses import dataclass, field from typing import Dict, List, Optional @@ -290,7 +288,7 @@ def prepare_app_metadata(target, source, env): ) -def validate_app_imports(target, source, env): +def _validate_app_imports(target, source, env): sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=False) app_syms = set() with open(target[0].path, "rt") as f: @@ -342,35 +340,7 @@ def GetExtAppByIdOrPath(env, app_dir): return app_artifacts -def resources_fap_dist_emitter(target, source, env): - # Initially we have a single target - target dir - # Here we inject pairs of (target, source) for each file - resources_root = target[0] - - target = [] - for app_artifacts in env["EXT_APPS"].values(): - for _, dist_path in filter( - lambda dist_entry: dist_entry[0], app_artifacts.dist_entries - ): - source.append(app_artifacts.compact) - target.append(resources_root.File(dist_path)) - - assert len(target) == len(source) - return (target, source) - - -def resources_fap_dist_action(target, source, env): - # FIXME: find a proper way to remove stale files - target_dir = env.Dir("${RESOURCES_ROOT}/apps") - shutil.rmtree(target_dir.path, ignore_errors=True) - - # Iterate over pairs generated in emitter - for src, target in zip(source, target): - os.makedirs(os.path.dirname(target.path), exist_ok=True) - shutil.copy(src.path, target.path) - - -def embed_app_metadata_emitter(target, source, env): +def _embed_app_metadata_emitter(target, source, env): app = env["APP"] # Hack: change extension for fap libs @@ -407,33 +377,52 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): Action(prepare_app_metadata, "$APPMETA_COMSTR"), ] - objcopy_str = ( - "${OBJCOPY} " - "--remove-section .ARM.attributes " - "--add-section ${_FAP_META_SECTION}=${APP._section_fapmeta} " - ) + objcopy_args = [ + "${OBJCOPY}", + "--remove-section", + ".ARM.attributes", + "--add-section", + "${_FAP_META_SECTION}=${APP._section_fapmeta}", + "--set-section-flags", + "${_FAP_META_SECTION}=contents,noload,readonly,data", + ] if app._section_fapfileassets: actions.append(Action(prepare_app_file_assets, "$APPFILE_COMSTR")) - objcopy_str += ( - "--add-section ${_FAP_FILEASSETS_SECTION}=${APP._section_fapfileassets} " + objcopy_args.extend( + ( + "--add-section", + "${_FAP_FILEASSETS_SECTION}=${APP._section_fapfileassets}", + "--set-section-flags", + "${_FAP_FILEASSETS_SECTION}=contents,noload,readonly,data", + ) ) - objcopy_str += ( - "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " - "--strip-debug --strip-unneeded " - "--add-gnu-debuglink=${SOURCE} " - "${SOURCES} ${TARGET}" + objcopy_args.extend( + ( + "--strip-debug", + "--strip-unneeded", + "--add-gnu-debuglink=${SOURCE}", + "${SOURCES}", + "${TARGET}", + ) ) actions.extend( ( Action( - objcopy_str, + [objcopy_args], "$APPMETAEMBED_COMSTR", ), Action( - "${PYTHON3} ${FBT_SCRIPT_DIR}/fastfap.py ${TARGET} ${OBJCOPY}", + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/fastfap.py", + "${TARGET}", + "${OBJCOPY}", + ] + ], "$FASTFAP_COMSTR", ), ) @@ -511,7 +500,6 @@ def generate(env, **kw): ) if not env["VERBOSE"]: env.SetDefault( - FAPDISTCOMSTR="\tFAPDIST\t${TARGET}", APPMETA_COMSTR="\tAPPMETA\t${TARGET}", APPFILE_COMSTR="\tAPPFILE\t${TARGET}", APPMETAEMBED_COMSTR="\tFAP\t${TARGET}", @@ -534,18 +522,11 @@ def generate(env, **kw): env.Append( BUILDERS={ - "FapDist": Builder( - action=Action( - resources_fap_dist_action, - "$FAPDISTCOMSTR", - ), - emitter=resources_fap_dist_emitter, - ), "EmbedAppMetadata": Builder( generator=generate_embed_app_metadata_actions, suffix=".fap", src_suffix=".elf", - emitter=embed_app_metadata_emitter, + emitter=_embed_app_metadata_emitter, ), "ValidateAppImports": Builder( action=[ @@ -554,7 +535,7 @@ def generate(env, **kw): None, # "$APPDUMP_COMSTR", ), Action( - validate_app_imports, + _validate_app_imports, "$APPCHECK_COMSTR", ), ], diff --git a/scripts/fbt_tools/fbt_hwtarget.py b/scripts/fbt_tools/fbt_hwtarget.py index 1831a6984d..67975ed0f8 100644 --- a/scripts/fbt_tools/fbt_hwtarget.py +++ b/scripts/fbt_tools/fbt_hwtarget.py @@ -2,9 +2,9 @@ class HardwareTargetLoader: - def __init__(self, env, target_scons_dir, target_id): + def __init__(self, env, root_target_scons_dir, target_id): self.env = env - self.target_scons_dir = target_scons_dir + self.all_targets_root_dir = root_target_scons_dir self.target_dir = self._getTargetDir(target_id) # self.target_id = target_id self.layered_target_dirs = [] @@ -23,7 +23,7 @@ def __init__(self, env, target_scons_dir, target_id): self._processTargetDefinitions(target_id) def _getTargetDir(self, target_id): - return self.target_scons_dir.Dir(f"f{target_id}") + return self.all_targets_root_dir.Dir(f"f{target_id}") def _loadDescription(self, target_id): target_json_file = self._getTargetDir(target_id).File("target.json") @@ -34,14 +34,14 @@ def _loadDescription(self, target_id): return vals def _processTargetDefinitions(self, target_id): - self.layered_target_dirs.append(f"targets/f{target_id}") + target_dir = self._getTargetDir(target_id) + self.layered_target_dirs.append(target_dir) config = self._loadDescription(target_id) for path_list in ("include_paths", "sdk_header_paths"): getattr(self, path_list).extend( - f"#/firmware/targets/f{target_id}/{p}" - for p in config.get(path_list, []) + target_dir.Dir(p) for p in config.get(path_list, []) ) self.excluded_sources.extend(config.get("excluded_sources", [])) @@ -50,7 +50,7 @@ def _processTargetDefinitions(self, target_id): file_attrs = ( # (name, use_src_node) - ("startup_script", False), + ("startup_script", True), ("linker_script_flash", True), ("linker_script_ram", True), ("linker_script_app", True), @@ -59,9 +59,10 @@ def _processTargetDefinitions(self, target_id): for attr_name, use_src_node in file_attrs: if (val := config.get(attr_name)) and not getattr(self, attr_name): - node = self.env.File(f"firmware/targets/f{target_id}/{val}") + node = target_dir.File(val) if use_src_node: node = node.srcnode() + # print(f"Got node {node}, {node.path} for {attr_name}") setattr(self, attr_name, node) for attr_name in ("linker_dependencies",): @@ -84,8 +85,8 @@ def gatherSources(self): ) seen_filenames.update(f.name for f in accepted_sources) sources.extend(accepted_sources) - # print(f"Found {len(sources)} sources: {list(f.name for f in sources)}") - return sources + # print(f"Found {len(sources)} sources: {list(f.path for f in sources)}") + return list(f.get_path(self.all_targets_root_dir) for f in sources) def gatherSdkHeaders(self): sdk_headers = [] @@ -101,7 +102,7 @@ def gatherSdkHeaders(self): def ConfigureForTarget(env, target_id): - target_loader = HardwareTargetLoader(env, env.Dir("#/firmware/targets"), target_id) + target_loader = HardwareTargetLoader(env, env["TARGETS_ROOT"], target_id) env.Replace( TARGET_CFG=target_loader, SDK_DEFINITION=target_loader.sdk_symbols, diff --git a/scripts/fbt_tools/fbt_resources.py b/scripts/fbt_tools/fbt_resources.py new file mode 100644 index 0000000000..47c624081f --- /dev/null +++ b/scripts/fbt_tools/fbt_resources.py @@ -0,0 +1,98 @@ +import os +import shutil + +from SCons.Action import Action +from SCons.Builder import Builder +from SCons.Errors import StopError +from SCons.Node.FS import Dir, File + + +def _resources_dist_emitter(target, source, env): + resources_root = env.Dir(env["RESOURCES_ROOT"]) + + target = [] + for app_artifacts in env["FW_EXTAPPS"].application_map.values(): + for _, dist_path in filter( + lambda dist_entry: dist_entry[0], app_artifacts.dist_entries + ): + source.append(app_artifacts.compact) + target.append(resources_root.File(dist_path)) + + # Deploy apps' resources too + for app in env["APPBUILD"].apps: + if not app.resources: + continue + apps_resource_dir = app._appdir.Dir(app.resources) + for res_file in env.GlobRecursive("*", apps_resource_dir): + if not isinstance(res_file, File): + continue + source.append(res_file) + target.append(resources_root.File(res_file.get_path(apps_resource_dir))) + + # Deploy other stuff from _EXTRA_DIST + for extra_dist in env["_EXTRA_DIST"]: + if isinstance(extra_dist, Dir): + for extra_file in env.GlobRecursive("*", extra_dist): + if not isinstance(extra_file, File): + continue + source.append(extra_file) + target.append( + # Preserve dir name from original node + resources_root.Dir(extra_dist.name).File( + extra_file.get_path(extra_dist) + ) + ) + else: + raise StopError(f"Unsupported extra dist type: {type(extra_dist)}") + + assert len(target) == len(source) + return (target, source) + + +def _resources_dist_action(target, source, env): + shutil.rmtree(env.Dir(env["RESOURCES_ROOT"]).abspath, ignore_errors=True) + for src, target in zip(source, target): + os.makedirs(os.path.dirname(target.path), exist_ok=True) + shutil.copy(src.path, target.path) + + +def generate(env, **kw): + env.SetDefault( + ASSETS_COMPILER="${FBT_SCRIPT_DIR}/assets.py", + ) + + if not env["VERBOSE"]: + env.SetDefault( + RESOURCEDISTCOMSTR="\tRESDIST\t${RESOURCES_ROOT}", + RESMANIFESTCOMSTR="\tMANIFST\t${TARGET}", + ) + + env.Append( + BUILDERS={ + "ResourcesDist": Builder( + action=Action( + _resources_dist_action, + "${RESOURCEDISTCOMSTR}", + ), + emitter=_resources_dist_emitter, + ), + "ManifestBuilder": Builder( + action=Action( + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "manifest", + "${TARGET.dir.posix}", + "--timestamp=${GIT_UNIX_TIMESTAMP}", + ] + ], + "${RESMANIFESTCOMSTR}", + ) + ), + } + ) + + +def exists(env): + return True diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 2f7d62388d..6350f14b8b 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -37,13 +37,13 @@ def ProcessSdkDepends(env, filename): return depends -def api_amalgam_emitter(target, source, env): +def _api_amalgam_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".i.c")) return target, source -def api_amalgam_gen_origin_header(target, source, env): +def _api_amalgam_gen_origin_header(target, source, env): mega_file = env.subst("${TARGET}.c", target=target[0]) with open(mega_file, "wt") as sdk_c: sdk_c.write( @@ -183,12 +183,12 @@ def deploy_action(self): self._generate_sdk_meta() -def deploy_sdk_header_tree_action(target, source, env): +def _deploy_sdk_header_tree_action(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.deploy_action() -def deploy_sdk_header_tree_emitter(target, source, env): +def _deploy_sdk_header_tree_emitter(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.emitter(target, source, env) @@ -227,7 +227,7 @@ def _check_sdk_is_up2date(sdk_cache: SdkCache): ) -def validate_api_cache(source, target, env): +def _validate_api_cache(source, target, env): # print(f"Generating SDK for {source[0]} to {target[0]}") current_sdk = SdkCollector() current_sdk.process_source_file_for_sdk(source[0].path) @@ -240,7 +240,7 @@ def validate_api_cache(source, target, env): _check_sdk_is_up2date(sdk_cache) -def generate_api_table(source, target, env): +def _generate_api_table(source, target, env): sdk_cache = SdkCache(source[0].path) _check_sdk_is_up2date(sdk_cache) @@ -278,10 +278,10 @@ def generate(env, **kw): env.Append( BUILDERS={ "ApiAmalgamator": Builder( - emitter=api_amalgam_emitter, + emitter=_api_amalgam_emitter, action=[ Action( - api_amalgam_gen_origin_header, + _api_amalgam_gen_origin_header, "$SDK_AMALGAMATE_HEADER_COMSTR", ), Action( @@ -293,15 +293,15 @@ def generate(env, **kw): ), "SDKHeaderTreeExtractor": Builder( action=Action( - deploy_sdk_header_tree_action, + _deploy_sdk_header_tree_action, "$SDKTREE_COMSTR", ), - emitter=deploy_sdk_header_tree_emitter, + emitter=_deploy_sdk_header_tree_emitter, src_suffix=".d", ), "ApiTableValidator": Builder( action=Action( - validate_api_cache, + _validate_api_cache, "$SDKSYM_UPDATER_COMSTR", ), suffix=".csv", @@ -309,7 +309,7 @@ def generate(env, **kw): ), "ApiSymbolTable": Builder( action=Action( - generate_api_table, + _generate_api_table, "$APITABLE_GENERATOR_COMSTR", ), suffix=".h", diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index aead13b29f..f1a782523f 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -2,7 +2,7 @@ from SCons.Builder import Builder -def version_emitter(target, source, env): +def _version_emitter(target, source, env): target_dir = target[0] target = [ target_dir.File("version.inc.h"), @@ -24,7 +24,7 @@ def generate(env): '-o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', "${VERSIONCOMSTR}", ), - emitter=version_emitter, + emitter=_version_emitter, ), } ) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 211f46aee8..f43db126e3 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -17,7 +17,7 @@ def _set_browser_action(target, source, env): __no_browser = True -def emit_pvsreport(target, source, env): +def _emit_pvsreport(target, source, env): target_dir = env["REPORT_DIR"] if env["PLATFORM"] == "win32": # Report generator on Windows emits to a subfolder of given output folder @@ -96,7 +96,7 @@ def generate(env): ], "${PVSCONVCOMSTR}", ), - emitter=emit_pvsreport, + emitter=_emit_pvsreport, src_suffix=".log", ), } diff --git a/scripts/flipper/assets/dolphin.py b/scripts/flipper/assets/dolphin.py index e9089a1b99..cf98c8253c 100644 --- a/scripts/flipper/assets/dolphin.py +++ b/scripts/flipper/assets/dolphin.py @@ -55,7 +55,7 @@ def load(self, animation_directory: str): if not os.path.isfile(meta_filename): raise Exception(f"Animation meta file doesn't exist: { meta_filename }") - self.logger.info(f"Loading meta from {meta_filename}") + self.logger.debug(f"Loading meta from {meta_filename}") file = FlipperFormatFile() file.load(meta_filename) diff --git a/scripts/meta.py b/scripts/meta.py deleted file mode 100755 index f47ef65fb0..0000000000 --- a/scripts/meta.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 - -import json - -from flipper.app import App - - -class Main(App): - def init(self): - self.subparsers = self.parser.add_subparsers(help="sub-command help") - - # generate - self.parser_generate = self.subparsers.add_parser( - "generate", help="Generate JSON meta file" - ) - self.parser_generate.add_argument("-p", dest="project", required=True) - self.parser_generate.add_argument( - "-DBUILD_DATE", dest="build_date", required=True - ) - self.parser_generate.add_argument("-DGIT_COMMIT", dest="commit", required=True) - self.parser_generate.add_argument("-DGIT_BRANCH", dest="branch", required=True) - self.parser_generate.add_argument( - "-DTARGET", dest="target", type=int, required=True - ) - self.parser_generate.set_defaults(func=self.generate) - - # merge - self.parser_merge = self.subparsers.add_parser( - "merge", help="Merge JSON meta files" - ) - self.parser_merge.add_argument( - "-i", dest="input", action="append", nargs="+", required=True - ) - self.parser_merge.set_defaults(func=self.merge) - - def generate(self): - meta = {} - for k, v in vars(self.args).items(): - if k in ["project", "func", "debug"]: - continue - if isinstance(v, str): - v = v.strip('"') - meta[self.args.project + "_" + k] = v - - print(json.dumps(meta, indent=4)) - return 0 - - def merge(self): - full = {} - for path in self.args.input[0]: - with open(path, mode="r") as file: - dict = json.loads(file.read()) - full.update(dict) - - print(json.dumps(full, indent=4)) - return 0 - - -if __name__ == "__main__": - Main()() diff --git a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml index c11ffc180f..143847c4a2 100644 --- a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml +++ b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml @@ -27,9 +27,9 @@ jobs: name: 'ufbt: Build for ${{ matrix.name }}' steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build with ufbt - uses: flipperdevices/flipperzero-ufbt-action@v0.1.1 + uses: flipperdevices/flipperzero-ufbt-action@v0.1 id: build-app with: sdk-channel: ${{ matrix.sdk-channel }} diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index f9227ed37b..769b3eb154 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -14,7 +14,6 @@ appenv = ENV["APPENV"] = ENV.Clone( "fbt_assets", "fbt_sdk", ], - RESOURCES_ROOT=ENV.Dir("#/assets/resources"), ) appenv.Replace( @@ -56,7 +55,6 @@ appenv.AppendUnique( @dataclass class FlipperExtAppBuildArtifacts: application_map: dict = field(default_factory=dict) - resources_dist: NodeList = field(default_factory=NodeList) sdk_tree: NodeList = field(default_factory=NodeList) @@ -78,8 +76,6 @@ Alias( list(app_artifact.validator for app_artifact in extapps.application_map.values()), ) -extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) - if appsrc := appenv.subst("$APPSRC"): launch_target = appenv.AddAppLaunchTarget(appsrc, "launch") diff --git a/firmware/ReadMe.md b/targets/ReadMe.md similarity index 100% rename from firmware/ReadMe.md rename to targets/ReadMe.md diff --git a/firmware/SConscript b/targets/SConscript similarity index 100% rename from firmware/SConscript rename to targets/SConscript diff --git a/firmware/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv similarity index 98% rename from firmware/targets/f18/api_symbols.csv rename to targets/f18/api_symbols.csv index 4789d316d6..b4efd7911b 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -36,51 +36,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,firmware/targets/f18/furi_hal/furi_hal_resources.h,, -Header,+,firmware/targets/f18/furi_hal/furi_hal_spi_config.h,, -Header,+,firmware/targets/f18/furi_hal/furi_hal_target_hw.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_bus.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_dma.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, -Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, -Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_i2c.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_info.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_rtc.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -194,6 +149,51 @@ Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, +Header,+,targets/f18/furi_hal/furi_hal_resources.h,, +Header,+,targets/f18/furi_hal/furi_hal_spi_config.h,, +Header,+,targets/f18/furi_hal/furi_hal_target_hw.h,, +Header,+,targets/f7/furi_hal/furi_hal_bus.h,, +Header,+,targets/f7/furi_hal/furi_hal_clock.h,, +Header,+,targets/f7/furi_hal/furi_hal_console.h,, +Header,+,targets/f7/furi_hal/furi_hal_dma.h,, +Header,+,targets/f7/furi_hal/furi_hal_flash.h,, +Header,+,targets/f7/furi_hal/furi_hal_gpio.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_config.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_idle_timer.h,, +Header,+,targets/f7/furi_hal/furi_hal_interrupt.h,, +Header,+,targets/f7/furi_hal/furi_hal_os.h,, +Header,+,targets/f7/furi_hal/furi_hal_pwm.h,, +Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_uart.h,, +Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,targets/f7/platform_specific/intrinsic_export.h,, +Header,+,targets/f7/platform_specific/math_wrapper.h,, +Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_bt.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, +Header,+,targets/furi_hal_include/furi_hal_cortex.h,, +Header,+,targets/furi_hal_include/furi_hal_crypto.h,, +Header,+,targets/furi_hal_include/furi_hal_debug.h,, +Header,+,targets/furi_hal_include/furi_hal_i2c.h,, +Header,+,targets/furi_hal_include/furi_hal_info.h,, +Header,+,targets/furi_hal_include/furi_hal_light.h,, +Header,+,targets/furi_hal_include/furi_hal_memory.h,, +Header,+,targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,targets/furi_hal_include/furi_hal_power.h,, +Header,+,targets/furi_hal_include/furi_hal_random.h,, +Header,+,targets/furi_hal_include/furi_hal_region.h,, +Header,+,targets/furi_hal_include/furi_hal_rtc.h,, +Header,+,targets/furi_hal_include/furi_hal_sd.h,, +Header,+,targets/furi_hal_include/furi_hal_speaker.h,, +Header,+,targets/furi_hal_include/furi_hal_spi.h,, +Header,+,targets/furi_hal_include/furi_hal_usb.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_ccid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, +Header,+,targets/furi_hal_include/furi_hal_version.h,, +Header,+,targets/furi_hal_include/furi_hal_vibro.h,, Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, const LL_ADC_CommonInitTypeDef*" Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal.c rename to targets/f18/furi_hal/furi_hal.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_power_config.c b/targets/f18/furi_hal/furi_hal_power_config.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_power_config.c rename to targets/f18/furi_hal/furi_hal_power_config.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_resources.c rename to targets/f18/furi_hal/furi_hal_resources.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_resources.h rename to targets/f18/furi_hal/furi_hal_resources.h diff --git a/firmware/targets/f18/furi_hal/furi_hal_spi_config.c b/targets/f18/furi_hal/furi_hal_spi_config.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_spi_config.c rename to targets/f18/furi_hal/furi_hal_spi_config.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_spi_config.h b/targets/f18/furi_hal/furi_hal_spi_config.h similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_spi_config.h rename to targets/f18/furi_hal/furi_hal_spi_config.h diff --git a/firmware/targets/f18/furi_hal/furi_hal_target_hw.h b/targets/f18/furi_hal/furi_hal_target_hw.h similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_target_hw.h rename to targets/f18/furi_hal/furi_hal_target_hw.h diff --git a/firmware/targets/f18/furi_hal/furi_hal_version_device.c b/targets/f18/furi_hal/furi_hal_version_device.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_version_device.c rename to targets/f18/furi_hal/furi_hal_version_device.c diff --git a/firmware/targets/f18/target.json b/targets/f18/target.json similarity index 100% rename from firmware/targets/f18/target.json rename to targets/f18/target.json diff --git a/firmware/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv similarity index 98% rename from firmware/targets/f7/api_symbols.csv rename to targets/f7/api_symbols.csv index 038a22eae4..7599230d72 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -37,56 +37,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_bus.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_dma.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_ibutton.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, -Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, -Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_i2c.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_info.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_infrared.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_nfc.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_rtc.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -262,6 +212,56 @@ Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, +Header,+,targets/f7/furi_hal/furi_hal_bus.h,, +Header,+,targets/f7/furi_hal/furi_hal_clock.h,, +Header,+,targets/f7/furi_hal/furi_hal_console.h,, +Header,+,targets/f7/furi_hal/furi_hal_dma.h,, +Header,+,targets/f7/furi_hal/furi_hal_flash.h,, +Header,+,targets/f7/furi_hal/furi_hal_gpio.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_config.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_ibutton.h,, +Header,+,targets/f7/furi_hal/furi_hal_idle_timer.h,, +Header,+,targets/f7/furi_hal/furi_hal_interrupt.h,, +Header,+,targets/f7/furi_hal/furi_hal_os.h,, +Header,+,targets/f7/furi_hal/furi_hal_pwm.h,, +Header,+,targets/f7/furi_hal/furi_hal_resources.h,, +Header,+,targets/f7/furi_hal/furi_hal_rfid.h,, +Header,+,targets/f7/furi_hal/furi_hal_spi_config.h,, +Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_subghz.h,, +Header,+,targets/f7/furi_hal/furi_hal_target_hw.h,, +Header,+,targets/f7/furi_hal/furi_hal_uart.h,, +Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,targets/f7/platform_specific/intrinsic_export.h,, +Header,+,targets/f7/platform_specific/math_wrapper.h,, +Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_bt.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, +Header,+,targets/furi_hal_include/furi_hal_cortex.h,, +Header,+,targets/furi_hal_include/furi_hal_crypto.h,, +Header,+,targets/furi_hal_include/furi_hal_debug.h,, +Header,+,targets/furi_hal_include/furi_hal_i2c.h,, +Header,+,targets/furi_hal_include/furi_hal_info.h,, +Header,+,targets/furi_hal_include/furi_hal_infrared.h,, +Header,+,targets/furi_hal_include/furi_hal_light.h,, +Header,+,targets/furi_hal_include/furi_hal_memory.h,, +Header,+,targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,targets/furi_hal_include/furi_hal_nfc.h,, +Header,+,targets/furi_hal_include/furi_hal_power.h,, +Header,+,targets/furi_hal_include/furi_hal_random.h,, +Header,+,targets/furi_hal_include/furi_hal_region.h,, +Header,+,targets/furi_hal_include/furi_hal_rtc.h,, +Header,+,targets/furi_hal_include/furi_hal_sd.h,, +Header,+,targets/furi_hal_include/furi_hal_speaker.h,, +Header,+,targets/furi_hal_include/furi_hal_spi.h,, +Header,+,targets/furi_hal_include/furi_hal_usb.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_ccid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, +Header,+,targets/furi_hal_include/furi_hal_version.h,, +Header,+,targets/furi_hal_include/furi_hal_vibro.h,, Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, const LL_ADC_CommonInitTypeDef*" Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* diff --git a/firmware/targets/f7/application_ext.ld b/targets/f7/application_ext.ld similarity index 100% rename from firmware/targets/f7/application_ext.ld rename to targets/f7/application_ext.ld diff --git a/firmware/targets/f7/ble_glue/app_common.h b/targets/f7/ble_glue/app_common.h similarity index 100% rename from firmware/targets/f7/ble_glue/app_common.h rename to targets/f7/ble_glue/app_common.h diff --git a/firmware/targets/f7/ble_glue/app_conf.h b/targets/f7/ble_glue/app_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/app_conf.h rename to targets/f7/ble_glue/app_conf.h diff --git a/firmware/targets/f7/ble_glue/app_debug.c b/targets/f7/ble_glue/app_debug.c similarity index 100% rename from firmware/targets/f7/ble_glue/app_debug.c rename to targets/f7/ble_glue/app_debug.c diff --git a/firmware/targets/f7/ble_glue/app_debug.h b/targets/f7/ble_glue/app_debug.h similarity index 100% rename from firmware/targets/f7/ble_glue/app_debug.h rename to targets/f7/ble_glue/app_debug.h diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/targets/f7/ble_glue/ble_app.c similarity index 100% rename from firmware/targets/f7/ble_glue/ble_app.c rename to targets/f7/ble_glue/ble_app.c diff --git a/firmware/targets/f7/ble_glue/ble_app.h b/targets/f7/ble_glue/ble_app.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_app.h rename to targets/f7/ble_glue/ble_app.h diff --git a/firmware/targets/f7/ble_glue/ble_conf.h b/targets/f7/ble_glue/ble_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_conf.h rename to targets/f7/ble_glue/ble_conf.h diff --git a/firmware/targets/f7/ble_glue/ble_const.h b/targets/f7/ble_glue/ble_const.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_const.h rename to targets/f7/ble_glue/ble_const.h diff --git a/firmware/targets/f7/ble_glue/ble_dbg_conf.h b/targets/f7/ble_glue/ble_dbg_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_dbg_conf.h rename to targets/f7/ble_glue/ble_dbg_conf.h diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/targets/f7/ble_glue/ble_glue.c similarity index 100% rename from firmware/targets/f7/ble_glue/ble_glue.c rename to targets/f7/ble_glue/ble_glue.c diff --git a/firmware/targets/f7/ble_glue/ble_glue.h b/targets/f7/ble_glue/ble_glue.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_glue.h rename to targets/f7/ble_glue/ble_glue.h diff --git a/firmware/targets/f7/ble_glue/compiler.h b/targets/f7/ble_glue/compiler.h similarity index 100% rename from firmware/targets/f7/ble_glue/compiler.h rename to targets/f7/ble_glue/compiler.h diff --git a/firmware/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c similarity index 100% rename from firmware/targets/f7/ble_glue/gap.c rename to targets/f7/ble_glue/gap.c diff --git a/firmware/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h similarity index 100% rename from firmware/targets/f7/ble_glue/gap.h rename to targets/f7/ble_glue/gap.h diff --git a/firmware/targets/f7/ble_glue/hsem_map.h b/targets/f7/ble_glue/hsem_map.h similarity index 100% rename from firmware/targets/f7/ble_glue/hsem_map.h rename to targets/f7/ble_glue/hsem_map.h diff --git a/firmware/targets/f7/ble_glue/hw_ipcc.c b/targets/f7/ble_glue/hw_ipcc.c similarity index 100% rename from firmware/targets/f7/ble_glue/hw_ipcc.c rename to targets/f7/ble_glue/hw_ipcc.c diff --git a/firmware/targets/f7/ble_glue/osal.h b/targets/f7/ble_glue/osal.h similarity index 100% rename from firmware/targets/f7/ble_glue/osal.h rename to targets/f7/ble_glue/osal.h diff --git a/firmware/targets/f7/ble_glue/services/battery_service.c b/targets/f7/ble_glue/services/battery_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/battery_service.c rename to targets/f7/ble_glue/services/battery_service.c diff --git a/firmware/targets/f7/ble_glue/services/battery_service.h b/targets/f7/ble_glue/services/battery_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/battery_service.h rename to targets/f7/ble_glue/services/battery_service.h diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service.c b/targets/f7/ble_glue/services/dev_info_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/dev_info_service.c rename to targets/f7/ble_glue/services/dev_info_service.c diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service.h b/targets/f7/ble_glue/services/dev_info_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/dev_info_service.h rename to targets/f7/ble_glue/services/dev_info_service.h diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc b/targets/f7/ble_glue/services/dev_info_service_uuid.inc similarity index 100% rename from firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc rename to targets/f7/ble_glue/services/dev_info_service_uuid.inc diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.c b/targets/f7/ble_glue/services/gatt_char.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/gatt_char.c rename to targets/f7/ble_glue/services/gatt_char.c diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.h b/targets/f7/ble_glue/services/gatt_char.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/gatt_char.h rename to targets/f7/ble_glue/services/gatt_char.h diff --git a/firmware/targets/f7/ble_glue/services/hid_service.c b/targets/f7/ble_glue/services/hid_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/hid_service.c rename to targets/f7/ble_glue/services/hid_service.c diff --git a/firmware/targets/f7/ble_glue/services/hid_service.h b/targets/f7/ble_glue/services/hid_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/hid_service.h rename to targets/f7/ble_glue/services/hid_service.h diff --git a/firmware/targets/f7/ble_glue/services/serial_service.c b/targets/f7/ble_glue/services/serial_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/serial_service.c rename to targets/f7/ble_glue/services/serial_service.c diff --git a/firmware/targets/f7/ble_glue/services/serial_service.h b/targets/f7/ble_glue/services/serial_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/serial_service.h rename to targets/f7/ble_glue/services/serial_service.h diff --git a/firmware/targets/f7/ble_glue/services/serial_service_uuid.inc b/targets/f7/ble_glue/services/serial_service_uuid.inc similarity index 100% rename from firmware/targets/f7/ble_glue/services/serial_service_uuid.inc rename to targets/f7/ble_glue/services/serial_service_uuid.inc diff --git a/firmware/targets/f7/ble_glue/tl_dbg_conf.h b/targets/f7/ble_glue/tl_dbg_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/tl_dbg_conf.h rename to targets/f7/ble_glue/tl_dbg_conf.h diff --git a/firmware/targets/f7/fatfs/fatfs.c b/targets/f7/fatfs/fatfs.c similarity index 100% rename from firmware/targets/f7/fatfs/fatfs.c rename to targets/f7/fatfs/fatfs.c diff --git a/firmware/targets/f7/fatfs/fatfs.h b/targets/f7/fatfs/fatfs.h similarity index 100% rename from firmware/targets/f7/fatfs/fatfs.h rename to targets/f7/fatfs/fatfs.h diff --git a/firmware/targets/f7/fatfs/ffconf.h b/targets/f7/fatfs/ffconf.h similarity index 100% rename from firmware/targets/f7/fatfs/ffconf.h rename to targets/f7/fatfs/ffconf.h diff --git a/firmware/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c similarity index 100% rename from firmware/targets/f7/fatfs/sector_cache.c rename to targets/f7/fatfs/sector_cache.c diff --git a/firmware/targets/f7/fatfs/sector_cache.h b/targets/f7/fatfs/sector_cache.h similarity index 100% rename from firmware/targets/f7/fatfs/sector_cache.h rename to targets/f7/fatfs/sector_cache.h diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/targets/f7/fatfs/user_diskio.c similarity index 100% rename from firmware/targets/f7/fatfs/user_diskio.c rename to targets/f7/fatfs/user_diskio.c diff --git a/firmware/targets/f7/fatfs/user_diskio.h b/targets/f7/fatfs/user_diskio.h similarity index 100% rename from firmware/targets/f7/fatfs/user_diskio.h rename to targets/f7/fatfs/user_diskio.h diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal.c rename to targets/f7/furi_hal/furi_hal.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bt.c rename to targets/f7/furi_hal/furi_hal_bt.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/targets/f7/furi_hal/furi_hal_bt_hid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bt_hid.c rename to targets/f7/furi_hal/furi_hal_bt_hid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c b/targets/f7/furi_hal/furi_hal_bt_serial.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bt_serial.c rename to targets/f7/furi_hal/furi_hal_bt_serial.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bus.c b/targets/f7/furi_hal/furi_hal_bus.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bus.c rename to targets/f7/furi_hal/furi_hal_bus.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bus.h b/targets/f7/furi_hal/furi_hal_bus.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bus.h rename to targets/f7/furi_hal/furi_hal_bus.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_clock.c rename to targets/f7/furi_hal/furi_hal_clock.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.h b/targets/f7/furi_hal/furi_hal_clock.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_clock.h rename to targets/f7/furi_hal/furi_hal_clock.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_console.c b/targets/f7/furi_hal/furi_hal_console.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_console.c rename to targets/f7/furi_hal/furi_hal_console.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_console.h b/targets/f7/furi_hal/furi_hal_console.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_console.h rename to targets/f7/furi_hal/furi_hal_console.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/targets/f7/furi_hal/furi_hal_cortex.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_cortex.c rename to targets/f7/furi_hal/furi_hal_cortex.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/targets/f7/furi_hal/furi_hal_crypto.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_crypto.c rename to targets/f7/furi_hal/furi_hal_crypto.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_debug.c b/targets/f7/furi_hal/furi_hal_debug.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_debug.c rename to targets/f7/furi_hal/furi_hal_debug.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_dma.c b/targets/f7/furi_hal/furi_hal_dma.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_dma.c rename to targets/f7/furi_hal/furi_hal_dma.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_dma.h b/targets/f7/furi_hal/furi_hal_dma.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_dma.h rename to targets/f7/furi_hal/furi_hal_dma.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_flash.c rename to targets/f7/furi_hal/furi_hal_flash.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.h b/targets/f7/furi_hal/furi_hal_flash.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_flash.h rename to targets/f7/furi_hal/furi_hal_flash.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_gpio.c rename to targets/f7/furi_hal/furi_hal_gpio.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.h b/targets/f7/furi_hal/furi_hal_gpio.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_gpio.h rename to targets/f7/furi_hal/furi_hal_gpio.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c.c b/targets/f7/furi_hal/furi_hal_i2c.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c.c rename to targets/f7/furi_hal/furi_hal_i2c.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c b/targets/f7/furi_hal/furi_hal_i2c_config.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c_config.c rename to targets/f7/furi_hal/furi_hal_i2c_config.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.h b/targets/f7/furi_hal/furi_hal_i2c_config.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c_config.h rename to targets/f7/furi_hal/furi_hal_i2c_config.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_types.h b/targets/f7/furi_hal/furi_hal_i2c_types.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c_types.h rename to targets/f7/furi_hal/furi_hal_i2c_types.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/targets/f7/furi_hal/furi_hal_ibutton.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_ibutton.c rename to targets/f7/furi_hal/furi_hal_ibutton.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.h b/targets/f7/furi_hal/furi_hal_ibutton.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_ibutton.h rename to targets/f7/furi_hal/furi_hal_ibutton.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_idle_timer.h b/targets/f7/furi_hal/furi_hal_idle_timer.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_idle_timer.h rename to targets/f7/furi_hal/furi_hal_idle_timer.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/targets/f7/furi_hal/furi_hal_info.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_info.c rename to targets/f7/furi_hal/furi_hal_info.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_infrared.c rename to targets/f7/furi_hal/furi_hal_infrared.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/targets/f7/furi_hal/furi_hal_interrupt.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_interrupt.c rename to targets/f7/furi_hal/furi_hal_interrupt.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.h b/targets/f7/furi_hal/furi_hal_interrupt.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_interrupt.h rename to targets/f7/furi_hal/furi_hal_interrupt.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_light.c rename to targets/f7/furi_hal/furi_hal_light.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/targets/f7/furi_hal/furi_hal_memory.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_memory.c rename to targets/f7/furi_hal/furi_hal_memory.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_mpu.c b/targets/f7/furi_hal/furi_hal_mpu.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_mpu.c rename to targets/f7/furi_hal/furi_hal_mpu.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/targets/f7/furi_hal/furi_hal_nfc.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc.c rename to targets/f7/furi_hal/furi_hal_nfc.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c b/targets/f7/furi_hal/furi_hal_nfc_event.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_event.c rename to targets/f7/furi_hal/furi_hal_nfc_event.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c rename to targets/f7/furi_hal/furi_hal_nfc_felica.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h b/targets/f7/furi_hal/furi_hal_nfc_i.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_i.h rename to targets/f7/furi_hal/furi_hal_nfc_i.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c b/targets/f7/furi_hal/furi_hal_nfc_irq.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c rename to targets/f7/furi_hal/furi_hal_nfc_irq.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c rename to targets/f7/furi_hal/furi_hal_nfc_iso14443a.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c rename to targets/f7/furi_hal/furi_hal_nfc_iso14443b.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c rename to targets/f7/furi_hal/furi_hal_nfc_iso15693.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h b/targets/f7/furi_hal/furi_hal_nfc_tech_i.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h rename to targets/f7/furi_hal/furi_hal_nfc_tech_i.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c b/targets/f7/furi_hal/furi_hal_nfc_timer.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c rename to targets/f7/furi_hal/furi_hal_nfc_timer.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_os.c b/targets/f7/furi_hal/furi_hal_os.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_os.c rename to targets/f7/furi_hal/furi_hal_os.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_os.h b/targets/f7/furi_hal/furi_hal_os.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_os.h rename to targets/f7/furi_hal/furi_hal_os.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_power.c rename to targets/f7/furi_hal/furi_hal_power.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_power_config.c b/targets/f7/furi_hal/furi_hal_power_config.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_power_config.c rename to targets/f7/furi_hal/furi_hal_power_config.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/targets/f7/furi_hal/furi_hal_pwm.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_pwm.c rename to targets/f7/furi_hal/furi_hal_pwm.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.h b/targets/f7/furi_hal/furi_hal_pwm.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_pwm.h rename to targets/f7/furi_hal/furi_hal_pwm.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_random.c b/targets/f7/furi_hal/furi_hal_random.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_random.c rename to targets/f7/furi_hal/furi_hal_random.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_region.c b/targets/f7/furi_hal/furi_hal_region.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_region.c rename to targets/f7/furi_hal/furi_hal_region.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_resources.c rename to targets/f7/furi_hal/furi_hal_resources.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_resources.h rename to targets/f7/furi_hal/furi_hal_resources.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.c b/targets/f7/furi_hal/furi_hal_rfid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_rfid.c rename to targets/f7/furi_hal/furi_hal_rfid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.h b/targets/f7/furi_hal/furi_hal_rfid.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_rfid.h rename to targets/f7/furi_hal/furi_hal_rfid.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/targets/f7/furi_hal/furi_hal_rtc.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_rtc.c rename to targets/f7/furi_hal/furi_hal_rtc.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_sd.c b/targets/f7/furi_hal/furi_hal_sd.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_sd.c rename to targets/f7/furi_hal/furi_hal_sd.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/targets/f7/furi_hal/furi_hal_speaker.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_speaker.c rename to targets/f7/furi_hal/furi_hal_speaker.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi.c rename to targets/f7/furi_hal/furi_hal_spi.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/targets/f7/furi_hal/furi_hal_spi_config.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi_config.c rename to targets/f7/furi_hal/furi_hal_spi_config.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.h b/targets/f7/furi_hal/furi_hal_spi_config.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi_config.h rename to targets/f7/furi_hal/furi_hal_spi_config.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_types.h b/targets/f7/furi_hal/furi_hal_spi_types.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi_types.h rename to targets/f7/furi_hal/furi_hal_spi_types.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_subghz.c rename to targets/f7/furi_hal/furi_hal_subghz.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/targets/f7/furi_hal/furi_hal_subghz.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_subghz.h rename to targets/f7/furi_hal/furi_hal_subghz.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_target_hw.h b/targets/f7/furi_hal/furi_hal_target_hw.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_target_hw.h rename to targets/f7/furi_hal/furi_hal_target_hw.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.c b/targets/f7/furi_hal/furi_hal_uart.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_uart.c rename to targets/f7/furi_hal/furi_hal_uart.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.h b/targets/f7/furi_hal/furi_hal_uart.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_uart.h rename to targets/f7/furi_hal/furi_hal_uart.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/targets/f7/furi_hal/furi_hal_usb.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb.c rename to targets/f7/furi_hal/furi_hal_usb.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c b/targets/f7/furi_hal/furi_hal_usb_ccid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c rename to targets/f7/furi_hal/furi_hal_usb_ccid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c rename to targets/f7/furi_hal/furi_hal_usb_cdc.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h b/targets/f7/furi_hal/furi_hal_usb_cdc.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h rename to targets/f7/furi_hal/furi_hal_usb_cdc.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/targets/f7/furi_hal/furi_hal_usb_hid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_hid.c rename to targets/f7/furi_hal/furi_hal_usb_hid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_i.h b/targets/f7/furi_hal/furi_hal_usb_i.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_i.h rename to targets/f7/furi_hal/furi_hal_usb_i.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c b/targets/f7/furi_hal/furi_hal_usb_u2f.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c rename to targets/f7/furi_hal/furi_hal_usb_u2f.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_version.c rename to targets/f7/furi_hal/furi_hal_version.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_version_device.c b/targets/f7/furi_hal/furi_hal_version_device.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_version_device.c rename to targets/f7/furi_hal/furi_hal_version_device.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_vibro.c b/targets/f7/furi_hal/furi_hal_vibro.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_vibro.c rename to targets/f7/furi_hal/furi_hal_vibro.c diff --git a/firmware/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h similarity index 100% rename from firmware/targets/f7/inc/FreeRTOSConfig.h rename to targets/f7/inc/FreeRTOSConfig.h diff --git a/firmware/targets/f7/inc/alt_boot.h b/targets/f7/inc/alt_boot.h similarity index 100% rename from firmware/targets/f7/inc/alt_boot.h rename to targets/f7/inc/alt_boot.h diff --git a/firmware/targets/f7/inc/stm32.h b/targets/f7/inc/stm32.h similarity index 100% rename from firmware/targets/f7/inc/stm32.h rename to targets/f7/inc/stm32.h diff --git a/firmware/targets/f7/inc/stm32_assert.h b/targets/f7/inc/stm32_assert.h similarity index 100% rename from firmware/targets/f7/inc/stm32_assert.h rename to targets/f7/inc/stm32_assert.h diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/targets/f7/platform_specific/intrinsic_export.h similarity index 100% rename from firmware/targets/f7/platform_specific/intrinsic_export.h rename to targets/f7/platform_specific/intrinsic_export.h diff --git a/firmware/targets/f7/platform_specific/math_wrapper.h b/targets/f7/platform_specific/math_wrapper.h similarity index 100% rename from firmware/targets/f7/platform_specific/math_wrapper.h rename to targets/f7/platform_specific/math_wrapper.h diff --git a/firmware/targets/f7/src/dfu.c b/targets/f7/src/dfu.c similarity index 100% rename from firmware/targets/f7/src/dfu.c rename to targets/f7/src/dfu.c diff --git a/firmware/targets/f7/src/main.c b/targets/f7/src/main.c similarity index 100% rename from firmware/targets/f7/src/main.c rename to targets/f7/src/main.c diff --git a/firmware/targets/f7/src/recovery.c b/targets/f7/src/recovery.c similarity index 100% rename from firmware/targets/f7/src/recovery.c rename to targets/f7/src/recovery.c diff --git a/firmware/targets/f7/src/system_stm32wbxx.c b/targets/f7/src/system_stm32wbxx.c similarity index 100% rename from firmware/targets/f7/src/system_stm32wbxx.c rename to targets/f7/src/system_stm32wbxx.c diff --git a/firmware/targets/f7/src/update.c b/targets/f7/src/update.c similarity index 100% rename from firmware/targets/f7/src/update.c rename to targets/f7/src/update.c diff --git a/firmware/targets/f7/startup_stm32wb55xx_cm4.s b/targets/f7/startup_stm32wb55xx_cm4.s similarity index 100% rename from firmware/targets/f7/startup_stm32wb55xx_cm4.s rename to targets/f7/startup_stm32wb55xx_cm4.s diff --git a/firmware/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld similarity index 100% rename from firmware/targets/f7/stm32wb55xx_flash.ld rename to targets/f7/stm32wb55xx_flash.ld diff --git a/firmware/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld similarity index 100% rename from firmware/targets/f7/stm32wb55xx_ram_fw.ld rename to targets/f7/stm32wb55xx_ram_fw.ld diff --git a/firmware/targets/f7/target.json b/targets/f7/target.json similarity index 100% rename from firmware/targets/f7/target.json rename to targets/f7/target.json diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal.h rename to targets/furi_hal_include/furi_hal.h diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_bt.h rename to targets/furi_hal_include/furi_hal_bt.h diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/targets/furi_hal_include/furi_hal_bt_hid.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_bt_hid.h rename to targets/furi_hal_include/furi_hal_bt_hid.h diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h b/targets/furi_hal_include/furi_hal_bt_serial.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_bt_serial.h rename to targets/furi_hal_include/furi_hal_bt_serial.h diff --git a/firmware/targets/furi_hal_include/furi_hal_cortex.h b/targets/furi_hal_include/furi_hal_cortex.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_cortex.h rename to targets/furi_hal_include/furi_hal_cortex.h diff --git a/firmware/targets/furi_hal_include/furi_hal_crypto.h b/targets/furi_hal_include/furi_hal_crypto.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_crypto.h rename to targets/furi_hal_include/furi_hal_crypto.h diff --git a/firmware/targets/furi_hal_include/furi_hal_debug.h b/targets/furi_hal_include/furi_hal_debug.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_debug.h rename to targets/furi_hal_include/furi_hal_debug.h diff --git a/firmware/targets/furi_hal_include/furi_hal_i2c.h b/targets/furi_hal_include/furi_hal_i2c.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_i2c.h rename to targets/furi_hal_include/furi_hal_i2c.h diff --git a/firmware/targets/furi_hal_include/furi_hal_info.h b/targets/furi_hal_include/furi_hal_info.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_info.h rename to targets/furi_hal_include/furi_hal_info.h diff --git a/firmware/targets/furi_hal_include/furi_hal_infrared.h b/targets/furi_hal_include/furi_hal_infrared.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_infrared.h rename to targets/furi_hal_include/furi_hal_infrared.h diff --git a/firmware/targets/furi_hal_include/furi_hal_light.h b/targets/furi_hal_include/furi_hal_light.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_light.h rename to targets/furi_hal_include/furi_hal_light.h diff --git a/firmware/targets/furi_hal_include/furi_hal_memory.h b/targets/furi_hal_include/furi_hal_memory.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_memory.h rename to targets/furi_hal_include/furi_hal_memory.h diff --git a/firmware/targets/furi_hal_include/furi_hal_mpu.h b/targets/furi_hal_include/furi_hal_mpu.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_mpu.h rename to targets/furi_hal_include/furi_hal_mpu.h diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_nfc.h rename to targets/furi_hal_include/furi_hal_nfc.h diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_power.h rename to targets/furi_hal_include/furi_hal_power.h diff --git a/firmware/targets/furi_hal_include/furi_hal_random.h b/targets/furi_hal_include/furi_hal_random.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_random.h rename to targets/furi_hal_include/furi_hal_random.h diff --git a/firmware/targets/furi_hal_include/furi_hal_region.h b/targets/furi_hal_include/furi_hal_region.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_region.h rename to targets/furi_hal_include/furi_hal_region.h diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/targets/furi_hal_include/furi_hal_rtc.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_rtc.h rename to targets/furi_hal_include/furi_hal_rtc.h diff --git a/firmware/targets/furi_hal_include/furi_hal_sd.h b/targets/furi_hal_include/furi_hal_sd.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_sd.h rename to targets/furi_hal_include/furi_hal_sd.h diff --git a/firmware/targets/furi_hal_include/furi_hal_speaker.h b/targets/furi_hal_include/furi_hal_speaker.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_speaker.h rename to targets/furi_hal_include/furi_hal_speaker.h diff --git a/firmware/targets/furi_hal_include/furi_hal_spi.h b/targets/furi_hal_include/furi_hal_spi.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_spi.h rename to targets/furi_hal_include/furi_hal_spi.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb.h b/targets/furi_hal_include/furi_hal_usb.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb.h rename to targets/furi_hal_include/furi_hal_usb.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h b/targets/furi_hal_include/furi_hal_usb_ccid.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb_ccid.h rename to targets/furi_hal_include/furi_hal_usb_ccid.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/targets/furi_hal_include/furi_hal_usb_hid.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb_hid.h rename to targets/furi_hal_include/furi_hal_usb_hid.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h b/targets/furi_hal_include/furi_hal_usb_hid_u2f.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h rename to targets/furi_hal_include/furi_hal_usb_hid_u2f.h diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/targets/furi_hal_include/furi_hal_version.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_version.h rename to targets/furi_hal_include/furi_hal_version.h diff --git a/firmware/targets/furi_hal_include/furi_hal_vibro.h b/targets/furi_hal_include/furi_hal_vibro.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_vibro.h rename to targets/furi_hal_include/furi_hal_vibro.h From c8180747dbfb7b6976bca9226ae42227737e0d0d Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:20:35 +0300 Subject: [PATCH 012/111] [FL-3456] Allow for larger Infrared remotes (#3164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Do not load all signals at once (Draft) * Minor cleanup * Refactor remote renaming * Improve function signatures * Rename infrared_remote functions * Optimise signal loading * Implement adding signals to remote * Add read_name() method * Deprecate a function * Partially implement deleting signals (draft) * Use m-array instead of m-list for signal name directory * Use plain C strings instead of furi_string * Implement deleting signals * Implement deleting signals via generalised callback * Implement renaming signals * Rename some types * Some more renaming * Remove unused type * Implement inserting signals (internal use) * Improve InfraredMoveView * Send an event to move a signal * Remove unused type * Implement moving signals * Implement creating new remotes with one signal * Un-deprecate and rename a function * Add InfraredRemote API docs * Add InfraredSignal API docs * Better error messages * Show progress pop-up when moving buttons in a remote * Copy labels to the InfraredMoveView to avoid pointer invalidation * Improve file selection scene * Show progress pop-up when renaming buttons in a remote * Refactor a scene * Show progress when deleting a button from remote * Use a random name for temp files * Add docs to infrared_brute_force.h * Rename Infrared type to InfraredApp * Add docs to infrared_app_i.h Co-authored-by: あく --- applications/main/infrared/infrared.h | 3 - .../infrared/{infrared.c => infrared_app.c} | 183 ++++---- applications/main/infrared/infrared_app.h | 15 + applications/main/infrared/infrared_app_i.h | 288 ++++++++++++ .../main/infrared/infrared_brute_force.c | 6 +- .../main/infrared/infrared_brute_force.h | 89 +++- applications/main/infrared/infrared_cli.c | 4 +- applications/main/infrared/infrared_i.h | 146 ------ applications/main/infrared/infrared_remote.c | 439 +++++++++++++----- applications/main/infrared/infrared_remote.h | 231 ++++++++- .../main/infrared/infrared_remote_button.c | 37 -- .../main/infrared/infrared_remote_button.h | 14 - applications/main/infrared/infrared_signal.c | 54 +-- applications/main/infrared/infrared_signal.h | 179 ++++++- .../common/infrared_scene_universal_common.c | 17 +- .../infrared/scenes/infrared_scene_ask_back.c | 10 +- .../scenes/infrared_scene_ask_retry.c | 10 +- .../infrared/scenes/infrared_scene_debug.c | 14 +- .../infrared/scenes/infrared_scene_edit.c | 10 +- .../infrared_scene_edit_button_select.c | 16 +- .../scenes/infrared_scene_edit_delete.c | 62 ++- .../scenes/infrared_scene_edit_delete_done.c | 8 +- .../scenes/infrared_scene_edit_move.c | 73 ++- .../scenes/infrared_scene_edit_rename.c | 35 +- .../scenes/infrared_scene_edit_rename_done.c | 8 +- .../scenes/infrared_scene_error_databases.c | 8 +- .../infrared/scenes/infrared_scene_learn.c | 8 +- .../scenes/infrared_scene_learn_done.c | 8 +- .../scenes/infrared_scene_learn_enter_name.c | 37 +- .../scenes/infrared_scene_learn_success.c | 20 +- .../infrared/scenes/infrared_scene_remote.c | 16 +- .../scenes/infrared_scene_remote_list.c | 31 +- .../main/infrared/scenes/infrared_scene_rpc.c | 13 +- .../infrared/scenes/infrared_scene_start.c | 10 +- .../scenes/infrared_scene_universal.c | 10 +- .../scenes/infrared_scene_universal_ac.c | 4 +- .../scenes/infrared_scene_universal_audio.c | 4 +- .../infrared_scene_universal_projector.c | 4 +- .../scenes/infrared_scene_universal_tv.c | 4 +- .../main/infrared/views/infrared_move_view.c | 200 ++++---- .../main/infrared/views/infrared_move_view.h | 13 +- 41 files changed, 1594 insertions(+), 747 deletions(-) delete mode 100644 applications/main/infrared/infrared.h rename applications/main/infrared/{infrared.c => infrared_app.c} (74%) create mode 100644 applications/main/infrared/infrared_app.h create mode 100644 applications/main/infrared/infrared_app_i.h delete mode 100644 applications/main/infrared/infrared_i.h delete mode 100644 applications/main/infrared/infrared_remote_button.c delete mode 100644 applications/main/infrared/infrared_remote_button.h diff --git a/applications/main/infrared/infrared.h b/applications/main/infrared/infrared.h deleted file mode 100644 index e5eeb11772..0000000000 --- a/applications/main/infrared/infrared.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -typedef struct Infrared Infrared; diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared_app.c similarity index 74% rename from applications/main/infrared/infrared.c rename to applications/main/infrared/infrared_app.c index fcf45c25c2..e29eda30fb 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared_app.c @@ -1,48 +1,52 @@ -#include "infrared_i.h" +#include "infrared_app_i.h" #include +#include #include +#define TAG "InfraredApp" + #define INFRARED_TX_MIN_INTERVAL_MS 50U -static const NotificationSequence* infrared_notification_sequences[] = { - &sequence_success, - &sequence_set_only_green_255, - &sequence_reset_green, - &sequence_solid_yellow, - &sequence_reset_rgb, - &sequence_blink_start_cyan, - &sequence_blink_start_magenta, - &sequence_blink_stop, +static const NotificationSequence* + infrared_notification_sequences[InfraredNotificationMessageCount] = { + &sequence_success, + &sequence_set_only_green_255, + &sequence_reset_green, + &sequence_solid_yellow, + &sequence_reset_rgb, + &sequence_blink_start_cyan, + &sequence_blink_start_magenta, + &sequence_blink_stop, }; -static void infrared_make_app_folder(Infrared* infrared) { +static void infrared_make_app_folder(InfraredApp* infrared) { if(!storage_simply_mkdir(infrared->storage, INFRARED_APP_FOLDER)) { - dialog_message_show_storage_error(infrared->dialogs, "Cannot create\napp folder"); + infrared_show_error_message(infrared, "Cannot create\napp folder"); } } static bool infrared_custom_event_callback(void* context, uint32_t event) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; return scene_manager_handle_custom_event(infrared->scene_manager, event); } static bool infrared_back_event_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; return scene_manager_handle_back_event(infrared->scene_manager); } static void infrared_tick_event_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; scene_manager_handle_tick_event(infrared->scene_manager); } static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; furi_assert(infrared->rpc_ctx); if(event == RpcAppEventSessionClose) { @@ -109,8 +113,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path) furi_record_close(RECORD_STORAGE); } -static Infrared* infrared_alloc() { - Infrared* infrared = malloc(sizeof(Infrared)); +static InfraredApp* infrared_alloc() { + InfraredApp* infrared = malloc(sizeof(InfraredApp)); infrared->file_path = furi_string_alloc(); @@ -139,7 +143,7 @@ static Infrared* infrared_alloc() { infrared->worker = infrared_worker_alloc(); infrared->remote = infrared_remote_alloc(); - infrared->received_signal = infrared_signal_alloc(); + infrared->current_signal = infrared_signal_alloc(); infrared->brute_force = infrared_brute_force_alloc(); infrared->submenu = submenu_alloc(); @@ -184,7 +188,7 @@ static Infrared* infrared_alloc() { return infrared; } -static void infrared_free(Infrared* infrared) { +static void infrared_free(InfraredApp* infrared) { furi_assert(infrared); ViewDispatcher* view_dispatcher = infrared->view_dispatcher; InfraredAppState* app_state = &infrared->app_state; @@ -229,7 +233,7 @@ static void infrared_free(Infrared* infrared) { scene_manager_free(infrared->scene_manager); infrared_brute_force_free(infrared->brute_force); - infrared_signal_free(infrared->received_signal); + infrared_signal_free(infrared->current_signal); infrared_remote_free(infrared->remote); infrared_worker_free(infrared->worker); @@ -248,65 +252,61 @@ static void infrared_free(Infrared* infrared) { } bool infrared_add_remote_with_button( - Infrared* infrared, + const InfraredApp* infrared, const char* button_name, - InfraredSignal* signal) { + const InfraredSignal* signal) { InfraredRemote* remote = infrared->remote; - FuriString *new_name, *new_path; - new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME); - new_path = furi_string_alloc_set(INFRARED_APP_FOLDER); + FuriString* new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME); + FuriString* new_path = furi_string_alloc_set(INFRARED_APP_FOLDER); infrared_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path)); furi_string_cat_printf( new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); - infrared_remote_reset(remote); - infrared_remote_set_name(remote, furi_string_get_cstr(new_name)); - infrared_remote_set_path(remote, furi_string_get_cstr(new_path)); + bool success = false; + + do { + if(!infrared_remote_create(remote, furi_string_get_cstr(new_path))) break; + if(!infrared_remote_append_signal(remote, signal, button_name)) break; + success = true; + } while(false); furi_string_free(new_name); furi_string_free(new_path); - return infrared_remote_add_button(remote, button_name, signal); + + return success; } -bool infrared_rename_current_remote(Infrared* infrared, const char* name) { +bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) { InfraredRemote* remote = infrared->remote; - const char* remote_path = infrared_remote_get_path(remote); + const char* old_path = infrared_remote_get_path(remote); - if(!strcmp(infrared_remote_get_name(remote), name)) { + if(!strcmp(infrared_remote_get_name(remote), new_name)) { return true; } - FuriString* new_name; - new_name = furi_string_alloc_set(name); + FuriString* new_name_fstr = furi_string_alloc_set(new_name); + FuriString* new_path_fstr = furi_string_alloc_set(old_path); - infrared_find_vacant_remote_name(new_name, remote_path); + infrared_find_vacant_remote_name(new_name_fstr, old_path); - FuriString* new_path; - new_path = furi_string_alloc_set(infrared_remote_get_path(remote)); - if(furi_string_end_with(new_path, INFRARED_APP_EXTENSION)) { - size_t filename_start = furi_string_search_rchar(new_path, '/'); - furi_string_left(new_path, filename_start); + if(furi_string_end_with(new_path_fstr, INFRARED_APP_EXTENSION)) { + path_extract_dirname(old_path, new_path_fstr); } - furi_string_cat_printf( - new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); - Storage* storage = furi_record_open(RECORD_STORAGE); + path_append(new_path_fstr, furi_string_get_cstr(new_name_fstr)); + furi_string_cat(new_path_fstr, INFRARED_APP_EXTENSION); - FS_Error status = storage_common_rename( - storage, infrared_remote_get_path(remote), furi_string_get_cstr(new_path)); - infrared_remote_set_name(remote, furi_string_get_cstr(new_name)); - infrared_remote_set_path(remote, furi_string_get_cstr(new_path)); + const bool success = infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr)); - furi_string_free(new_name); - furi_string_free(new_path); + furi_string_free(new_name_fstr); + furi_string_free(new_path_fstr); - furi_record_close(RECORD_STORAGE); - return (status == FSE_OK || status == FSE_EXIST); + return success; } -void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { +void infrared_tx_start(InfraredApp* infrared) { if(infrared->app_state.is_transmitting) { return; } @@ -317,12 +317,12 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { return; } - if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + if(infrared_signal_is_raw(infrared->current_signal)) { + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(infrared->current_signal); infrared_worker_set_raw_signal( infrared->worker, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal); infrared_worker_set_decoded_signal(infrared->worker, message); } @@ -336,20 +336,20 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { infrared->app_state.is_transmitting = true; } -void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) { - furi_assert(button_index < infrared_remote_get_button_count(infrared->remote)); - - InfraredRemoteButton* button = infrared_remote_get_button(infrared->remote, button_index); - InfraredSignal* signal = infrared_remote_button_get_signal(button); - - infrared_tx_start_signal(infrared, signal); -} +void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { + furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote)); -void infrared_tx_start_received(Infrared* infrared) { - infrared_tx_start_signal(infrared, infrared->received_signal); + if(infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index)) { + infrared_tx_start(infrared); + } else { + infrared_show_error_message( + infrared, + "Failed to load\n\"%s\"", + infrared_remote_get_signal_name(infrared->remote, button_index)); + } } -void infrared_tx_stop(Infrared* infrared) { +void infrared_tx_stop(InfraredApp* infrared) { if(!infrared->app_state.is_transmitting) { return; } @@ -363,25 +363,27 @@ void infrared_tx_stop(Infrared* infrared) { infrared->app_state.last_transmit_time = furi_get_tick(); } -void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) { +void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) { va_list args; - va_start(args, text); + va_start(args, fmt); - vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, text, args); + vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, fmt, args); va_end(args); } -void infrared_text_store_clear(Infrared* infrared, uint32_t bank) { +void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank) { memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE + 1); } -void infrared_play_notification_message(Infrared* infrared, uint32_t message) { - furi_assert(message < sizeof(infrared_notification_sequences) / sizeof(NotificationSequence*)); +void infrared_play_notification_message( + const InfraredApp* infrared, + InfraredNotificationMessage message) { + furi_assert(message < InfraredNotificationMessageCount); notification_message(infrared->notifications, infrared_notification_sequences[message]); } -void infrared_show_loading_popup(Infrared* infrared, bool show) { +void infrared_show_loading_popup(const InfraredApp* infrared, bool show) { TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); ViewStack* view_stack = infrared->view_stack; Loading* loading = infrared->loading; @@ -397,19 +399,30 @@ void infrared_show_loading_popup(Infrared* infrared, bool show) { } } +void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + + FuriString* message = furi_string_alloc_vprintf(fmt, args); + dialog_message_show_storage_error(infrared->dialogs, furi_string_get_cstr(message)); + + furi_string_free(message); + va_end(args); +} + void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; if(infrared_worker_signal_is_decoded(received_signal)) { infrared_signal_set_message( - infrared->received_signal, infrared_worker_get_decoded_signal(received_signal)); + infrared->current_signal, infrared_worker_get_decoded_signal(received_signal)); } else { const uint32_t* timings; size_t timings_size; infrared_worker_get_raw_signal(received_signal, &timings, &timings_size); infrared_signal_set_raw_signal( - infrared->received_signal, + infrared->current_signal, timings, timings_size, INFRARED_COMMON_CARRIER_FREQUENCY, @@ -422,20 +435,20 @@ void infrared_signal_received_callback(void* context, InfraredWorkerSignal* rece void infrared_text_input_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTextEditDone); } void infrared_popup_closed_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypePopupClosed); } int32_t infrared_app(void* p) { - Infrared* infrared = infrared_alloc(); + InfraredApp* infrared = infrared_alloc(); infrared_make_app_folder(infrared); @@ -451,13 +464,15 @@ int32_t infrared_app(void* p) { rpc_system_app_send_started(infrared->rpc_ctx); is_rpc_mode = true; } else { - furi_string_set(infrared->file_path, (const char*)p); - is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); + const char* file_path = (const char*)p; + is_remote_loaded = infrared_remote_load(infrared->remote, file_path); + if(!is_remote_loaded) { - dialog_message_show_storage_error( - infrared->dialogs, "Failed to load\nselected remote"); + infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); return -1; } + + furi_string_set(infrared->file_path, file_path); } } diff --git a/applications/main/infrared/infrared_app.h b/applications/main/infrared/infrared_app.h new file mode 100644 index 0000000000..a6f87402a9 --- /dev/null +++ b/applications/main/infrared/infrared_app.h @@ -0,0 +1,15 @@ +/** + * @file infrared_app.h + * @brief Infrared application - start here. + * + * @see infrared_app_i.h for the main application data structure and functions. + * @see infrared_signal.h for the infrared signal library - loading, storing and transmitting signals. + * @see infrared_remote.hl for the infrared remote library - loading, storing and manipulating remotes. + * @see infrared_brute_force.h for the infrared brute force - loading and transmitting multiple signals. + */ +#pragma once + +/** + * @brief InfraredApp opaque type declaration. + */ +typedef struct InfraredApp InfraredApp; diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h new file mode 100644 index 0000000000..c9dfe3ab86 --- /dev/null +++ b/applications/main/infrared/infrared_app_i.h @@ -0,0 +1,288 @@ +/** + * @file infrared_app_i.h + * @brief Main Infrared application types and functions. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "infrared_app.h" +#include "infrared_remote.h" +#include "infrared_brute_force.h" +#include "infrared_custom_event.h" + +#include "scenes/infrared_scene.h" +#include "views/infrared_progress_view.h" +#include "views/infrared_debug_view.h" +#include "views/infrared_move_view.h" + +#include "rpc/rpc_app.h" + +#define INFRARED_FILE_NAME_SIZE 100 +#define INFRARED_TEXT_STORE_NUM 2 +#define INFRARED_TEXT_STORE_SIZE 128 + +#define INFRARED_MAX_BUTTON_NAME_LENGTH 22 +#define INFRARED_MAX_REMOTE_NAME_LENGTH 22 + +#define INFRARED_APP_FOLDER ANY_PATH("infrared") +#define INFRARED_APP_EXTENSION ".ir" + +#define INFRARED_DEFAULT_REMOTE_NAME "Remote" +#define INFRARED_LOG_TAG "InfraredApp" + +/** + * @brief Enumeration of invalid remote button indices. + */ +typedef enum { + InfraredButtonIndexNone = -1, /**< No button is currently selected. */ +} InfraredButtonIndex; + +/** + * @brief Enumeration of editing targets. + */ +typedef enum { + InfraredEditTargetNone, /**< No editing target is selected. */ + InfraredEditTargetRemote, /**< Whole remote is selected as editing target. */ + InfraredEditTargetButton, /**< Single button is selected as editing target. */ +} InfraredEditTarget; + +/** + * @brief Enumeration of editing modes. + */ +typedef enum { + InfraredEditModeNone, /**< No editing mode is selected. */ + InfraredEditModeRename, /**< Rename mode is selected. */ + InfraredEditModeDelete, /**< Delete mode is selected. */ +} InfraredEditMode; + +/** + * @brief Infrared application state type. + */ +typedef struct { + bool is_learning_new_remote; /**< Learning new remote or adding to an existing one. */ + bool is_debug_enabled; /**< Whether to enable or disable debugging features. */ + bool is_transmitting; /**< Whether a signal is currently being transmitted. */ + InfraredEditTarget edit_target : 8; /**< Selected editing target (a remote or a button). */ + InfraredEditMode edit_mode : 8; /**< Selected editing operation (rename or delete). */ + int32_t current_button_index; /**< Selected button index (move destination). */ + int32_t prev_button_index; /**< Previous button index (move source). */ + uint32_t last_transmit_time; /**< Lat time a signal was transmitted. */ +} InfraredAppState; + +/** + * @brief Infrared application type. + */ +struct InfraredApp { + SceneManager* scene_manager; /**< Pointer to a SceneManager instance. */ + ViewDispatcher* view_dispatcher; /**< Pointer to a ViewDispatcher instance. */ + + Gui* gui; /**< Pointer to a Gui instance. */ + Storage* storage; /**< Pointer to a Storage instance. */ + DialogsApp* dialogs; /**< Pointer to a DialogsApp instance. */ + NotificationApp* notifications; /**< Pointer to a NotificationApp instance. */ + InfraredWorker* worker; /**< Used to send or receive signals. */ + InfraredRemote* remote; /**< Holds the currently loaded remote. */ + InfraredSignal* current_signal; /**< Holds the currently loaded signal. */ + InfraredBruteForce* brute_force; /**< Used for the Universal Remote feature. */ + + Submenu* submenu; /**< Standard view for displaying application menus. */ + TextInput* text_input; /**< Standard view for receiving user text input. */ + DialogEx* dialog_ex; /**< Standard view for displaying dialogs. */ + ButtonMenu* button_menu; /**< Custom view for interacting with IR remotes. */ + Popup* popup; /**< Standard view for displaying messages. */ + + ViewStack* view_stack; /**< Standard view for displaying stacked interfaces. */ + InfraredDebugView* debug_view; /**< Custom view for displaying debug information. */ + InfraredMoveView* move_view; /**< Custom view for rearranging buttons in a remote. */ + + ButtonPanel* button_panel; /**< Standard view for displaying control panels. */ + Loading* loading; /**< Standard view for informing about long operations. */ + InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ + + FuriString* file_path; /**< Full path to the currently loaded file. */ + /** Arbitrary text storage for various inputs. */ + char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; + InfraredAppState app_state; /**< Application state. */ + + void* rpc_ctx; /**< Pointer to the RPC context object. */ +}; + +/** + * @brief Enumeration of all used view types. + */ +typedef enum { + InfraredViewSubmenu, + InfraredViewTextInput, + InfraredViewDialogEx, + InfraredViewButtonMenu, + InfraredViewPopup, + InfraredViewStack, + InfraredViewDebugView, + InfraredViewMove, +} InfraredView; + +/** + * @brief Enumeration of all notification message types. + */ +typedef enum { + InfraredNotificationMessageSuccess, /**< Play a short happy tune. */ + InfraredNotificationMessageGreenOn, /**< Turn green LED on. */ + InfraredNotificationMessageGreenOff, /**< Turn green LED off. */ + InfraredNotificationMessageYellowOn, /**< Turn yellow LED on. */ + InfraredNotificationMessageYellowOff, /**< Turn yellow LED off. */ + InfraredNotificationMessageBlinkStartRead, /**< Blink the LED to indicate receiver mode. */ + InfraredNotificationMessageBlinkStartSend, /**< Blink the LED to indicate transmitter mode. */ + InfraredNotificationMessageBlinkStop, /**< Stop blinking the LED. */ + InfraredNotificationMessageCount, /**< Special value equal to the message type count. */ +} InfraredNotificationMessage; + +/** + * @brief Add a new remote with a single signal. + * + * The filename will be automatically generated depending on + * the names and number of other files in the infrared data directory. + * + * @param[in] infrared pointer to the application instance. + * @param[in] name pointer to a zero-terminated string containing the signal name. + * @param[in] signal pointer to the signal to be added. + * @return true if the remote was successfully created, false otherwise. + */ +bool infrared_add_remote_with_button( + const InfraredApp* infrared, + const char* name, + const InfraredSignal* signal); + +/** + * @brief Rename the currently loaded remote. + * + * @param[in] infrared pointer to the application instance. + * @param[in] new_name pointer to a zero-terminated string containing the new remote name. + * @return true if the remote was successfully renamed, false otherwise. + */ +bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name); + +/** + * @brief Begin transmission of the currently loaded signal. + * + * The signal will be repeated indefinitely until stopped. + * + * @param[in,out] infrared pointer to the application instance. + */ +void infrared_tx_start(InfraredApp* infrared); + +/** + * @brief Load a signal under the given index and begin transmission. + * + * The signal will be repeated indefinitely until stopped. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] button_index index of the signal to be loaded. + * @returns true if the signal could be loaded, false otherwise. + */ +void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); + +/** + * @brief Stop transmission of the currently loaded signal. + * + * @param[in,out] infrared pointer to the application instance. + */ +void infrared_tx_stop(InfraredApp* infrared); + +/** + * @brief Set the internal text store with formatted text. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] bank index of text store bank (0 or 1). + * @param[in] fmt pointer to a zero-terminated string containing the format text. + * @param[in] ... additional arguments. + */ +void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) + _ATTRIBUTE((__format__(__printf__, 3, 4))); + +/** + * @brief Clear the internal text store. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] bank index of text store bank (0 or 1). + */ +void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank); + +/** + * @brief Play a sound and/or blink the LED. + * + * @param[in] infrared pointer to the application instance. + * @param[in] message type of the message to play. + */ +void infrared_play_notification_message( + const InfraredApp* infrared, + InfraredNotificationMessage message); + +/** + * @brief Show a loading pop-up screen. + * + * In order for this to work, a Stack view must be currently active and + * the main view must be added to it. + * + * @param[in] infrared pointer to the application instance. + * @param[in] show whether to show or hide the pop-up. + */ +void infrared_show_loading_popup(const InfraredApp* infrared, bool show); + +/** + * @brief Show a formatted error messsage. + * + * @param[in] infrared pointer to the application instance. + * @param[in] fmt pointer to a zero-terminated string containing the format text. + * @param[in] ... additional arguments. + */ +void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); + +/** + * @brief Common received signal callback. + * + * Called when the worker has received a complete infrared signal. + * + * @param[in,out] context pointer to the user-specified context object. + * @param[in] received_signal pointer to the received signal. + */ +void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal); + +/** + * @brief Common text input callback. + * + * Called when the input has been accepted by the user. + * + * @param[in,out] context pointer to the user-specified context object. + */ +void infrared_text_input_callback(void* context); + +/** + * @brief Common popup close callback. + * + * Called when the popup has been closed either by the user or after a timeout. + * + * @param[in,out] context pointer to the user-specified context object. + */ +void infrared_popup_closed_callback(void* context); diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 3ca5c409f7..bb7992ae61 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -111,7 +111,7 @@ bool infrared_brute_force_start( return success; } -bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) { +bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) { return brute_force->is_started; } @@ -129,7 +129,9 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { furi_assert(brute_force->is_started); const bool success = infrared_signal_search_and_read( - brute_force->current_signal, brute_force->ff, brute_force->current_record_name); + brute_force->current_signal, + brute_force->ff, + furi_string_get_cstr(brute_force->current_record_name)); if(success) { infrared_signal_transmit(brute_force->current_signal); } diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index 042d1556b7..33677d2ec1 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -1,23 +1,110 @@ +/** + * @file infrared_brute_force.h + * @brief Infrared signal brute-forcing library. + * + * The BruteForce library is used to send large quantities of signals, + * sorted by a category. It is used to implement the Universal Remote + * feature. + */ #pragma once #include #include +/** + * @brief InfraredBruteForce opaque type declaration. + */ typedef struct InfraredBruteForce InfraredBruteForce; +/** + * @brief Create a new InfraredBruteForce instance. + * + * @returns pointer to the created instance. + */ InfraredBruteForce* infrared_brute_force_alloc(); + +/** + * @brief Delete an InfraredBruteForce instance. + * + * @param[in,out] brute_force pointer to the instance to be deleted. + */ void infrared_brute_force_free(InfraredBruteForce* brute_force); + +/** + * @brief Set an InfraredBruteForce instance to use a signal database contained in a file. + * + * @param[in,out] brute_force pointer to the instance to be configured. + * @param[in] db_filename pointer to a zero-terminated string containing a full path to the database file. + */ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename); + +/** + * @brief Build a signal dictionary from a previously set database file. + * + * This function must be called each time after setting the database via + * a infrared_brute_force_set_db_filename() call. + * + * @param[in,out] brute_force pointer to the instance to be updated. + * @returns true on success, false otherwise. + */ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); + +/** + * @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary. + * + * @param[in,out] brute_force pointer to the instance to be started. + * @param[in] index index of the signal category in the dictionary. + * @returns true on success, false otherwise. + */ bool infrared_brute_force_start( InfraredBruteForce* brute_force, uint32_t index, uint32_t* record_count); -bool infrared_brute_force_is_started(InfraredBruteForce* brute_force); + +/** + * @brief Determine whether the transmission was started. + * + * @param[in] brute_force pointer to the instance to be tested. + * @returns true if transmission was started, false otherwise. + */ +bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force); + +/** + * @brief Stop transmitting the signals. + * + * @param[in] brute_force pointer to the instance to be stopped. + */ void infrared_brute_force_stop(InfraredBruteForce* brute_force); + +/** + * @brief Send the next signal from the chosen category. + * + * This function is called repeatedly until no more signals are left + * in the chosen signal category. + * + * @warning Transmission must be started first by calling infrared_brute_force_start() + * before calling this function. + * + * @param[in,out] brute_force pointer to the instance to be used. + * @returns true if the next signal existed and could be transmitted, false otherwise. + */ bool infrared_brute_force_send_next(InfraredBruteForce* brute_force); + +/** + * @brief Add a signal category to an InfraredBruteForce instance's dictionary. + * + * @param[in,out] brute_force pointer to the instance to be updated. + * @param[in] index index of the category to be added. + * @param[in] name name of the category to be added. + */ void infrared_brute_force_add_record( InfraredBruteForce* brute_force, uint32_t index, const char* name); + +/** + * @brief Reset an InfraredBruteForce instance. + * + * @param[in,out] brute_force pointer to the instance to be reset. + */ void infrared_brute_force_reset(InfraredBruteForce* brute_force); diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 54b5cabab7..c960ffa285 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -202,7 +202,7 @@ static bool } static bool infrared_cli_decode_raw_signal( - InfraredRawSignal* raw_signal, + const InfraredRawSignal* raw_signal, InfraredDecoderHandler* decoder, FlipperFormat* output_file, const char* signal_name) { @@ -274,7 +274,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o continue; } } - InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal); + const InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal); printf( "Raw signal: %s, %zu samples\r\n", furi_string_get_cstr(tmp), diff --git a/applications/main/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h deleted file mode 100644 index 6fb6a65c7b..0000000000 --- a/applications/main/infrared/infrared_i.h +++ /dev/null @@ -1,146 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include - -#include "infrared.h" -#include "infrared_remote.h" -#include "infrared_brute_force.h" -#include "infrared_custom_event.h" - -#include "scenes/infrared_scene.h" -#include "views/infrared_progress_view.h" -#include "views/infrared_debug_view.h" -#include "views/infrared_move_view.h" - -#include "rpc/rpc_app.h" - -#define INFRARED_FILE_NAME_SIZE 100 -#define INFRARED_TEXT_STORE_NUM 2 -#define INFRARED_TEXT_STORE_SIZE 128 - -#define INFRARED_MAX_BUTTON_NAME_LENGTH 22 -#define INFRARED_MAX_REMOTE_NAME_LENGTH 22 - -#define INFRARED_APP_FOLDER ANY_PATH("infrared") -#define INFRARED_APP_EXTENSION ".ir" - -#define INFRARED_DEFAULT_REMOTE_NAME "Remote" -#define INFRARED_LOG_TAG "InfraredApp" - -typedef enum { - InfraredButtonIndexNone = -1, -} InfraredButtonIndex; - -typedef enum { - InfraredEditTargetNone, - InfraredEditTargetRemote, - InfraredEditTargetButton, -} InfraredEditTarget; - -typedef enum { - InfraredEditModeNone, - InfraredEditModeRename, - InfraredEditModeDelete, -} InfraredEditMode; - -typedef struct { - bool is_learning_new_remote; - bool is_debug_enabled; - bool is_transmitting; - InfraredEditTarget edit_target : 8; - InfraredEditMode edit_mode : 8; - int32_t current_button_index; - int32_t current_button_index_move_orig; - uint32_t last_transmit_time; -} InfraredAppState; - -struct Infrared { - SceneManager* scene_manager; - ViewDispatcher* view_dispatcher; - - Gui* gui; - Storage* storage; - DialogsApp* dialogs; - NotificationApp* notifications; - InfraredWorker* worker; - InfraredRemote* remote; - InfraredSignal* received_signal; - InfraredBruteForce* brute_force; - - Submenu* submenu; - TextInput* text_input; - DialogEx* dialog_ex; - ButtonMenu* button_menu; - Popup* popup; - - ViewStack* view_stack; - InfraredDebugView* debug_view; - InfraredMoveView* move_view; - - ButtonPanel* button_panel; - Loading* loading; - InfraredProgressView* progress; - - FuriString* file_path; - char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; - InfraredAppState app_state; - - void* rpc_ctx; -}; - -typedef enum { - InfraredViewSubmenu, - InfraredViewTextInput, - InfraredViewDialogEx, - InfraredViewButtonMenu, - InfraredViewPopup, - InfraredViewStack, - InfraredViewDebugView, - InfraredViewMove, -} InfraredView; - -typedef enum { - InfraredNotificationMessageSuccess, - InfraredNotificationMessageGreenOn, - InfraredNotificationMessageGreenOff, - InfraredNotificationMessageYellowOn, - InfraredNotificationMessageYellowOff, - InfraredNotificationMessageBlinkStartRead, - InfraredNotificationMessageBlinkStartSend, - InfraredNotificationMessageBlinkStop, -} InfraredNotificationMessage; - -bool infrared_add_remote_with_button(Infrared* infrared, const char* name, InfraredSignal* signal); -bool infrared_rename_current_remote(Infrared* infrared, const char* name); -void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal); -void infrared_tx_start_button_index(Infrared* infrared, size_t button_index); -void infrared_tx_start_received(Infrared* infrared); -void infrared_tx_stop(Infrared* infrared); -void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...); -void infrared_text_store_clear(Infrared* infrared, uint32_t bank); -void infrared_play_notification_message(Infrared* infrared, uint32_t message); -void infrared_show_loading_popup(Infrared* infrared, bool show); - -void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal); -void infrared_text_input_callback(void* context); -void infrared_popup_closed_callback(void* context); diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 70d1b59ef3..5b241fe9f6 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -1,197 +1,428 @@ #include "infrared_remote.h" -#include -#include -#include #include + +#include #include #include -#include #define TAG "InfraredRemote" -ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST); +#define INFRARED_FILE_HEADER "IR signals file" +#define INFRARED_FILE_VERSION (1) + +ARRAY_DEF(StringArray, const char*, M_CSTR_DUP_OPLIST); //-V575 struct InfraredRemote { - InfraredButtonArray_t buttons; + StringArray_t signal_names; FuriString* name; FuriString* path; }; -static void infrared_remote_clear_buttons(InfraredRemote* remote) { - InfraredButtonArray_it_t it; - for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); - InfraredButtonArray_next(it)) { - infrared_remote_button_free(*InfraredButtonArray_cref(it)); - } - InfraredButtonArray_reset(remote->buttons); -} +typedef struct { + InfraredRemote* remote; + FlipperFormat* ff_in; + FlipperFormat* ff_out; + FuriString* signal_name; + InfraredSignal* signal; + size_t signal_index; +} InfraredBatch; + +typedef struct { + size_t signal_index; + const char* signal_name; + const InfraredSignal* signal; +} InfraredBatchTarget; + +typedef bool ( + *InfraredBatchCallback)(const InfraredBatch* batch, const InfraredBatchTarget* target); InfraredRemote* infrared_remote_alloc() { InfraredRemote* remote = malloc(sizeof(InfraredRemote)); - InfraredButtonArray_init(remote->buttons); + StringArray_init(remote->signal_names); remote->name = furi_string_alloc(); remote->path = furi_string_alloc(); return remote; } void infrared_remote_free(InfraredRemote* remote) { - infrared_remote_clear_buttons(remote); - InfraredButtonArray_clear(remote->buttons); + StringArray_clear(remote->signal_names); furi_string_free(remote->path); furi_string_free(remote->name); free(remote); } void infrared_remote_reset(InfraredRemote* remote) { - infrared_remote_clear_buttons(remote); + StringArray_reset(remote->signal_names); furi_string_reset(remote->name); furi_string_reset(remote->path); } -void infrared_remote_set_name(InfraredRemote* remote, const char* name) { - furi_string_set(remote->name, name); -} - -const char* infrared_remote_get_name(InfraredRemote* remote) { +const char* infrared_remote_get_name(const InfraredRemote* remote) { return furi_string_get_cstr(remote->name); } -void infrared_remote_set_path(InfraredRemote* remote, const char* path) { +static void infrared_remote_set_path(InfraredRemote* remote, const char* path) { furi_string_set(remote->path, path); + path_extract_filename(remote->path, remote->name, true); } -const char* infrared_remote_get_path(InfraredRemote* remote) { +const char* infrared_remote_get_path(const InfraredRemote* remote) { return furi_string_get_cstr(remote->path); } -size_t infrared_remote_get_button_count(InfraredRemote* remote) { - return InfraredButtonArray_size(remote->buttons); +size_t infrared_remote_get_signal_count(const InfraredRemote* remote) { + return StringArray_size(remote->signal_names); +} + +const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + return *StringArray_cget(remote->signal_names, index); } -InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) { - furi_assert(index < InfraredButtonArray_size(remote->buttons)); - return *InfraredButtonArray_get(remote->buttons, index); +bool infrared_remote_load_signal( + const InfraredRemote* remote, + InfraredSignal* signal, + size_t index) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + bool success = false; + + do { + const char* path = furi_string_get_cstr(remote->path); + if(!flipper_format_buffered_file_open_existing(ff, path)) break; + + const char* name = infrared_remote_get_signal_name(remote, index); + + if(!infrared_signal_search_and_read(signal, ff, name)) { + FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", name, path); + break; + } + + success = true; + } while(false); + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + return success; } -bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) { - for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) { - InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i); - if(!strcmp(infrared_remote_button_get_name(button), name)) { +bool infrared_remote_get_signal_index( + const InfraredRemote* remote, + const char* name, + size_t* index) { + uint32_t i = 0; + StringArray_it_t it; + + for(StringArray_it(it, remote->signal_names); !StringArray_end_p(it); + StringArray_next(it), ++i) { + if(strcmp(*StringArray_cref(it), name) == 0) { *index = i; return true; } } + return false; } -bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) { - InfraredRemoteButton* button = infrared_remote_button_alloc(); - infrared_remote_button_set_name(button, name); - infrared_remote_button_set_signal(button, signal); - InfraredButtonArray_push_back(remote->buttons, button); - return infrared_remote_store(remote); +bool infrared_remote_append_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + bool success = false; + const char* path = furi_string_get_cstr(remote->path); + + do { + if(!flipper_format_file_open_append(ff, path)) break; + if(!infrared_signal_save(signal, ff, name)) break; + + StringArray_push_back(remote->signal_names, name); + success = true; + } while(false); + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + return success; +} + +static bool infrared_remote_batch_start( + InfraredRemote* remote, + InfraredBatchCallback batch_callback, + const InfraredBatchTarget* target) { + FuriString* tmp = furi_string_alloc(); + Storage* storage = furi_record_open(RECORD_STORAGE); + + InfraredBatch batch_context = { + .remote = remote, + .ff_in = flipper_format_buffered_file_alloc(storage), + .ff_out = flipper_format_buffered_file_alloc(storage), + .signal_name = furi_string_alloc(), + .signal = infrared_signal_alloc(), + .signal_index = 0, + }; + + const char* path_in = furi_string_get_cstr(remote->path); + const char* path_out; + + FS_Error status; + + do { + furi_string_printf(tmp, "%s.temp%08x.swp", path_in, rand()); + path_out = furi_string_get_cstr(tmp); + status = storage_common_stat(storage, path_out, NULL); + } while(status == FSE_OK || status == FSE_EXIST); + + bool success = false; + + do { + if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in)) break; + if(!flipper_format_buffered_file_open_always(batch_context.ff_out, path_out)) break; + if(!flipper_format_write_header_cstr( + batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) + break; + + const size_t signal_count = infrared_remote_get_signal_count(remote); + + for(; batch_context.signal_index < signal_count; ++batch_context.signal_index) { + if(!infrared_signal_read( + batch_context.signal, batch_context.ff_in, batch_context.signal_name)) + break; + if(!batch_callback(&batch_context, target)) break; + } + + if(batch_context.signal_index != signal_count) break; + + if(!flipper_format_buffered_file_close(batch_context.ff_out)) break; + if(!flipper_format_buffered_file_close(batch_context.ff_in)) break; + + const FS_Error status = storage_common_rename(storage, path_out, path_in); + success = (status == FSE_OK || status == FSE_EXIST); + } while(false); + + infrared_signal_free(batch_context.signal); + furi_string_free(batch_context.signal_name); + flipper_format_free(batch_context.ff_out); + flipper_format_free(batch_context.ff_in); + furi_string_free(tmp); + + furi_record_close(RECORD_STORAGE); + + return success; +} + +static bool infrared_remote_insert_signal_callback( + const InfraredBatch* batch, + const InfraredBatchTarget* target) { + // Insert a signal under the specified index + if(batch->signal_index == target->signal_index) { + if(!infrared_signal_save(target->signal, batch->ff_out, target->signal_name)) return false; + StringArray_push_at( + batch->remote->signal_names, target->signal_index, target->signal_name); + } + + // Write the rest normally + return infrared_signal_save( + batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); +} + +bool infrared_remote_insert_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name, + size_t index) { + if(index >= infrared_remote_get_signal_count(remote)) { + return infrared_remote_append_signal(remote, signal, name); + } + + const InfraredBatchTarget insert_target = { + .signal_index = index, + .signal_name = name, + .signal = signal, + }; + + return infrared_remote_batch_start( + remote, infrared_remote_insert_signal_callback, &insert_target); +} + +static bool infrared_remote_rename_signal_callback( + const InfraredBatch* batch, + const InfraredBatchTarget* target) { + const char* signal_name; + + if(batch->signal_index == target->signal_index) { + // Rename the signal at requested index + signal_name = target->signal_name; + StringArray_set_at(batch->remote->signal_names, batch->signal_index, signal_name); + } else { + // Use the original name otherwise + signal_name = furi_string_get_cstr(batch->signal_name); + } + + return infrared_signal_save(batch->signal, batch->ff_out, signal_name); } -bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) { - furi_assert(index < InfraredButtonArray_size(remote->buttons)); - InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index); - infrared_remote_button_set_name(button, new_name); - return infrared_remote_store(remote); +bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + + const InfraredBatchTarget rename_target = { + .signal_index = index, + .signal_name = new_name, + .signal = NULL, + }; + + return infrared_remote_batch_start( + remote, infrared_remote_rename_signal_callback, &rename_target); +} + +static bool infrared_remote_delete_signal_callback( + const InfraredBatch* batch, + const InfraredBatchTarget* target) { + if(batch->signal_index == target->signal_index) { + // Do not save the signal to be deleted, remove it from the signal name list instead + StringArray_remove_v( + batch->remote->signal_names, batch->signal_index, batch->signal_index + 1); + } else { + // Pass other signals through + return infrared_signal_save( + batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); + } + + return true; } -bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) { - furi_assert(index < InfraredButtonArray_size(remote->buttons)); - InfraredRemoteButton* button; - InfraredButtonArray_pop_at(&button, remote->buttons, index); - infrared_remote_button_free(button); - return infrared_remote_store(remote); +bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + + const InfraredBatchTarget delete_target = { + .signal_index = index, + .signal_name = NULL, + .signal = NULL, + }; + + return infrared_remote_batch_start( + remote, infrared_remote_delete_signal_callback, &delete_target); } -void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) { - furi_assert(index_orig < InfraredButtonArray_size(remote->buttons)); - furi_assert(index_dest < InfraredButtonArray_size(remote->buttons)); +bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) { + const size_t signal_count = infrared_remote_get_signal_count(remote); + furi_assert(index < signal_count); + furi_assert(new_index < signal_count); + + if(index == new_index) return true; + + InfraredSignal* signal = infrared_signal_alloc(); + char* signal_name = strdup(infrared_remote_get_signal_name(remote, index)); + + bool success = false; + + do { + if(!infrared_remote_load_signal(remote, signal, index)) break; + if(!infrared_remote_delete_signal(remote, index)) break; + if(!infrared_remote_insert_signal(remote, signal, signal_name, new_index)) break; + + success = true; + } while(false); + + free(signal_name); + infrared_signal_free(signal); - InfraredRemoteButton* button; - InfraredButtonArray_pop_at(&button, remote->buttons, index_orig); - InfraredButtonArray_push_at(remote->buttons, index_dest, button); + return success; } -bool infrared_remote_store(InfraredRemote* remote) { +bool infrared_remote_create(InfraredRemote* remote, const char* path) { + FURI_LOG_I(TAG, "Creating new file: '%s'", path); + + infrared_remote_reset(remote); + infrared_remote_set_path(remote, path); + Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_file_alloc(storage); - const char* path = furi_string_get_cstr(remote->path); - FURI_LOG_I(TAG, "store file: \'%s\'", path); + bool success = false; - bool success = flipper_format_file_open_always(ff, path) && - flipper_format_write_header_cstr(ff, "IR signals file", 1); - if(success) { - InfraredButtonArray_it_t it; - for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); - InfraredButtonArray_next(it)) { - InfraredRemoteButton* button = *InfraredButtonArray_cref(it); - success = infrared_signal_save( - infrared_remote_button_get_signal(button), - ff, - infrared_remote_button_get_name(button)); - if(!success) { - break; - } - } - } + do { + if(!flipper_format_file_open_always(ff, path)) break; + if(!flipper_format_write_header_cstr(ff, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) + break; + + success = true; + } while(false); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); + return success; } -bool infrared_remote_load(InfraredRemote* remote, FuriString* path) { +bool infrared_remote_load(InfraredRemote* remote, const char* path) { + FURI_LOG_I(TAG, "Loading file: '%s'", path); + Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - FuriString* buf; - buf = furi_string_alloc(); - - FURI_LOG_I(TAG, "load file: \'%s\'", furi_string_get_cstr(path)); + FuriString* tmp = furi_string_alloc(); bool success = false; do { - if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break; + if(!flipper_format_buffered_file_open_existing(ff, path)) break; + uint32_t version; - if(!flipper_format_read_header(ff, buf, &version)) break; - if(!furi_string_equal(buf, "IR signals file") || (version != 1)) break; - - path_extract_filename(path, buf, true); - infrared_remote_clear_buttons(remote); - infrared_remote_set_name(remote, furi_string_get_cstr(buf)); - infrared_remote_set_path(remote, furi_string_get_cstr(path)); - - for(bool can_read = true; can_read;) { - InfraredRemoteButton* button = infrared_remote_button_alloc(); - can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf); - if(can_read) { - infrared_remote_button_set_name(button, furi_string_get_cstr(buf)); - InfraredButtonArray_push_back(remote->buttons, button); - } else { - infrared_remote_button_free(button); - } + if(!flipper_format_read_header(ff, tmp, &version)) break; + + if(!furi_string_equal(tmp, INFRARED_FILE_HEADER) || (version != INFRARED_FILE_VERSION)) + break; + + infrared_remote_set_path(remote, path); + StringArray_reset(remote->signal_names); + + while(infrared_signal_read_name(ff, tmp)) { + StringArray_push_back(remote->signal_names, furi_string_get_cstr(tmp)); } + success = true; } while(false); - furi_string_free(buf); + furi_string_free(tmp); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); + return success; } -bool infrared_remote_remove(InfraredRemote* remote) { +bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) { + const char* old_path = infrared_remote_get_path(remote); + Storage* storage = furi_record_open(RECORD_STORAGE); + const FS_Error status = storage_common_rename(storage, old_path, new_path); + furi_record_close(RECORD_STORAGE); - FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path)); - infrared_remote_reset(remote); + const bool success = (status == FSE_OK || status == FSE_EXIST); + + if(success) { + infrared_remote_set_path(remote, new_path); + } + + return success; +} +bool infrared_remote_remove(InfraredRemote* remote) { + Storage* storage = furi_record_open(RECORD_STORAGE); + const FS_Error status = storage_common_remove(storage, infrared_remote_get_path(remote)); furi_record_close(RECORD_STORAGE); - return (status == FSE_OK || status == FSE_NOT_EXIST); + + const bool success = (status == FSE_OK || status == FSE_NOT_EXIST); + + if(success) { + infrared_remote_reset(remote); + } + + return success; } diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 47aa77e2ef..7477cd3b5a 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -1,30 +1,229 @@ +/** + * @file infrared_remote.h + * @brief Infrared remote library. + * + * An infrared remote contains zero or more infrared signals which + * have a (possibly non-unique) name each. + * + * The current implementation does load only the names into the memory, + * while the signals themselves are loaded on-demand one by one. In theory, + * this should allow for quite large remotes with relatively bulky signals. + */ #pragma once -#include - -#include "infrared_remote_button.h" +#include "infrared_signal.h" +/** + * @brief InfraredRemote opaque type declaration. + */ typedef struct InfraredRemote InfraredRemote; +/** + * @brief Create a new InfraredRemote instance. + * + * @returns pointer to the created instance. + */ InfraredRemote* infrared_remote_alloc(); + +/** + * @brief Delete an InfraredRemote instance. + * + * @param[in,out] remote pointer to the instance to be deleted. + */ void infrared_remote_free(InfraredRemote* remote); + +/** + * @brief Reset an InfraredRemote instance. + * + * Resetting a remote clears its signal name list and + * the associated file path. + * + * @param[in,out] remote pointer to the instance to be deleted. + */ void infrared_remote_reset(InfraredRemote* remote); -void infrared_remote_set_name(InfraredRemote* remote, const char* name); -const char* infrared_remote_get_name(InfraredRemote* remote); +/** + * @brief Get an InfraredRemote instance's name. + * + * The name is deduced from the file path. + * + * The return value remains valid unless one of the following functions is called: + * - infrared_remote_reset() + * - infrared_remote_load() + * - infrared_remote_create() + * + * @param[in] remote pointer to the instance to be queried. + * @returns pointer to a zero-terminated string containing the name. + */ +const char* infrared_remote_get_name(const InfraredRemote* remote); + +/** + * @brief Get an InfraredRemote instance's file path. + * + * Same return value validity considerations as infrared_remote_get_name(). + * + * @param[in] remote pointer to the instance to be queried. + * @returns pointer to a zero-terminated string containing the path. + */ +const char* infrared_remote_get_path(const InfraredRemote* remote); + +/** + * @brief Get the number of signals listed in an InfraredRemote instance. + * + * @param[in] remote pointer to the instance to be queried. + * @returns number of signals, zero or more + */ +size_t infrared_remote_get_signal_count(const InfraredRemote* remote); + +/** + * @brief Get the name of a signal listed in an InfraredRemote instance. + * + * @param[in] remote pointer to the instance to be queried. + * @param[in] index index of the signal in question. Must be less than the total signal count. + */ +const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index); + +/** + * @brief Get the index of a signal listed in an InfraredRemote instance by its name. + * + * @param[in] remote pointer to the instance to be queried. + * @param[in] name pointer to a zero-terminated string containig the name of the signal in question. + * @param[out] index pointer to the variable to hold the signal index. + * @returns true if a signal with the given name was found, false otherwise. + */ +bool infrared_remote_get_signal_index( + const InfraredRemote* remote, + const char* name, + size_t* index); + +/** + * @brief Load a signal listed in an InfraredRemote instance. + * + * As mentioned above, the signals are loaded on-demand. The user code must call this function + * each time it wants to interact with a new signal. + * + * @param[in] remote pointer to the instance to load from. + * @param[out] signal pointer to the signal to load into. Must be allocated. + * @param[in] index index of the signal to be loaded. Must be less than the total signal count. + * @return true if the signal was successfully loaded, false otherwise. + */ +bool infrared_remote_load_signal( + const InfraredRemote* remote, + InfraredSignal* signal, + size_t index); + +/** + * @brief Append a signal to the file associated with an InfraredRemote instance. + * + * The file path must be somehow initialised first by calling either infrared_remote_load() or + * infrared_remote_create(). As the name suggests, the signal will be put in the end of the file. + * + * @param[in,out] remote pointer to the instance to append to. + * @param[in] signal pointer to the signal to be appended. + * @param[in] name pointer to a zero-terminated string containing the name of the signal. + * @returns true if the signal was successfully appended, false otherwise. + */ +bool infrared_remote_append_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name); + +/** + * @brief Insert a signal to the file associated with an InfraredRemote instance. + * + * Same behaviour as infrared_remote_append_signal(), but the user code can decide where to + * put the signal in the file. + * + * Index values equal to or greater than the total signal count will result in behaviour + * identical to infrared_remote_append_signal(). + * + * @param[in,out] remote pointer to the instance to insert to. + * @param[in] signal pointer to the signal to be inserted. + * @param[in] name pointer to a zero-terminated string containing the name of the signal. + * @param[in] index the index under which the signal shall be inserted. + * @returns true if the signal was successfully inserted, false otherwise. + */ +bool infrared_remote_insert_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name, + size_t index); + +/** + * @brief Rename a signal in the file associated with an InfraredRemote instance. + * + * Only changes the signal's name, but neither its position nor contents. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] index index of the signal to be renamed. Must be less than the total signal count. + * @param[in] new_name pointer to a zero-terminated string containig the signal's new name. + * @returns true if the signal was successfully renamed, false otherwise. + */ +bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name); + +/** + * @brief Change a signal's position in the file associated with an InfraredRemote instance. + * + * Only changes the signal's position (index), but neither its name nor contents. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] index index of the signal to be moved. Must be less than the total signal count. + * @param[in] new_index index of the signal to be moved. Must be less than the total signal count. + */ +bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index); + +/** + * @brief Delete a signal in the file associated with an InfraredRemote instance. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] index index of the signal to be deleted. Must be less than the total signal count. + * @returns true if the signal was successfully deleted, false otherwise. + */ +bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index); -void infrared_remote_set_path(InfraredRemote* remote, const char* path); -const char* infrared_remote_get_path(InfraredRemote* remote); +/** + * @brief Create a new file and associate it with an InfraredRemote instance. + * + * The instance will be reset and given a new empty file with just the header. + * + * @param[in,out] remote pointer to the instance to be assigned with a new file. + * @param[in] path pointer to a zero-terminated string containing the full file path. + * @returns true if the file was successfully created, false otherwise. + */ +bool infrared_remote_create(InfraredRemote* remote, const char* path); -size_t infrared_remote_get_button_count(InfraredRemote* remote); -InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index); -bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index); +/** + * @brief Associate an InfraredRemote instance with a file and load the signal names from it. + * + * The instance will be reset and fill its signal name list from the given file. + * The file must already exist and be valid. + * + * @param[in,out] remote pointer to the instance to be assigned with an existing file. + * @param[in] path pointer to a zero-terminated string containing the full file path. + * @returns true if the file was successfully loaded, false otherwise. + */ +bool infrared_remote_load(InfraredRemote* remote, const char* path); -bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal); -bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index); -bool infrared_remote_delete_button(InfraredRemote* remote, size_t index); -void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest); +/** + * @brief Rename the file associated with an InfraredRemote instance. + * + * Only renames the file, no signals are added, moved or deleted. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] new_path pointer to a zero-terminated string containing the new full file path. + * @returns true if the file was successfully renamed, false otherwise. + */ +bool infrared_remote_rename(InfraredRemote* remote, const char* new_path); -bool infrared_remote_store(InfraredRemote* remote); -bool infrared_remote_load(InfraredRemote* remote, FuriString* path); +/** + * @brief Remove the file associated with an InfraredRemote instance. + * + * This operation is irreversible and fully deletes the remote file + * from the underlying filesystem. + * After calling this function, the instance becomes invalid until + * infrared_remote_create() or infrared_remote_load() are successfully executed. + * + * @param[in,out] remote pointer to the instance to be modified. + * @returns true if the file was successfully removed, false otherwise. + */ bool infrared_remote_remove(InfraredRemote* remote); diff --git a/applications/main/infrared/infrared_remote_button.c b/applications/main/infrared/infrared_remote_button.c deleted file mode 100644 index 1f6315ec52..0000000000 --- a/applications/main/infrared/infrared_remote_button.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "infrared_remote_button.h" - -#include - -struct InfraredRemoteButton { - FuriString* name; - InfraredSignal* signal; -}; - -InfraredRemoteButton* infrared_remote_button_alloc() { - InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton)); - button->name = furi_string_alloc(); - button->signal = infrared_signal_alloc(); - return button; -} - -void infrared_remote_button_free(InfraredRemoteButton* button) { - furi_string_free(button->name); - infrared_signal_free(button->signal); - free(button); -} - -void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) { - furi_string_set(button->name, name); -} - -const char* infrared_remote_button_get_name(InfraredRemoteButton* button) { - return furi_string_get_cstr(button->name); -} - -void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) { - infrared_signal_set_signal(button->signal, signal); -} - -InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) { - return button->signal; -} diff --git a/applications/main/infrared/infrared_remote_button.h b/applications/main/infrared/infrared_remote_button.h deleted file mode 100644 index f25b759b56..0000000000 --- a/applications/main/infrared/infrared_remote_button.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "infrared_signal.h" - -typedef struct InfraredRemoteButton InfraredRemoteButton; - -InfraredRemoteButton* infrared_remote_button_alloc(); -void infrared_remote_button_free(InfraredRemoteButton* button); - -void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name); -const char* infrared_remote_button_get_name(InfraredRemoteButton* button); - -void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal); -InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button); diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index 9154dfbf68..c73e4db98d 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -8,6 +8,8 @@ #define TAG "InfraredSignal" +#define INFRARED_SIGNAL_NAME_KEY "name" + struct InfraredSignal { bool is_raw; union { @@ -24,7 +26,7 @@ static void infrared_signal_clear_timings(InfraredSignal* signal) { } } -static bool infrared_signal_is_message_valid(InfraredMessage* message) { +static bool infrared_signal_is_message_valid(const InfraredMessage* message) { if(!infrared_is_protocol_valid(message->protocol)) { FURI_LOG_E(TAG, "Unknown protocol"); return false; @@ -57,7 +59,7 @@ static bool infrared_signal_is_message_valid(InfraredMessage* message) { return true; } -static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { +static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) { FURI_LOG_E( TAG, @@ -83,7 +85,8 @@ static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { return true; } -static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) { +static inline bool + infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) { const char* protocol_name = infrared_get_protocol_name(message->protocol); return flipper_format_write_string_cstr(ff, "type", "parsed") && flipper_format_write_string_cstr(ff, "protocol", protocol_name) && @@ -91,7 +94,7 @@ static inline bool infrared_signal_save_message(InfraredMessage* message, Flippe flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4); } -static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) { +static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); return flipper_format_write_string_cstr(ff, "type", "raw") && flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) && @@ -180,11 +183,11 @@ void infrared_signal_free(InfraredSignal* signal) { free(signal); } -bool infrared_signal_is_raw(InfraredSignal* signal) { +bool infrared_signal_is_raw(const InfraredSignal* signal) { return signal->is_raw; } -bool infrared_signal_is_valid(InfraredSignal* signal) { +bool infrared_signal_is_valid(const InfraredSignal* signal) { return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) : infrared_signal_is_message_valid(&signal->payload.message); } @@ -218,7 +221,7 @@ void infrared_signal_set_raw_signal( memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t)); } -InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) { +const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal) { furi_assert(signal->is_raw); return &signal->payload.raw; } @@ -230,14 +233,14 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* signal->payload.message = *message; } -InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) { +const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) { furi_assert(!signal->is_raw); return &signal->payload.message; } -bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) { +bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) { if(!flipper_format_write_comment_cstr(ff, "") || - !flipper_format_write_string_cstr(ff, "name", name)) { + !flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) { return false; } else if(signal->is_raw) { return infrared_signal_save_raw(&signal->payload.raw, ff); @@ -247,33 +250,30 @@ bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* } bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { - FuriString* tmp = furi_string_alloc(); - bool success = false; do { - if(!flipper_format_read_string(ff, "name", tmp)) break; - furi_string_set(name, tmp); + if(!infrared_signal_read_name(ff, name)) break; if(!infrared_signal_read_body(signal, ff)) break; - success = true; - } while(0); - furi_string_free(tmp); + success = true; //-V779 + } while(false); + return success; } -bool infrared_signal_search_and_read( - InfraredSignal* signal, - FlipperFormat* ff, - const FuriString* name) { +bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { + return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name); +} + +bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name) { bool success = false; FuriString* tmp = furi_string_alloc(); do { bool is_name_found = false; - while(flipper_format_read_string(ff, "name", tmp)) { - is_name_found = furi_string_equal(name, tmp); - if(is_name_found) break; + while(!is_name_found && infrared_signal_read_name(ff, tmp)) { //-V560 + is_name_found = furi_string_equal(tmp, name); } if(!is_name_found) break; //-V547 if(!infrared_signal_read_body(signal, ff)) break; //-V779 @@ -284,9 +284,9 @@ bool infrared_signal_search_and_read( return success; } -void infrared_signal_transmit(InfraredSignal* signal) { +void infrared_signal_transmit(const InfraredSignal* signal) { if(signal->is_raw) { - InfraredRawSignal* raw_signal = &signal->payload.raw; + const InfraredRawSignal* raw_signal = &signal->payload.raw; infrared_send_raw_ext( raw_signal->timings, raw_signal->timings_size, @@ -294,7 +294,7 @@ void infrared_signal_transmit(InfraredSignal* signal) { raw_signal->frequency, raw_signal->duty_cycle); } else { - InfraredMessage* message = &signal->payload.message; + const InfraredMessage* message = &signal->payload.message; infrared_send(message, 1); } } diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index 29c6619383..fcbb3c8739 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -1,45 +1,186 @@ +/** + * @file infrared_signal.h + * @brief Infrared signal library. + * + * Infrared signals may be of two types: + * - known to the infrared signal decoder, or *parsed* signals + * - the rest, or *raw* signals, which are treated merely as a set of timings. + */ #pragma once -#include -#include -#include - -#include #include +#include +/** + * @brief InfraredSignal opaque type declaration. + */ typedef struct InfraredSignal InfraredSignal; +/** + * @brief Raw signal type definition. + * + * Measurement units used: + * - time: microseconds (uS) + * - frequency: Hertz (Hz) + * - duty_cycle: no units, fraction between 0 and 1. + */ typedef struct { - size_t timings_size; - uint32_t* timings; - uint32_t frequency; - float duty_cycle; + size_t timings_size; /**< Number of elements in the timings array. */ + uint32_t* timings; /**< Pointer to an array of timings describing the signal. */ + uint32_t frequency; /**< Carrier frequency of the signal. */ + float duty_cycle; /**< Duty cycle of the signal. */ } InfraredRawSignal; +/** + * @brief Create a new InfraredSignal instance. + * + * @returns pointer to the instance created. + */ InfraredSignal* infrared_signal_alloc(); + +/** + * @brief Delete an InfraredSignal instance. + * + * @param[in,out] signal pointer to the instance to be deleted. + */ void infrared_signal_free(InfraredSignal* signal); -bool infrared_signal_is_raw(InfraredSignal* signal); -bool infrared_signal_is_valid(InfraredSignal* signal); +/** + * @brief Test whether an InfraredSignal instance holds a raw signal. + * + * @param[in] signal pointer to the instance to be tested. + * @returns true if the instance holds a raw signal, false otherwise. + */ +bool infrared_signal_is_raw(const InfraredSignal* signal); + +/** + * @brief Test whether an InfraredSignal instance holds any signal. + * + * @param[in] signal pointer to the instance to be tested. + * @returns true if the instance holds raw signal, false otherwise. + */ +bool infrared_signal_is_valid(const InfraredSignal* signal); +/** + * @brief Set an InfraredInstance to hold the signal from another one. + * + * Any instance's previous contents will be automatically deleted before + * copying the source instance's contents. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] other pointer to the source instance. + */ void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other); +/** + * @brief Set an InfraredInstance to hold a raw signal. + * + * Any instance's previous contents will be automatically deleted before + * copying the raw signal. + * + * After this call, infrared_signal_is_raw() will return true. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] timings pointer to an array containing the raw signal timings. + * @param[in] timings_size number of elements in the timings array. + * @param[in] frequency signal carrier frequency, in Hertz. + * @param[in] duty_cycle signal duty cycle, fraction between 0 and 1. + */ void infrared_signal_set_raw_signal( InfraredSignal* signal, const uint32_t* timings, size_t timings_size, uint32_t frequency, float duty_cycle); -InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal); +/** + * @brief Get the raw signal held by an InfraredSignal instance. + * + * @warning the instance MUST hold a *raw* signal, otherwise undefined behaviour will occur. + * + * @param[in] signal pointer to the instance to be queried. + * @returns pointer to the raw signal structure held by the instance. + */ +const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal); + +/** + * @brief Set an InfraredInstance to hold a parsed signal. + * + * Any instance's previous contents will be automatically deleted before + * copying the raw signal. + * + * After this call, infrared_signal_is_raw() will return false. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] message pointer to the message containing the parsed signal. + */ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message); -InfraredMessage* infrared_signal_get_message(InfraredSignal* signal); -bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name); +/** + * @brief Get the parsed signal held by an InfraredSignal instance. + * + * @warning the instance MUST hold a *parsed* signal, otherwise undefined behaviour will occur. + * + * @param[in] signal pointer to the instance to be queried. + * @returns pointer to the parsed signal structure held by the instance. + */ +const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal); + +/** + * @brief Read a signal from a FlipperFormat file into an InfraredSignal instance. + * + * The file must be allocated and open prior to this call. The seek position determines + * which signal will be read (if there is more than one in the file). Calling this function + * repeatedly will result in all signals in the file to be read until no more are left. + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. + * @returns true if a signal was successfully read, false otherwise (e.g. no more signals to read). + */ bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); -bool infrared_signal_search_and_read( - InfraredSignal* signal, - FlipperFormat* ff, - const FuriString* name); -void infrared_signal_transmit(InfraredSignal* signal); +/** + * @brief Read a signal name from a FlipperFormat file. + * + * Same behaviour as infrared_signal_read(), but only the name is read. + * + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. + * @returns true if a signal name was successfully read, false otherwise (e.g. no more signals to read). + */ +bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); + +/** + * @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance. + * + * This function will look for a signal with the given name and if found, attempt to read it. + * Same considerations apply as to infrared_signal_read(). + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[in] name pointer to a zero-terminated string containing the requested signal name. + * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + */ +bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name); + +/** + * @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file. + * + * The file must be allocated and open prior to this call. Additionally, an appropriate header + * must be already written into the file. + * + * @param[in] signal pointer to the instance holding the signal to be saved. + * @param[in,out] ff pointer to the FlipperFormat file instance to write to. + * @param[in] name pointer to a zero-terminated string contating the name of the signal. + */ +bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name); + +/** + * @brief Transmit a signal contained in an InfraredSignal instance. + * + * The transmission happens once per call using the built-in hardware (via HAL calls). + * + * @param[in] signal pointer to the instance holding the signal to be transmitted. + */ +void infrared_signal_transmit(const InfraredSignal* signal); diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 96f28cc489..4967d19566 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -1,20 +1,21 @@ -#include "../../infrared_i.h" +#include "../../infrared_app_i.h" #include void infrared_scene_universal_common_item_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); view_dispatcher_send_custom_event(infrared->view_dispatcher, event); } static void infrared_scene_universal_common_progress_back_callback(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1); view_dispatcher_send_custom_event(infrared->view_dispatcher, event); } -static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint32_t record_count) { +static void + infrared_scene_universal_common_show_popup(InfraredApp* infrared, uint32_t record_count) { ViewStack* view_stack = infrared->view_stack; InfraredProgressView* progress = infrared->progress; infrared_progress_view_set_progress_total(progress, record_count); @@ -24,7 +25,7 @@ static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint3 infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); } -static void infrared_scene_universal_common_hide_popup(Infrared* infrared) { +static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { ViewStack* view_stack = infrared->view_stack; InfraredProgressView* progress = infrared->progress; view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress)); @@ -32,12 +33,12 @@ static void infrared_scene_universal_common_hide_popup(Infrared* infrared) { } void infrared_scene_universal_common_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); } bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; InfraredBruteForce* brute_force = infrared->brute_force; bool consumed = false; @@ -84,7 +85,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e } void infrared_scene_universal_common_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel)); infrared_brute_force_reset(infrared->brute_force); diff --git a/applications/main/infrared/scenes/infrared_scene_ask_back.c b/applications/main/infrared/scenes/infrared_scene_ask_back.c index 77fc97f987..f97a38bb09 100644 --- a/applications/main/infrared/scenes/infrared_scene_ask_back.c +++ b/applications/main/infrared/scenes/infrared_scene_ask_back.c @@ -1,12 +1,12 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_ask_back_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; if(infrared->app_state.is_learning_new_remote) { @@ -28,7 +28,7 @@ void infrared_scene_ask_back_on_enter(void* context) { } bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -54,6 +54,6 @@ bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_ask_back_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; dialog_ex_reset(infrared->dialog_ex); } diff --git a/applications/main/infrared/scenes/infrared_scene_ask_retry.c b/applications/main/infrared/scenes/infrared_scene_ask_retry.c index 602e470c7c..365ed5c812 100644 --- a/applications/main/infrared/scenes/infrared_scene_ask_retry.c +++ b/applications/main/infrared/scenes/infrared_scene_ask_retry.c @@ -1,12 +1,12 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_ask_retry_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop); @@ -23,7 +23,7 @@ void infrared_scene_ask_retry_on_enter(void* context) { } bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -43,6 +43,6 @@ bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_ask_retry_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; dialog_ex_reset(infrared->dialog_ex); } diff --git a/applications/main/infrared/scenes/infrared_scene_debug.c b/applications/main/infrared/scenes/infrared_scene_debug.c index 204978697b..adffbc83ac 100644 --- a/applications/main/infrared/scenes/infrared_scene_debug.c +++ b/applications/main/infrared/scenes/infrared_scene_debug.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_debug_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredWorker* worker = infrared->worker; infrared_worker_rx_set_received_signal_callback( @@ -14,16 +14,16 @@ void infrared_scene_debug_on_enter(void* context) { } bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeSignalReceived) { InfraredDebugView* debug_view = infrared->debug_view; - InfraredSignal* signal = infrared->received_signal; + InfraredSignal* signal = infrared->current_signal; if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); infrared_debug_view_set_text(debug_view, "RAW\n%d samples\n", raw->timings_size); printf("RAW, %zu samples:\r\n", raw->timings_size); @@ -33,7 +33,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) { printf("\r\n"); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(signal); infrared_debug_view_set_text( debug_view, "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n", @@ -61,7 +61,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_debug_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredWorker* worker = infrared->worker; infrared_worker_rx_stop(worker); infrared_worker_rx_enable_blink_on_receiving(worker, false); diff --git a/applications/main/infrared/scenes/infrared_scene_edit.c b/applications/main/infrared/scenes/infrared_scene_edit.c index 02bba7a3f4..c22e953963 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit.c +++ b/applications/main/infrared/scenes/infrared_scene_edit.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" typedef enum { SubmenuIndexAddButton, @@ -10,12 +10,12 @@ typedef enum { } SubmenuIndex; static void infrared_scene_edit_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_edit_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; SceneManager* scene_manager = infrared->scene_manager; @@ -64,7 +64,7 @@ void infrared_scene_edit_on_enter(void* context) { } bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -106,6 +106,6 @@ bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_edit_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c index 5f5a1d8fac..a76b4e836b 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c @@ -1,12 +1,12 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_edit_button_select_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_edit_button_select_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; InfraredRemote* remote = infrared->remote; InfraredAppState* app_state = &infrared->app_state; @@ -16,16 +16,16 @@ void infrared_scene_edit_button_select_on_enter(void* context) { "Delete Button:"; submenu_set_header(submenu, header); - const size_t button_count = infrared_remote_get_button_count(remote); + const size_t button_count = infrared_remote_get_signal_count(remote); for(size_t i = 0; i < button_count; ++i) { - InfraredRemoteButton* button = infrared_remote_get_button(remote, i); submenu_add_item( submenu, - infrared_remote_button_get_name(button), + infrared_remote_get_signal_name(remote, i), i, infrared_scene_edit_button_select_submenu_callback, context); } + if(button_count && app_state->current_button_index != InfraredButtonIndexNone) { submenu_set_selected_item(submenu, app_state->current_button_index); app_state->current_button_index = InfraredButtonIndexNone; @@ -35,7 +35,7 @@ void infrared_scene_edit_button_select_on_enter(void* context) { } bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredAppState* app_state = &infrared->app_state; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -57,6 +57,6 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent } void infrared_scene_edit_button_select_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 4dfc054fbd..c1735da082 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -1,42 +1,49 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_edit_delete_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_edit_delete_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; InfraredRemote* remote = infrared->remote; const InfraredEditTarget edit_target = infrared->app_state.edit_target; if(edit_target == InfraredEditTargetButton) { - int32_t current_button_index = infrared->app_state.current_button_index; - furi_assert(current_button_index != InfraredButtonIndexNone); - dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop); - InfraredRemoteButton* current_button = - infrared_remote_get_button(remote, current_button_index); - InfraredSignal* signal = infrared_remote_button_get_signal(current_button); - if(infrared_signal_is_raw(signal)) { - const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + const int32_t current_button_index = infrared->app_state.current_button_index; + furi_check(current_button_index != InfraredButtonIndexNone); + + if(!infrared_remote_load_signal(remote, infrared->current_signal, current_button_index)) { + infrared_show_error_message( + infrared, + "Failed to load\n\"%s\"", + infrared_remote_get_signal_name(remote, current_button_index)); + scene_manager_previous_scene(infrared->scene_manager); + return; + } + + if(infrared_signal_is_raw(infrared->current_signal)) { + const InfraredRawSignal* raw = + infrared_signal_get_raw_signal(infrared->current_signal); infrared_text_store_set( infrared, 0, - "%s\nRAW\n%ld samples", - infrared_remote_button_get_name(current_button), + "%s\nRAW\n%zu samples", + infrared_remote_get_signal_name(remote, current_button_index), raw->timings_size); } else { - const InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal); infrared_text_store_set( infrared, 0, "%s\n%s\nA=0x%0*lX C=0x%0*lX", - infrared_remote_button_get_name(current_button), + infrared_remote_get_signal_name(remote, current_button_index), infrared_get_protocol_name(message->protocol), ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), message->address, @@ -49,9 +56,9 @@ void infrared_scene_edit_delete_on_enter(void* context) { infrared_text_store_set( infrared, 0, - "%s\n with %lu buttons", + "%s\n with %zu buttons", infrared_remote_get_name(remote), - infrared_remote_get_button_count(remote)); + infrared_remote_get_signal_count(remote)); } else { furi_assert(0); } @@ -63,11 +70,14 @@ void infrared_scene_edit_delete_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback); dialog_ex_set_context(dialog_ex, context); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); + view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); + + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -83,18 +93,24 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) if(edit_target == InfraredEditTargetButton) { furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - success = infrared_remote_delete_button(remote, app_state->current_button_index); + infrared_show_loading_popup(infrared, true); + success = infrared_remote_delete_signal(remote, app_state->current_button_index); + infrared_show_loading_popup(infrared, false); app_state->current_button_index = InfraredButtonIndexNone; } else if(edit_target == InfraredEditTargetRemote) { success = infrared_remote_remove(remote); app_state->current_button_index = InfraredButtonIndexNone; } else { - furi_assert(0); + furi_crash(NULL); } if(success) { scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); } else { + infrared_show_error_message( + infrared, + "Failed to\ndelete %s", + edit_target == InfraredEditTargetButton ? "button" : "file"); const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); @@ -107,6 +123,6 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) } void infrared_scene_edit_delete_on_exit(void* context) { - Infrared* infrared = context; - UNUSED(infrared); + InfraredApp* infrared = context; + view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c index 49a299d2aa..0ee6399142 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_edit_delete_done_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); @@ -16,7 +16,7 @@ void infrared_scene_edit_delete_done_on_enter(void* context) { } bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -43,6 +43,6 @@ bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent e } void infrared_scene_edit_delete_done_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; UNUSED(infrared); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 370c352dbd..4959a83109 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -1,44 +1,69 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" -static void infrared_scene_move_button(uint32_t index_old, uint32_t index_new, void* context) { - InfraredRemote* remote = context; - furi_assert(remote); - infrared_remote_move_button(remote, index_old, index_new); -} +static void infrared_scene_edit_move_button_callback( + uint32_t index_old, + uint32_t index_new, + void* context) { + InfraredApp* infrared = context; + furi_assert(infrared); + + infrared->app_state.prev_button_index = index_old; + infrared->app_state.current_button_index = index_new; -static const char* infrared_scene_get_btn_name(uint32_t index, void* context) { - InfraredRemote* remote = context; - furi_assert(remote); - InfraredRemoteButton* button = infrared_remote_get_button(remote, index); - return (infrared_remote_button_get_name(button)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeButtonSelected); } void infrared_scene_edit_move_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; - infrared_move_view_set_callback(infrared->move_view, infrared_scene_move_button); + for(size_t i = 0; i < infrared_remote_get_signal_count(remote); ++i) { + infrared_move_view_add_item( + infrared->move_view, infrared_remote_get_signal_name(remote, i)); + } + + infrared_move_view_set_callback( + infrared->move_view, infrared_scene_edit_move_button_callback, infrared); - uint32_t btn_count = infrared_remote_get_button_count(remote); - infrared_move_view_list_init( - infrared->move_view, btn_count, infrared_scene_get_btn_name, remote); - infrared_move_view_list_update(infrared->move_view); + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); + view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; - UNUSED(event); - UNUSED(infrared); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InfraredCustomEventTypeButtonSelected) { + infrared_show_loading_popup(infrared, true); + const bool button_moved = infrared_remote_move_signal( + infrared->remote, + infrared->app_state.prev_button_index, + infrared->app_state.current_button_index); + infrared_show_loading_popup(infrared, false); + + if(!button_moved) { + infrared_show_error_message( + infrared, + "Failed to move\n\"%s\"", + infrared_remote_get_signal_name( + infrared->remote, infrared->app_state.current_button_index)); + scene_manager_search_and_switch_to_previous_scene( + infrared->scene_manager, InfraredSceneRemoteList); + } + + consumed = true; + } + } return consumed; } void infrared_scene_edit_move_on_exit(void* context) { - Infrared* infrared = context; - InfraredRemote* remote = infrared->remote; - infrared_remote_store(remote); + InfraredApp* infrared = context; + view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); + infrared_move_view_reset(infrared->move_view); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index a2199215d7..e29f108651 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -1,10 +1,10 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include #include void infrared_scene_edit_rename_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; TextInput* text_input = infrared->text_input; size_t enter_name_length = 0; @@ -14,14 +14,12 @@ void infrared_scene_edit_rename_on_enter(void* context) { text_input_set_header_text(text_input, "Name the button"); const int32_t current_button_index = infrared->app_state.current_button_index; - furi_assert(current_button_index != InfraredButtonIndexNone); + furi_check(current_button_index != InfraredButtonIndexNone); - InfraredRemoteButton* current_button = - infrared_remote_get_button(remote, current_button_index); enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH; strncpy( infrared->text_store[0], - infrared_remote_button_get_name(current_button), + infrared_remote_get_signal_name(remote, current_button_index), enter_name_length); } else if(edit_target == InfraredEditTargetRemote) { @@ -44,7 +42,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { furi_string_free(folder_path); } else { - furi_assert(0); + furi_crash(NULL); } text_input_set_result_callback( @@ -55,11 +53,14 @@ void infrared_scene_edit_rename_on_enter(void* context) { enter_name_length, false); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput); + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); + view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input)); + + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; SceneManager* scene_manager = infrared->scene_manager; InfraredAppState* app_state = &infrared->app_state; @@ -72,18 +73,24 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) if(edit_target == InfraredEditTargetButton) { const int32_t current_button_index = app_state->current_button_index; furi_assert(current_button_index != InfraredButtonIndexNone); - success = infrared_remote_rename_button( - remote, infrared->text_store[0], current_button_index); + infrared_show_loading_popup(infrared, true); + success = infrared_remote_rename_signal( + remote, current_button_index, infrared->text_store[0]); + infrared_show_loading_popup(infrared, false); app_state->current_button_index = InfraredButtonIndexNone; } else if(edit_target == InfraredEditTargetRemote) { success = infrared_rename_current_remote(infrared, infrared->text_store[0]); } else { - furi_assert(0); + furi_crash(NULL); } if(success) { scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); } else { + infrared_show_error_message( + infrared, + "Failed to\nrename %s", + edit_target == InfraredEditTargetButton ? "button" : "file"); scene_manager_search_and_switch_to_previous_scene( scene_manager, InfraredSceneRemoteList); } @@ -95,9 +102,11 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) } void infrared_scene_edit_rename_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; TextInput* text_input = infrared->text_input; + view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input)); + void* validator_context = text_input_get_validator_callback_context(text_input); text_input_set_validator(text_input, NULL, NULL); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c index 6c7096e17d..35f5159894 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_edit_rename_done_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); @@ -16,7 +16,7 @@ void infrared_scene_edit_rename_done_on_enter(void* context) { } bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -33,6 +33,6 @@ bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent e } void infrared_scene_edit_rename_done_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; UNUSED(infrared); } diff --git a/applications/main/infrared/scenes/infrared_scene_error_databases.c b/applications/main/infrared/scenes/infrared_scene_error_databases.c index 4ed4dee583..f51f83f25f 100644 --- a/applications/main/infrared/scenes/infrared_scene_error_databases.c +++ b/applications/main/infrared/scenes/infrared_scene_error_databases.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_error_databases_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 5, 11, &I_SDQuestion_35x43); @@ -16,7 +16,7 @@ void infrared_scene_error_databases_on_enter(void* context) { } bool infrared_scene_error_databases_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -31,7 +31,7 @@ bool infrared_scene_error_databases_on_event(void* context, SceneManagerEvent ev } void infrared_scene_error_databases_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; popup_reset(infrared->popup); infrared_play_notification_message(infrared, InfraredNotificationMessageYellowOff); } diff --git a/applications/main/infrared/scenes/infrared_scene_learn.c b/applications/main/infrared/scenes/infrared_scene_learn.c index 46646c6d69..bcd0a2cd0f 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn.c +++ b/applications/main/infrared/scenes/infrared_scene_learn.c @@ -1,8 +1,8 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include void infrared_scene_learn_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; InfraredWorker* worker = infrared->worker; @@ -21,7 +21,7 @@ void infrared_scene_learn_on_enter(void* context) { } bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -37,7 +37,7 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_learn_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL); infrared_worker_rx_stop(infrared->worker); diff --git a/applications/main/infrared/scenes/infrared_scene_learn_done.c b/applications/main/infrared/scenes/infrared_scene_learn_done.c index 54b7da7247..b4eb38331d 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_done.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_done.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_learn_done_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); @@ -21,7 +21,7 @@ void infrared_scene_learn_done_on_enter(void* context) { } bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -38,7 +38,7 @@ bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) } void infrared_scene_learn_done_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; infrared->app_state.is_learning_new_remote = false; popup_set_header(infrared->popup, NULL, 0, 0, AlignLeft, AlignTop); } diff --git a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c index 104a4cb7b6..be46a86916 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c @@ -1,16 +1,16 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include void infrared_scene_learn_enter_name_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; TextInput* text_input = infrared->text_input; - InfraredSignal* signal = infrared->received_signal; + InfraredSignal* signal = infrared->current_signal; if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); - infrared_text_store_set(infrared, 0, "RAW_%d", raw->timings_size); + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + infrared_text_store_set(infrared, 0, "RAW_%zu", raw->timings_size); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(signal); infrared_text_store_set( infrared, 0, @@ -28,31 +28,32 @@ void infrared_scene_learn_enter_name_on_enter(void* context) { infrared->text_store[0], INFRARED_MAX_BUTTON_NAME_LENGTH, true); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput); } bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; - InfraredSignal* signal = infrared->received_signal; + InfraredApp* infrared = context; + InfraredSignal* signal = infrared->current_signal; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTextEditDone) { - bool success = false; - if(infrared->app_state.is_learning_new_remote) { - success = - infrared_add_remote_with_button(infrared, infrared->text_store[0], signal); - } else { - success = - infrared_remote_add_button(infrared->remote, infrared->text_store[0], signal); - } + const char* signal_name = infrared->text_store[0]; + const bool success = + infrared->app_state.is_learning_new_remote ? + infrared_add_remote_with_button(infrared, signal_name, signal) : + infrared_remote_append_signal(infrared->remote, signal, signal_name); if(success) { scene_manager_next_scene(scene_manager, InfraredSceneLearnDone); dolphin_deed(DolphinDeedIrSave); } else { - dialog_message_show_storage_error(infrared->dialogs, "Failed to save file"); + infrared_show_error_message( + infrared, + "Failed to\n%s", + infrared->app_state.is_learning_new_remote ? "create file" : "add signal"); const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); @@ -65,6 +66,6 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e } void infrared_scene_learn_enter_name_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; UNUSED(infrared); } diff --git a/applications/main/infrared/scenes/infrared_scene_learn_success.c b/applications/main/infrared/scenes/infrared_scene_learn_success.c index 469d4de9e4..deb54bb5ef 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_success.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_success.c @@ -1,26 +1,26 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_learn_success_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; - InfraredSignal* signal = infrared->received_signal; + InfraredSignal* signal = infrared->current_signal; infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn); if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter); - infrared_text_store_set(infrared, 0, "%d samples", raw->timings_size); + infrared_text_store_set(infrared, 0, "%zu samples", raw->timings_size); dialog_ex_set_text(dialog_ex, infrared->text_store[0], 75, 23, AlignLeft, AlignTop); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(signal); uint8_t addr_digits = ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4); uint8_t cmd_digits = @@ -56,7 +56,7 @@ void infrared_scene_learn_success_on_enter(void* context) { } bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; const bool is_transmitter_idle = !infrared->app_state.is_transmitting; bool consumed = false; @@ -84,7 +84,7 @@ bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent even consumed = true; } else if(event.event == DialogExPressCenter) { infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff); - infrared_tx_start_received(infrared); + infrared_tx_start(infrared); consumed = true; } else if(event.event == DialogExReleaseCenter) { infrared_tx_stop(infrared); @@ -96,7 +96,7 @@ bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent even } void infrared_scene_learn_success_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; dialog_ex_reset(infrared->dialog_ex); infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff); } diff --git a/applications/main/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c index c1f5b6627c..5974acbfec 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote.c +++ b/applications/main/infrared/scenes/infrared_scene_remote.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" typedef enum { ButtonIndexPlus = -2, @@ -8,7 +8,7 @@ typedef enum { static void infrared_scene_remote_button_menu_callback(void* context, int32_t index, InputType type) { - Infrared* infrared = context; + InfraredApp* infrared = context; uint16_t custom_type; if(type == InputTypePress) { @@ -26,17 +26,15 @@ static void } void infrared_scene_remote_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; ButtonMenu* button_menu = infrared->button_menu; SceneManager* scene_manager = infrared->scene_manager; - size_t button_count = infrared_remote_get_button_count(remote); - for(size_t i = 0; i < button_count; ++i) { - InfraredRemoteButton* button = infrared_remote_get_button(remote, i); + for(size_t i = 0; i < infrared_remote_get_signal_count(remote); ++i) { button_menu_add_item( button_menu, - infrared_remote_button_get_name(button), + infrared_remote_get_signal_name(remote, i), i, infrared_scene_remote_button_menu_callback, ButtonMenuItemTypeCommon, @@ -68,7 +66,7 @@ void infrared_scene_remote_on_enter(void* context) { } bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; const bool is_transmitter_idle = !infrared->app_state.is_transmitting; bool consumed = false; @@ -116,6 +114,6 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_remote_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; button_menu_reset(infrared->button_menu); } diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 55f14416bf..2276e270a0 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -1,31 +1,34 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_remote_list_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; ViewDispatcher* view_dispatcher = infrared->view_dispatcher; + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); + DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); browser_options.base_path = INFRARED_APP_FOLDER; - bool success = dialog_file_browser_show( - infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); - - if(success) { - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); + while(dialog_file_browser_show( + infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) { + const char* file_path = furi_string_get_cstr(infrared->file_path); infrared_show_loading_popup(infrared, true); - success = infrared_remote_load(infrared->remote, infrared->file_path); + const bool remote_loaded = infrared_remote_load(infrared->remote, file_path); infrared_show_loading_popup(infrared, false); - } - if(success) { - scene_manager_next_scene(scene_manager, InfraredSceneRemote); - } else { - scene_manager_previous_scene(scene_manager); + if(remote_loaded) { + scene_manager_next_scene(scene_manager, InfraredSceneRemote); + return; + } else { + infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); + } } + + scene_manager_previous_scene(scene_manager); } bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 04f17416d9..fa5a599afa 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include typedef enum { @@ -8,7 +8,7 @@ typedef enum { } InfraredRpcState; void infrared_scene_rpc_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_header(popup, "Infrared", 89, 42, AlignCenter, AlignBottom); @@ -27,7 +27,7 @@ void infrared_scene_rpc_on_enter(void* context) { } bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -43,7 +43,8 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); if(arg && (state == InfraredRpcStateIdle)) { furi_string_set(infrared->file_path, arg); - result = infrared_remote_load(infrared->remote, infrared->file_path); + result = infrared_remote_load( + infrared->remote, furi_string_get_cstr(infrared->file_path)); if(result) { scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); @@ -61,7 +62,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); if(arg && (state == InfraredRpcStateLoaded)) { size_t button_index = 0; - if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) { + if(infrared_remote_get_signal_index(infrared->remote, arg, &button_index)) { infrared_tx_start_button_index(infrared, button_index); result = true; scene_manager_set_scene_state( @@ -91,7 +92,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_rpc_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; if(scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc) == InfraredRpcStateSending) { infrared_tx_stop(infrared); diff --git a/applications/main/infrared/scenes/infrared_scene_start.c b/applications/main/infrared/scenes/infrared_scene_start.c index c7df0f45ba..0e23bb7b8c 100644 --- a/applications/main/infrared/scenes/infrared_scene_start.c +++ b/applications/main/infrared/scenes/infrared_scene_start.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" enum SubmenuIndex { SubmenuIndexUniversalRemotes, @@ -8,12 +8,12 @@ enum SubmenuIndex { }; static void infrared_scene_start_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_start_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; SceneManager* scene_manager = infrared->scene_manager; @@ -50,7 +50,7 @@ void infrared_scene_start_on_enter(void* context) { } bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -79,6 +79,6 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_start_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index e09abde70e..197478e334 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" typedef enum { SubmenuIndexUniversalTV, @@ -8,12 +8,12 @@ typedef enum { } SubmenuIndex; static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_universal_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; submenu_add_item( @@ -47,7 +47,7 @@ void infrared_scene_universal_on_enter(void* context) { } bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -72,6 +72,6 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_universal_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 5f762d122f..764a951890 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_ac_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index 3938b6080d..241a22bcbb 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_audio_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index 27ca46ea9d..d8520deb39 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_projector_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index f2958d887d..6031205f55 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_tv_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/views/infrared_move_view.c b/applications/main/infrared/views/infrared_move_view.c index d838a5f828..374a65a44e 100644 --- a/applications/main/infrared/views/infrared_move_view.c +++ b/applications/main/infrared/views/infrared_move_view.c @@ -1,10 +1,11 @@ #include "infrared_move_view.h" +#include + #include #include -#include -#include +#include #define LIST_ITEMS 4U #define LIST_LINE_H 13U @@ -13,42 +14,41 @@ struct InfraredMoveView { View* view; - InfraredMoveCallback move_cb; - void* cb_context; + InfraredMoveCallback callback; + void* callback_context; }; +ARRAY_DEF(InfraredMoveViewItemArray, const char*, M_CSTR_DUP_OPLIST); //-V575 + typedef struct { - const char** btn_names; - uint32_t btn_number; + InfraredMoveViewItemArray_t labels; int32_t list_offset; - int32_t item_idx; + int32_t current_idx; + int32_t start_idx; bool is_moving; - - InfraredMoveGetItemCallback get_item_cb; } InfraredMoveViewModel; static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) { InfraredMoveViewModel* model = _model; - UNUSED(model); - canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned( canvas, canvas_width(canvas) / 2, 0, AlignCenter, AlignTop, "Select a button to move"); - bool show_scrollbar = model->btn_number > LIST_ITEMS; + const size_t btn_number = InfraredMoveViewItemArray_size(model->labels); + const bool show_scrollbar = btn_number > LIST_ITEMS; canvas_set_font(canvas, FontSecondary); - for(uint32_t i = 0; i < MIN(model->btn_number, LIST_ITEMS); i++) { - int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->btn_number, 0u); - uint8_t x_offset = (model->is_moving && model->item_idx == idx) ? MOVE_X_OFFSET : 0; + for(uint32_t i = 0; i < MIN(btn_number, LIST_ITEMS); i++) { + int32_t idx = CLAMP((uint32_t)(i + model->list_offset), btn_number, 0U); + uint8_t x_offset = (model->is_moving && model->current_idx == idx) ? MOVE_X_OFFSET : 0; uint8_t y_offset = HEADER_H + i * LIST_LINE_H; uint8_t box_end_x = canvas_width(canvas) - (show_scrollbar ? 6 : 1); canvas_set_color(canvas, ColorBlack); - if(model->item_idx == idx) { + if(model->current_idx == idx) { canvas_draw_box(canvas, x_offset, y_offset, box_end_x - x_offset, LIST_LINE_H); canvas_set_color(canvas, ColorWhite); @@ -60,7 +60,12 @@ static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_dot(canvas, box_end_x - 1, y_offset + LIST_LINE_H - 1); } canvas_draw_str_aligned( - canvas, x_offset + 3, y_offset + 3, AlignLeft, AlignTop, model->btn_names[idx]); + canvas, + x_offset + 3, + y_offset + 3, + AlignLeft, + AlignTop, + *InfraredMoveViewItemArray_cget(model->labels, idx)); } if(show_scrollbar) { @@ -69,22 +74,22 @@ static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) { canvas_width(canvas), HEADER_H, canvas_height(canvas) - HEADER_H, - model->item_idx, - model->btn_number); + model->current_idx, + btn_number); } } static void update_list_offset(InfraredMoveViewModel* model) { - int32_t bounds = model->btn_number > (LIST_ITEMS - 1) ? 2 : model->btn_number; - - if((model->btn_number > (LIST_ITEMS - 1)) && - (model->item_idx >= ((int32_t)model->btn_number - 1))) { - model->list_offset = model->item_idx - (LIST_ITEMS - 1); - } else if(model->list_offset < model->item_idx - bounds) { - model->list_offset = CLAMP( - model->item_idx - (int32_t)(LIST_ITEMS - 2), (int32_t)model->btn_number - bounds, 0); - } else if(model->list_offset > model->item_idx - bounds) { - model->list_offset = CLAMP(model->item_idx - 1, (int32_t)model->btn_number - bounds, 0); + const size_t btn_number = InfraredMoveViewItemArray_size(model->labels); + const int32_t bounds = btn_number > (LIST_ITEMS - 1) ? 2 : btn_number; + + if((btn_number > (LIST_ITEMS - 1)) && (model->current_idx >= ((int32_t)btn_number - 1))) { + model->list_offset = model->current_idx - (LIST_ITEMS - 1); + } else if(model->list_offset < model->current_idx - bounds) { + model->list_offset = + CLAMP(model->current_idx - (int32_t)(LIST_ITEMS - 2), (int32_t)btn_number - bounds, 0); + } else if(model->list_offset > model->current_idx - bounds) { + model->list_offset = CLAMP(model->current_idx - 1, (int32_t)btn_number - bounds, 0); } } @@ -95,117 +100,130 @@ static bool infrared_move_view_input_callback(InputEvent* event, void* context) if(((event->type == InputTypeShort || event->type == InputTypeRepeat)) && ((event->key == InputKeyUp) || (event->key == InputKeyDown))) { - bool is_moving = false; - uint32_t index_old = 0; - uint32_t index_new = 0; with_view_model( move_view->view, InfraredMoveViewModel * model, { - is_moving = model->is_moving; - index_old = model->item_idx; + const size_t btn_number = InfraredMoveViewItemArray_size(model->labels); + const int32_t item_idx_prev = model->current_idx; + if(event->key == InputKeyUp) { - if(model->item_idx <= 0) { - model->item_idx = model->btn_number; + if(model->current_idx <= 0) { + model->current_idx = btn_number; } - model->item_idx--; + model->current_idx--; + } else if(event->key == InputKeyDown) { - model->item_idx++; - if(model->item_idx >= (int32_t)(model->btn_number)) { - model->item_idx = 0; + model->current_idx++; + if(model->current_idx >= (int32_t)(btn_number)) { + model->current_idx = 0; } } - index_new = model->item_idx; + + if(model->is_moving) { + InfraredMoveViewItemArray_swap_at( + model->labels, item_idx_prev, model->current_idx); + } + update_list_offset(model); }, - !is_moving); - if((is_moving) && (move_view->move_cb)) { - move_view->move_cb(index_old, index_new, move_view->cb_context); - infrared_move_view_list_update(move_view); - } + true); + consumed = true; - } - if((event->key == InputKeyOk) && (event->type == InputTypeShort)) { + } else if((event->key == InputKeyOk) && (event->type == InputTypeShort)) { with_view_model( move_view->view, InfraredMoveViewModel * model, - { model->is_moving = !(model->is_moving); }, + { + if(!model->is_moving) { + model->start_idx = model->current_idx; + } else if(move_view->callback) { + move_view->callback( + model->start_idx, model->current_idx, move_view->callback_context); + } + model->is_moving = !(model->is_moving); + }, true); + consumed = true; - } - return consumed; -} -static void infrared_move_view_on_exit(void* context) { - furi_assert(context); - InfraredMoveView* move_view = context; + } else if(event->key == InputKeyBack) { + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { + if(model->is_moving && move_view->callback) { + move_view->callback( + model->start_idx, model->current_idx, move_view->callback_context); + } + model->is_moving = false; + }, + false); - with_view_model( - move_view->view, - InfraredMoveViewModel * model, - { - if(model->btn_names) { - free(model->btn_names); - model->btn_names = NULL; - } - model->btn_number = 0; - model->get_item_cb = NULL; - }, - false); - move_view->cb_context = NULL; -} + // Not consuming, Back event is passed thru + } -void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback) { - furi_assert(move_view); - move_view->move_cb = callback; + return consumed; } -void infrared_move_view_list_init( +void infrared_move_view_set_callback( InfraredMoveView* move_view, - uint32_t item_count, - InfraredMoveGetItemCallback load_cb, + InfraredMoveCallback callback, void* context) { furi_assert(move_view); - move_view->cb_context = context; + move_view->callback = callback; + move_view->callback_context = context; +} + +void infrared_move_view_add_item(InfraredMoveView* move_view, const char* label) { with_view_model( move_view->view, InfraredMoveViewModel * model, - { - furi_assert(model->btn_names == NULL); - model->btn_names = malloc(sizeof(char*) * item_count); - model->btn_number = item_count; - model->get_item_cb = load_cb; - }, - false); + { InfraredMoveViewItemArray_push_back(model->labels, label); }, + true); } -void infrared_move_view_list_update(InfraredMoveView* move_view) { - furi_assert(move_view); +void infrared_move_view_reset(InfraredMoveView* move_view) { with_view_model( move_view->view, InfraredMoveViewModel * model, { - for(uint32_t i = 0; i < model->btn_number; i++) { - if(!model->get_item_cb) break; - model->btn_names[i] = model->get_item_cb(i, move_view->cb_context); - } + InfraredMoveViewItemArray_reset(model->labels); + model->list_offset = 0; + model->start_idx = 0; + model->current_idx = 0; + model->is_moving = false; }, - true); + false); + move_view->callback_context = NULL; } InfraredMoveView* infrared_move_view_alloc(void) { InfraredMoveView* move_view = malloc(sizeof(InfraredMoveView)); + move_view->view = view_alloc(); view_allocate_model(move_view->view, ViewModelTypeLocking, sizeof(InfraredMoveViewModel)); view_set_draw_callback(move_view->view, infrared_move_view_draw_callback); view_set_input_callback(move_view->view, infrared_move_view_input_callback); - view_set_exit_callback(move_view->view, infrared_move_view_on_exit); view_set_context(move_view->view, move_view); + + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { InfraredMoveViewItemArray_init(model->labels); }, + true); + return move_view; } void infrared_move_view_free(InfraredMoveView* move_view) { + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { InfraredMoveViewItemArray_clear(model->labels); }, + true); + view_free(move_view->view); free(move_view); } diff --git a/applications/main/infrared/views/infrared_move_view.h b/applications/main/infrared/views/infrared_move_view.h index b9b0cd864a..0ab15ce0d1 100644 --- a/applications/main/infrared/views/infrared_move_view.h +++ b/applications/main/infrared/views/infrared_move_view.h @@ -6,20 +6,17 @@ typedef struct InfraredMoveView InfraredMoveView; typedef void (*InfraredMoveCallback)(uint32_t index_old, uint32_t index_new, void* context); -typedef const char* (*InfraredMoveGetItemCallback)(uint32_t index, void* context); - InfraredMoveView* infrared_move_view_alloc(void); void infrared_move_view_free(InfraredMoveView* debug_view); View* infrared_move_view_get_view(InfraredMoveView* debug_view); -void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback); - -void infrared_move_view_list_init( +void infrared_move_view_set_callback( InfraredMoveView* move_view, - uint32_t item_count, - InfraredMoveGetItemCallback load_cb, + InfraredMoveCallback callback, void* context); -void infrared_move_view_list_update(InfraredMoveView* move_view); \ No newline at end of file +void infrared_move_view_add_item(InfraredMoveView* move_view, const char* label); + +void infrared_move_view_reset(InfraredMoveView* move_view); From 9af81ce8d03320ec73bafcda132199e7d82d8674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 31 Oct 2023 19:40:32 +0900 Subject: [PATCH 013/111] Furi: cleanup crash use (#3175) * Furi: optional message in furi_crash and furi_halt * Consistent furi_crash use * UnitTests: crash instead of assert * furi: check: fixed macro for default arg * unit_tests: fixed crashes everywhere * lib: infrared: fixed PVS warnings * furi: eliminated __FURI_ASSERT_MESSAGE_FLAG * Furi: update check.h docs * Furi: add check.h usage note * Docs: grammar --------- Co-authored-by: hedger --- applications/debug/unit_tests/bt/bt_test.c | 4 +- .../debug/unit_tests/infrared/infrared_test.c | 2 +- applications/debug/unit_tests/nfc/nfc_test.c | 2 +- .../debug/unit_tests/nfc/nfc_transport.c | 50 +++++++++---------- .../infrared_scene_edit_button_select.c | 2 +- .../scenes/infrared_scene_edit_delete.c | 4 +- .../scenes/infrared_scene_edit_delete_done.c | 2 +- .../scenes/infrared_scene_edit_rename.c | 4 +- .../desktop/animations/animation_manager.c | 6 +-- .../desktop/views/desktop_view_pin_input.c | 4 +- applications/services/gui/canvas.c | 8 +-- applications/services/gui/elements.c | 2 +- .../services/gui/modules/byte_input.c | 4 +- applications/services/gui/modules/popup.c | 2 +- .../services/gui/modules/text_input.c | 4 +- applications/services/gui/view.c | 4 +- applications/services/gui/view_dispatcher.c | 2 +- applications/services/rpc/rpc_app.c | 2 +- .../scenes/desktop_settings_scene_pin_auth.c | 2 +- .../scenes/desktop_settings_scene_pin_error.c | 2 +- .../desktop_settings_scene_pin_setup_howto.c | 2 +- .../desktop_settings_scene_pin_setup_howto2.c | 2 +- applications/system/hid_app/hid.c | 22 ++++---- documentation/FuriCheck.md | 40 +++++++++++++++ furi/core/check.c | 4 +- furi/core/check.h | 32 ++++++++---- lib/ibutton/ibutton_protocols.c | 2 +- .../nec/infrared_encoder_nec.c | 2 +- .../sirc/infrared_encoder_sirc.c | 2 +- lib/infrared/worker/infrared_transmit.c | 2 +- lib/infrared/worker/infrared_worker.c | 12 ++--- targets/f18/api_symbols.csv | 6 +-- targets/f7/api_symbols.csv | 6 +-- targets/f7/furi_hal/furi_hal_infrared.c | 14 +++--- targets/f7/furi_hal/furi_hal_spi.c | 2 +- targets/f7/furi_hal/furi_hal_subghz.c | 2 +- targets/f7/furi_hal/furi_hal_version.c | 2 +- 37 files changed, 159 insertions(+), 107 deletions(-) create mode 100644 documentation/FuriCheck.md diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/bt/bt_test.c index 2cbfd684a2..32cf6533f8 100644 --- a/applications/debug/unit_tests/bt/bt_test.c +++ b/applications/debug/unit_tests/bt/bt_test.c @@ -28,7 +28,7 @@ void bt_test_alloc() { } void bt_test_free() { - furi_assert(bt_test); + furi_check(bt_test); free(bt_test->nvm_ram_buff_ref); free(bt_test->nvm_ram_buff_dut); bt_keys_storage_free(bt_test->bt_keys_storage); @@ -89,7 +89,7 @@ static void bt_test_keys_remove_test_file() { } MU_TEST(bt_test_keys_storage_serial_profile) { - furi_assert(bt_test); + furi_check(bt_test); bt_test_keys_remove_test_file(); bt_test_keys_storage_profile(); diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index b2acad470e..294c2da9a5 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -27,7 +27,7 @@ static void infrared_test_alloc() { } static void infrared_test_free() { - furi_assert(test); + furi_check(test); infrared_free_decoder(test->decoder_handler); infrared_free_encoder(test->encoder_handler); flipper_format_free(test->ff); diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index e7406fab8a..2d647f8ef5 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -34,7 +34,7 @@ static void nfc_test_alloc() { } static void nfc_test_free() { - furi_assert(nfc_test); + furi_check(nfc_test); furi_record_close(RECORD_STORAGE); free(nfc_test); diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index ee2657bea1..e2e313fdef 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -122,7 +122,7 @@ Nfc* nfc_alloc() { } void nfc_free(Nfc* instance) { - furi_assert(instance); + furi_check(instance); free(instance); } @@ -165,9 +165,9 @@ NfcError nfc_iso14443a_listener_set_col_res_data( uint8_t uid_len, uint8_t* atqa, uint8_t sak) { - furi_assert(instance); - furi_assert(uid); - furi_assert(atqa); + furi_check(instance); + furi_check(uid); + furi_check(atqa); nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak); @@ -176,7 +176,7 @@ NfcError nfc_iso14443a_listener_set_col_res_data( static int32_t nfc_worker_poller(void* context) { Nfc* instance = context; - furi_assert(instance->callback); + furi_check(instance->callback); instance->state = NfcStateReady; NfcCommand command = NfcCommandContinue; @@ -196,7 +196,7 @@ static int32_t nfc_worker_poller(void* context) { } static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, uint16_t rx_bits) { - furi_assert(instance->col_res_status != Iso14443_3aColResStatusDone); + furi_check(instance->col_res_status != Iso14443_3aColResStatusDone); BitBuffer* tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); bool processed = false; @@ -255,7 +255,7 @@ static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, ui static int32_t nfc_worker_listener(void* context) { Nfc* instance = context; - furi_assert(instance->callback); + furi_check(instance->callback); NfcMessage message = {}; @@ -295,17 +295,17 @@ static int32_t nfc_worker_listener(void* context) { } void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { - furi_assert(instance); - furi_assert(instance->worker_thread == NULL); + furi_check(instance); + furi_check(instance->worker_thread == NULL); if(instance->mode == NfcModeListener) { - furi_assert(listener_queue == NULL); + furi_check(listener_queue == NULL); // Check that poller didn't start - furi_assert(poller_queue == NULL); + furi_check(poller_queue == NULL); } else { - furi_assert(poller_queue == NULL); + furi_check(poller_queue == NULL); // Check that poller is started after listener - furi_assert(listener_queue); + furi_check(listener_queue); } instance->callback = callback; @@ -334,8 +334,8 @@ void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { } void nfc_stop(Nfc* instance) { - furi_assert(instance); - furi_assert(instance->worker_thread); + furi_check(instance); + furi_check(instance->worker_thread); if(instance->mode == NfcModeListener) { NfcMessage message = {.type = NfcMessageTypeAbort}; @@ -361,10 +361,10 @@ void nfc_stop(Nfc* instance) { // Called from worker thread NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) { - furi_assert(instance); - furi_assert(poller_queue); - furi_assert(listener_queue); - furi_assert(tx_buffer); + furi_check(instance); + furi_check(poller_queue); + furi_check(listener_queue); + furi_check(tx_buffer); NfcMessage message = {}; message.type = NfcMessageTypeTx; @@ -382,11 +382,11 @@ NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* NfcError nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { - furi_assert(instance); - furi_assert(tx_buffer); - furi_assert(rx_buffer); - furi_assert(poller_queue); - furi_assert(listener_queue); + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + furi_check(poller_queue); + furi_check(listener_queue); UNUSED(fwt); NfcError error = NfcErrorNone; @@ -396,7 +396,7 @@ NfcError message.data.data_bits = bit_buffer_get_size(tx_buffer); bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer)); // Tx - furi_assert(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk); + furi_check(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk); // Rx FuriStatus status = furi_message_queue_get(poller_queue, &message, 50); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c index a76b4e836b..3fd59b5792 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c @@ -48,7 +48,7 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent } else if(edit_mode == InfraredEditModeDelete) { scene_manager_next_scene(scene_manager, InfraredSceneEditDelete); } else { - furi_assert(0); + furi_crash(); } consumed = true; } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index c1735da082..0cb88efdb6 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -60,7 +60,7 @@ void infrared_scene_edit_delete_on_enter(void* context) { infrared_remote_get_name(remote), infrared_remote_get_signal_count(remote)); } else { - furi_assert(0); + furi_crash(); } dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter); @@ -101,7 +101,7 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) success = infrared_remote_remove(remote); app_state->current_button_index = InfraredButtonIndexNone; } else { - furi_crash(NULL); + furi_crash(); } if(success) { diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c index 0ee6399142..9205db4c4e 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c @@ -33,7 +33,7 @@ bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent e view_dispatcher_stop(infrared->view_dispatcher); } } else { - furi_assert(0); + furi_crash(); } consumed = true; } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index e29f108651..178690926d 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -42,7 +42,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { furi_string_free(folder_path); } else { - furi_crash(NULL); + furi_crash(); } text_input_set_result_callback( @@ -81,7 +81,7 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) } else if(edit_target == InfraredEditTargetRemote) { success = infrared_rename_current_remote(infrared, infrared->text_store[0]); } else { - furi_crash(NULL); + furi_crash(); } if(success) { diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index f4c8f17a3a..873fb6aa2c 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -456,7 +456,7 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma } furi_timer_stop(animation_manager->idle_animation_timer); } else { - furi_assert(0); + furi_crash(); } FURI_LOG_I( @@ -528,7 +528,7 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m } } else { /* Unknown state is an error. But not in release version.*/ - furi_assert(0); + furi_crash(); } /* if can't restore previous animation - select new */ @@ -564,7 +564,7 @@ static void animation_manager_switch_to_one_shot_view(AnimationManager* animatio } else if(stats.level == 2) { one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup2_128x64); } else { - furi_assert(0); + furi_crash(); } } diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index d3dadd7d71..93bbffedc6 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -78,7 +78,7 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) { } break; default: - furi_assert(0); + furi_crash(); break; } } @@ -129,7 +129,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu canvas_draw_icon_ex(canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation90); break; default: - furi_assert(0); + furi_crash(); break; } } diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 37edc5d33d..85c0528530 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -135,7 +135,7 @@ void canvas_set_font(Canvas* canvas, Font font) { } else if(font == FontBigNumbers) { u8g2_SetFont(&canvas->fb, u8g2_font_profont22_tn); } else { - furi_crash(NULL); + furi_crash(); } } @@ -175,7 +175,7 @@ void canvas_draw_str_aligned( x -= (u8g2_GetStrWidth(&canvas->fb, str) / 2); break; default: - furi_crash(NULL); + furi_crash(); break; } @@ -189,7 +189,7 @@ void canvas_draw_str_aligned( y += (u8g2_GetAscent(&canvas->fb) / 2); break; default: - furi_crash(NULL); + furi_crash(); break; } @@ -530,7 +530,7 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { rotate_cb = U8G2_R1; break; default: - furi_assert(0); + furi_crash(); } if(need_swap) FURI_SWAP(canvas->width, canvas->height); diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index a6ab84fb8d..e92c2433cd 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -232,7 +232,7 @@ static size_t } else if(horizontal == AlignRight) { px_left = x; } else { - furi_assert(0); + furi_crash(); } if(len_px > px_left) { diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 4846bbd8cc..e9cd78da02 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -79,7 +79,7 @@ static uint8_t byte_input_get_row_size(uint8_t row_index) { row_size = COUNT_OF(keyboard_keys_row_2); break; default: - furi_crash(NULL); + furi_crash(); } return row_size; @@ -102,7 +102,7 @@ static const ByteInputKey* byte_input_get_row(uint8_t row_index) { row = keyboard_keys_row_2; break; default: - furi_crash(NULL); + furi_crash(); } return row; diff --git a/applications/services/gui/modules/popup.c b/applications/services/gui/modules/popup.c index d75abb95fa..520efceef9 100644 --- a/applications/services/gui/modules/popup.c +++ b/applications/services/gui/modules/popup.c @@ -98,7 +98,7 @@ void popup_start_timer(void* context) { if(timer_period == 0) timer_period = 1; if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) { - furi_assert(0); + furi_crash(); }; } } diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 86b7bca1e0..50453cf22c 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -101,7 +101,7 @@ static uint8_t get_row_size(uint8_t row_index) { row_size = COUNT_OF(keyboard_keys_row_3); break; default: - furi_crash(NULL); + furi_crash(); } return row_size; @@ -121,7 +121,7 @@ static const TextInputKey* get_row(uint8_t row_index) { row = keyboard_keys_row_3; break; default: - furi_crash(NULL); + furi_crash(); } return row; diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 07ae072a17..316f5c612a 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -82,7 +82,7 @@ void view_allocate_model(View* view, ViewModelType type, size_t size) { model->data = malloc(size); view->model = model; } else { - furi_crash(NULL); + furi_crash(); } } @@ -99,7 +99,7 @@ void view_free_model(View* view) { free(model); view->model = NULL; } else { - furi_crash(NULL); + furi_crash(); } } diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 0119abc200..87b07a87c4 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -207,7 +207,7 @@ void view_dispatcher_attach_to_gui( } else if(type == ViewDispatcherTypeFullscreen) { gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerFullscreen); } else { - furi_check(NULL); + furi_crash(); } view_dispatcher->gui = gui; } diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index bf44ed2dee..e86eaa493d 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -62,7 +62,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) } else if(status == LoaderStatusOk) { result = PB_CommandStatus_OK; } else { - furi_crash(NULL); + furi_crash(); } } else { result = PB_CommandStatus_ERROR_INVALID_PARAMETERS; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index be2ee48259..b73fe347b2 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -68,7 +68,7 @@ bool desktop_settings_scene_pin_auth_on_event(void* context, SceneManagerEvent e } else if(state == SCENE_STATE_PIN_AUTH_DISABLE) { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinDisable); } else { - furi_assert(0); + furi_crash(); } consumed = true; break; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c index 508992cee7..1ba3c1b2da 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -39,7 +39,7 @@ void desktop_settings_scene_pin_error_on_enter(void* context) { } else if(state == SCENE_STATE_PIN_ERROR_WRONG) { desktop_view_pin_input_set_label_primary(app->pin_input_view, 35, 8, "Wrong PIN!"); } else { - furi_assert(0); + furi_crash(); } desktop_view_pin_input_set_label_secondary(app->pin_input_view, 0, 8, NULL); desktop_view_pin_input_set_label_button(app->pin_input_view, "Retry"); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c index ec128246fa..31eec3871a 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c @@ -32,7 +32,7 @@ bool desktop_settings_scene_pin_setup_howto_on_event(void* context, SceneManager consumed = true; break; default: - furi_crash(NULL); + furi_crash(); } } return consumed; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c index 44b8e1bf79..0ebf85c64b 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c @@ -52,7 +52,7 @@ bool desktop_settings_scene_pin_setup_howto2_on_event(void* context, SceneManage break; } default: - furi_crash(NULL); + furi_crash(); } } return consumed; diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 6c4b928dea..a42fc60917 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -264,7 +264,7 @@ void hid_hal_keyboard_press(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_press(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -275,7 +275,7 @@ void hid_hal_keyboard_release(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_release(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -286,7 +286,7 @@ void hid_hal_keyboard_release_all(Hid* instance) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_release_all(); } else { - furi_crash(NULL); + furi_crash(); } } @@ -297,7 +297,7 @@ void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_consumer_key_press(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -308,7 +308,7 @@ void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_consumer_key_release(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -319,7 +319,7 @@ void hid_hal_consumer_key_release_all(Hid* instance) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_release_all(); } else { - furi_crash(NULL); + furi_crash(); } } @@ -330,7 +330,7 @@ void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_move(dx, dy); } else { - furi_crash(NULL); + furi_crash(); } } @@ -341,7 +341,7 @@ void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_scroll(delta); } else { - furi_crash(NULL); + furi_crash(); } } @@ -352,7 +352,7 @@ void hid_hal_mouse_press(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_press(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -363,7 +363,7 @@ void hid_hal_mouse_release(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_release(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -375,7 +375,7 @@ void hid_hal_mouse_release_all(Hid* instance) { furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); } else { - furi_crash(NULL); + furi_crash(); } } diff --git a/documentation/FuriCheck.md b/documentation/FuriCheck.md new file mode 100644 index 0000000000..02f3fc9173 --- /dev/null +++ b/documentation/FuriCheck.md @@ -0,0 +1,40 @@ +# Run time checks and forced system crash + +The best way to protect system integrity is to reduce amount cases that we must handle and crash the system as early as possible. +For that purpose we have bunch of helpers located in Furi Core check.h. + +## Couple notes before start + +- Definition of Crash - log event, save crash information in RTC and reboot the system. +- Definition of Halt - log event, stall the system. +- Debug and production builds behaves differently: debug build will never reset system in order to preserve state for debugging. +- If you have debugger connected we will stop before reboot automatically. +- All helpers accept optional MESSAGE_CSTR: it can be in RAM or Flash memory, but only messages from Flash will be shown after system reboot. +- MESSAGE_CSTR can be NULL, but macros magic already doing it for you, so just don't. + +## `furi_assert(CONDITION)` or `furi_assert(CONDITION, MESSAGE_CSTR)` + +Assert condition in development environment and crash the system if CONDITION is false. + +- Should be used at development stage in apps and services +- Keep in mind that release never contains this check +- Keep in mind that libraries never contains this check by default, use `LIB_DEBUG=1` if you need it +- Avoid putting function calls into CONDITION, since it may be omitted in some builds + +## `furi_check(CONDITION)` or `furi_check(CONDITION, MESSAGE_CSTR)` + +Always assert condition and crash the system if CONDITION is false. + +- Use it if you always need to check conditions + +## `furi_crash()` or `furi_crash(MESSAGE_CSTR)` + +Crash the system. + +- Use it to crash the system. For example: if abnormal condition detected. + +## `furi_halt()` or `furi_halt(MESSAGE_CSTR)` + +Halt the system. + +- We use it internally to shutdown flipper if poweroff is not possible. diff --git a/furi/core/check.c b/furi/core/check.c index 8888eddfb3..ea1de71425 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -128,7 +128,7 @@ static void __furi_print_name(bool isr) { } } -FURI_NORETURN void __furi_crash() { +FURI_NORETURN void __furi_crash_implementation() { __disable_irq(); GET_MESSAGE_AND_STORE_REGISTERS(); @@ -179,7 +179,7 @@ FURI_NORETURN void __furi_crash() { __builtin_unreachable(); } -FURI_NORETURN void __furi_halt() { +FURI_NORETURN void __furi_halt_implementation() { __disable_irq(); GET_MESSAGE_AND_STORE_REGISTERS(); diff --git a/furi/core/check.h b/furi/core/check.h index 004422e807..2d5df4cf6c 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -28,39 +28,51 @@ extern "C" { #define __FURI_CHECK_MESSAGE_FLAG (0x02) /** Crash system */ -FURI_NORETURN void __furi_crash(); +FURI_NORETURN void __furi_crash_implementation(); /** Halt system */ -FURI_NORETURN void __furi_halt(); +FURI_NORETURN void __furi_halt_implementation(); /** Crash system with message. Show message after reboot. */ -#define furi_crash(message) \ +#define __furi_crash(message) \ do { \ register const void* r12 asm("r12") = (void*)message; \ asm volatile("sukima%=:" : : "r"(r12)); \ - __furi_crash(); \ + __furi_crash_implementation(); \ } while(0) +/** Crash system + * + * @param optional message (const char*) + */ +#define furi_crash(...) M_APPLY(__furi_crash, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) + /** Halt system with message. */ -#define furi_halt(message) \ +#define __furi_halt(message) \ do { \ register const void* r12 asm("r12") = (void*)message; \ asm volatile("sukima%=:" : : "r"(r12)); \ - __furi_halt(); \ + __furi_halt_implementation(); \ } while(0) +/** Halt system + * + * @param optional message (const char*) + */ +#define furi_halt(...) M_APPLY(__furi_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) + /** Check condition and crash if check failed */ #define __furi_check(__e, __m) \ do { \ if(!(__e)) { \ - furi_crash(__m); \ + __furi_crash(__m); \ } \ } while(0) /** Check condition and crash if failed * * @param condition to check - * @param optional message + * @param optional message (const char*) */ #define furi_check(...) \ M_APPLY(__furi_check, M_DEFAULT_ARGS(2, (__FURI_CHECK_MESSAGE_FLAG), __VA_ARGS__)) @@ -70,7 +82,7 @@ FURI_NORETURN void __furi_halt(); #define __furi_assert(__e, __m) \ do { \ if(!(__e)) { \ - furi_crash(__m); \ + __furi_crash(__m); \ } \ } while(0) #else @@ -86,7 +98,7 @@ FURI_NORETURN void __furi_halt(); * @warning only will do check if firmware compiled in debug mode * * @param condition to check - * @param optional message + * @param optional message (const char*) */ #define furi_assert(...) \ M_APPLY(__furi_assert, M_DEFAULT_ARGS(2, (__FURI_ASSERT_MESSAGE_FLAG), __VA_ARGS__)) diff --git a/lib/ibutton/ibutton_protocols.c b/lib/ibutton/ibutton_protocols.c index 75aa225efe..df74126708 100644 --- a/lib/ibutton/ibutton_protocols.c +++ b/lib/ibutton/ibutton_protocols.c @@ -48,7 +48,7 @@ static void ibutton_protocols_get_group_by_id( local_id -= ibutton_protocol_groups[i]->protocol_count; } } - furi_crash(NULL); + furi_crash(); } iButtonProtocols* ibutton_protocols_alloc() { diff --git a/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c index 87f815142a..47d8c3c649 100644 --- a/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c +++ b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c @@ -48,7 +48,7 @@ void infrared_encoder_nec_reset(void* encoder_ptr, const InfraredMessage* messag *data2 = (message->command & 0xFFC0) >> 6; encoder->bits_to_encode = 42; } else { - furi_assert(0); + furi_crash(); } } diff --git a/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c index 6adf2235ce..39c2eb1667 100644 --- a/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c +++ b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c @@ -23,7 +23,7 @@ void infrared_encoder_sirc_reset(void* encoder_ptr, const InfraredMessage* messa *data |= (message->address & 0x1FFF) << 7; encoder->bits_to_encode = 20; } else { - furi_assert(0); + furi_crash(); } } diff --git a/lib/infrared/worker/infrared_transmit.c b/lib/infrared/worker/infrared_transmit.c index 113fb63244..8f99c00662 100644 --- a/lib/infrared/worker/infrared_transmit.c +++ b/lib/infrared/worker/infrared_transmit.c @@ -88,7 +88,7 @@ FuriHalInfraredTxGetDataState state = FuriHalInfraredTxGetDataStateDone; } } else { - furi_crash(NULL); + furi_crash(); } return state; diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 46effd420d..5e3257e260 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -367,10 +367,11 @@ static FuriHalInfraredTxGetDataState *duration = timing.duration; state = timing.state; } else { - furi_assert(0); + // Why bother if we crash anyway?.. *level = 0; *duration = 100; state = FuriHalInfraredTxGetDataStateDone; + furi_crash(); } uint32_t flags_set = furi_thread_flags_set( @@ -414,7 +415,7 @@ static bool infrared_get_new_signal(InfraredWorker* instance) { } else if(response == InfraredWorkerGetSignalResponseStop) { new_signal_obtained = false; } else { - furi_assert(0); + furi_crash(); } return new_signal_obtained; @@ -443,9 +444,8 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { } if(status == InfraredStatusError) { - furi_assert(0); new_data_available = false; - break; + furi_crash(); } else if(status == InfraredStatusOk) { timing.state = FuriHalInfraredTxGetDataStateOk; } else if(status == InfraredStatusDone) { @@ -456,7 +456,7 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { timing.state = FuriHalInfraredTxGetDataStateLastDone; } } else { - furi_assert(0); + furi_crash(); } uint32_t written_size = furi_stream_buffer_send(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0); @@ -548,7 +548,7 @@ static int32_t infrared_worker_tx_thread(void* thread_context) { break; default: - furi_assert(0); + furi_crash(); break; } } diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b4efd7911b..cb62b40c40 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.1,, +Version,+,41.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -302,10 +302,10 @@ Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,-,__fpclassifyd,int,double Function,-,__fpclassifyf,int,float -Function,+,__furi_crash,void, +Function,+,__furi_crash_implementation,void, Function,+,__furi_critical_enter,__FuriCriticalInfo, Function,+,__furi_critical_exit,void,__FuriCriticalInfo -Function,+,__furi_halt,void, +Function,+,__furi_halt_implementation,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" Function,-,__isinfd,int,double diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7599230d72..9d9c1a01b3 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.1,, +Version,+,41.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -370,10 +370,10 @@ Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,-,__fpclassifyd,int,double Function,-,__fpclassifyf,int,float -Function,+,__furi_crash,void, +Function,+,__furi_crash_implementation,void, Function,+,__furi_critical_enter,__FuriCriticalInfo, Function,+,__furi_critical_exit,void,__FuriCriticalInfo -Function,+,__furi_halt,void, +Function,+,__furi_halt_implementation,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" Function,-,__isinfd,int,double diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index d3e36c2b5d..3b20b6bc3a 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -125,7 +125,7 @@ static void furi_hal_infrared_tim_rx_isr() { if(infrared_tim_rx.capture_callback) infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 1, duration); } else { - furi_assert(0); + furi_crash(); } } @@ -141,7 +141,7 @@ static void furi_hal_infrared_tim_rx_isr() { if(infrared_tim_rx.capture_callback) infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 0, duration); } else { - furi_assert(0); + furi_crash(); } } } @@ -254,7 +254,7 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { } else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) { buf_num = 1; } else { - furi_assert(0); + furi_crash(); } return buf_num; } @@ -263,7 +263,7 @@ static void furi_hal_infrared_tx_dma_polarity_isr() { #if INFRARED_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 if(LL_DMA_IsActiveFlag_TE1(INFRARED_DMA)) { LL_DMA_ClearFlag_TE1(INFRARED_DMA); - furi_crash(NULL); + furi_crash(); } if(LL_DMA_IsActiveFlag_TC1(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH1_DEF)) { LL_DMA_ClearFlag_TC1(INFRARED_DMA); @@ -285,7 +285,7 @@ static void furi_hal_infrared_tx_dma_isr() { #if INFRARED_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 if(LL_DMA_IsActiveFlag_TE2(INFRARED_DMA)) { LL_DMA_ClearFlag_TE2(INFRARED_DMA); - furi_crash(NULL); + furi_crash(); } if(LL_DMA_IsActiveFlag_HT2(INFRARED_DMA) && LL_DMA_IsEnabledIT_HT(INFRARED_DMA_CH2_DEF)) { LL_DMA_ClearFlag_HT2(INFRARED_DMA); @@ -303,7 +303,7 @@ static void furi_hal_infrared_tx_dma_isr() { } else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) { /* fallthrough */ } else { - furi_crash(NULL); + furi_crash(); } } if(LL_DMA_IsActiveFlag_TC2(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH2_DEF)) { @@ -596,7 +596,7 @@ static void furi_hal_infrared_async_tx_free_resources(void) { void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) || (freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) { - furi_crash(NULL); + furi_crash(); } furi_assert(furi_hal_infrared_state == InfraredStateIdle); diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index a8884105aa..5c33760ea1 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -221,7 +221,7 @@ bool furi_hal_spi_bus_trx_dma( dma_rx_req = LL_DMAMUX_REQ_SPI2_RX; dma_tx_req = LL_DMAMUX_REQ_SPI2_TX; } else { - furi_crash(NULL); + furi_crash(); } if(rx_buffer == NULL) { diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index ac71b5f6c7..f751463532 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -604,7 +604,7 @@ static void furi_hal_subghz_async_tx_timer_isr() { furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); LL_TIM_DisableCounter(TIM2); } else { - furi_crash(NULL); + furi_crash(); } } } diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index 859a8c39fc..e4364a5189 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -187,7 +187,7 @@ void furi_hal_version_init() { furi_hal_version_load_otp_v2(); break; default: - furi_crash(NULL); + furi_crash(); } furi_hal_rtc_set_register(FuriHalRtcRegisterVersion, (uint32_t)version_get()); From bbe68d6ffc10998a364118abace7ca44ecf6ace7 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 31 Oct 2023 15:27:58 +0400 Subject: [PATCH 014/111] [FL-3629] fbt: SD card resource handling speedup (#3178) * fbt: reduced size of resources dependency graphs, resulting in faster build task evaluation * lib: flipper_app: fixed error message & error handling for plugins --- firmware.scons | 8 +- .../plugins/plugin_manager.c | 3 +- scripts/fbt_tools/fbt_assets.py | 26 +++--- scripts/fbt_tools/fbt_resources.py | 93 +++++++++++-------- 4 files changed, 74 insertions(+), 56 deletions(-) diff --git a/firmware.scons b/firmware.scons index e8e50022c7..537774254c 100644 --- a/firmware.scons +++ b/firmware.scons @@ -148,15 +148,11 @@ if env["IS_BASE_FIRMWARE"]: ) fw_artifacts.append(fw_extapps.sdk_tree) - # Resources for SD card - resources = fwenv.ResourcesDist( - _EXTRA_DIST=[fwenv["DOLPHIN_EXTERNAL_OUT_DIR"]], - ) - + # Resources & manifest for SD card manifest = fwenv.ManifestBuilder( "${RESOURCES_ROOT}/Manifest", - source=resources, GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), + _EXTRA_DIST=[fwenv["DOLPHIN_EXTERNAL_OUT_DIR"]], ) fwenv.Replace(FW_RESOURCES_MANIFEST=manifest) fwenv.Alias("resources", manifest) diff --git a/lib/flipper_application/plugins/plugin_manager.c b/lib/flipper_application/plugins/plugin_manager.c index e2a7b83f42..8f30ed13ec 100644 --- a/lib/flipper_application/plugins/plugin_manager.c +++ b/lib/flipper_application/plugins/plugin_manager.c @@ -66,7 +66,8 @@ PluginManagerError plugin_manager_load_single(PluginManager* manager, const char FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib); if(load_status != FlipperApplicationLoadStatusSuccess) { - FURI_LOG_E(TAG, "Failed to load module_demo_plugin1.fal"); + FURI_LOG_E(TAG, "Failed to load %s", path); + error = PluginManagerErrorLoaderError; break; } diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index dcf391f2d2..5c32ae1a98 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -30,24 +30,26 @@ def _proto_emitter(target, source, env): def _dolphin_emitter(target, source, env): res_root_dir = source[0].Dir(env["DOLPHIN_RES_TYPE"]) - source = [res_root_dir] + source = list() source.extend(env.GlobRecursive("*.*", res_root_dir.srcnode())) target_base_dir = target[0] env.Replace(_DOLPHIN_OUT_DIR=target[0]) + env.Replace(_DOLPHIN_SRC_DIR=res_root_dir) if env["DOLPHIN_RES_TYPE"] == "external": target = [target_base_dir.File("manifest.txt")] ## A detailed list of files to be generated + # Not used ATM, becasuse it inflates the internal dependency graph too much # Preserve original paths, do .png -> .bm conversion - target.extend( - map( - lambda node: target_base_dir.File( - res_root_dir.rel_path(node).replace(".png", ".bm") - ), - filter(lambda node: isinstance(node, File), source), - ) - ) + # target.extend( + # map( + # lambda node: target_base_dir.File( + # res_root_dir.rel_path(node).replace(".png", ".bm") + # ), + # filter(lambda node: isinstance(node, File), source), + # ) + # ) else: asset_basename = f"assets_dolphin_{env['DOLPHIN_RES_TYPE']}" target = [ @@ -55,7 +57,7 @@ def _dolphin_emitter(target, source, env): target_base_dir.File(asset_basename + ".h"), ] - # Debug output + ## Debug output # print( # f"Dolphin res type: {env['DOLPHIN_RES_TYPE']},\ntarget files:", # list(f.path for f in target), @@ -176,7 +178,7 @@ def generate(env): "dolphin", "-s", "dolphin_${DOLPHIN_RES_TYPE}", - "${SOURCE}", + "${_DOLPHIN_SRC_DIR}", "${_DOLPHIN_OUT_DIR}", ], ], @@ -191,7 +193,7 @@ def generate(env): "${PYTHON3}", "${ASSETS_COMPILER}", "dolphin", - "${SOURCE}", + "${_DOLPHIN_SRC_DIR}", "${_DOLPHIN_OUT_DIR}", ], ], diff --git a/scripts/fbt_tools/fbt_resources.py b/scripts/fbt_tools/fbt_resources.py index 47c624081f..4c3146ba45 100644 --- a/scripts/fbt_tools/fbt_resources.py +++ b/scripts/fbt_tools/fbt_resources.py @@ -7,16 +7,21 @@ from SCons.Node.FS import Dir, File -def _resources_dist_emitter(target, source, env): +def __generate_resources_dist_entries(env): + src_target_entries = [] + resources_root = env.Dir(env["RESOURCES_ROOT"]) - target = [] for app_artifacts in env["FW_EXTAPPS"].application_map.values(): for _, dist_path in filter( lambda dist_entry: dist_entry[0], app_artifacts.dist_entries ): - source.append(app_artifacts.compact) - target.append(resources_root.File(dist_path)) + src_target_entries.append( + ( + app_artifacts.compact, + resources_root.File(dist_path), + ) + ) # Deploy apps' resources too for app in env["APPBUILD"].apps: @@ -26,34 +31,48 @@ def _resources_dist_emitter(target, source, env): for res_file in env.GlobRecursive("*", apps_resource_dir): if not isinstance(res_file, File): continue - source.append(res_file) - target.append(resources_root.File(res_file.get_path(apps_resource_dir))) + src_target_entries.append( + ( + res_file, + resources_root.File( + res_file.get_path(apps_resource_dir), + ), + ) + ) # Deploy other stuff from _EXTRA_DIST for extra_dist in env["_EXTRA_DIST"]: if isinstance(extra_dist, Dir): - for extra_file in env.GlobRecursive("*", extra_dist): - if not isinstance(extra_file, File): - continue - source.append(extra_file) - target.append( - # Preserve dir name from original node - resources_root.Dir(extra_dist.name).File( - extra_file.get_path(extra_dist) - ) + src_target_entries.append( + ( + extra_dist, + resources_root.Dir(extra_dist.name), ) + ) else: raise StopError(f"Unsupported extra dist type: {type(extra_dist)}") - assert len(target) == len(source) + return src_target_entries + + +def _resources_dist_emitter(target, source, env): + src_target_entries = __generate_resources_dist_entries(env) + source = list(map(lambda entry: entry[0], src_target_entries)) return (target, source) def _resources_dist_action(target, source, env): + dist_entries = __generate_resources_dist_entries(env) + assert len(dist_entries) == len(source) shutil.rmtree(env.Dir(env["RESOURCES_ROOT"]).abspath, ignore_errors=True) - for src, target in zip(source, target): - os.makedirs(os.path.dirname(target.path), exist_ok=True) - shutil.copy(src.path, target.path) + for src, target in dist_entries: + if isinstance(src, File): + os.makedirs(os.path.dirname(target.path), exist_ok=True) + shutil.copy(src.path, target.path) + elif isinstance(src, Dir): + shutil.copytree(src.path, target.path) + else: + raise StopError(f"Unsupported dist entry type: {type(src)}") def generate(env, **kw): @@ -69,26 +88,26 @@ def generate(env, **kw): env.Append( BUILDERS={ - "ResourcesDist": Builder( - action=Action( - _resources_dist_action, - "${RESOURCEDISTCOMSTR}", - ), - emitter=_resources_dist_emitter, - ), "ManifestBuilder": Builder( - action=Action( - [ + action=[ + Action( + _resources_dist_action, + "${RESOURCEDISTCOMSTR}", + ), + Action( [ - "${PYTHON3}", - "${ASSETS_COMPILER}", - "manifest", - "${TARGET.dir.posix}", - "--timestamp=${GIT_UNIX_TIMESTAMP}", - ] - ], - "${RESMANIFESTCOMSTR}", - ) + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "manifest", + "${TARGET.dir.posix}", + "--timestamp=${GIT_UNIX_TIMESTAMP}", + ] + ], + "${RESMANIFESTCOMSTR}", + ), + ], + emitter=_resources_dist_emitter, ), } ) From bf8984a225099152b23dd1590862b635d81a7329 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:34:21 +0400 Subject: [PATCH 015/111] [FL-3647] Rename menu items related to dummy mode and sound (#3177) Co-authored-by: hedger --- .../services/desktop/views/desktop_view_lock_menu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index f4790ebb8c..1ba8542b4a 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -60,13 +60,13 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { str = "Lock"; } else if(i == DesktopLockMenuIndexStealth) { if(m->stealth_mode) { - str = "Sound Mode"; + str = "Unmute"; } else { - str = "Stealth Mode"; + str = "Mute"; } } else if(i == DesktopLockMenuIndexDummy) { //-V547 if(m->dummy_mode) { - str = "Brainiac Mode"; + str = "Default Mode"; } else { str = "Dummy Mode"; } From 7bd3bd7ea4a0d600d7e5171dcfe7574da2019526 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 1 Nov 2023 08:21:31 +0400 Subject: [PATCH 016/111] fbt: source collection improvements (#3181) * fbt: reduced amount of redundant compilation units * fbt: added GatherSources() method which can reject source paths starting with "!" in sources list; optimized apps' source lists * docs: updated on path exclusion for `sources` * apps: examples: fixed example_advanced_plugins source list * docs: more details on `sources`; apps: narrower sources lists --- .../examples/example_plugins/application.fam | 3 +++ .../example_plugins_advanced/application.fam | 1 + applications/main/ibutton/application.fam | 1 + applications/main/infrared/application.fam | 6 +++++ applications/main/lfrfid/application.fam | 1 + applications/main/nfc/application.fam | 12 +++++++++ applications/main/subghz/application.fam | 6 +++++ documentation/AppManifests.md | 2 +- firmware.scons | 2 +- scripts/fbt_tools/fbt_extapps.py | 12 +++------ scripts/fbt_tools/sconsrecursiveglob.py | 27 ++++++++++++++++++- 11 files changed, 61 insertions(+), 12 deletions(-) diff --git a/applications/examples/example_plugins/application.fam b/applications/examples/example_plugins/application.fam index a6e3c20781..d9a36da564 100644 --- a/applications/examples/example_plugins/application.fam +++ b/applications/examples/example_plugins/application.fam @@ -5,6 +5,7 @@ App( entry_point="example_plugins_app", stack_size=2 * 1024, fap_category="Examples", + sources=["*.c", "!plugin*.c"], ) App( @@ -21,6 +22,7 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="example_plugin1_ep", requires=["example_plugins", "example_plugins_multi"], + sources=["plugin1.c"], ) App( @@ -28,4 +30,5 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="example_plugin2_ep", requires=["example_plugins_multi"], + sources=["plugin2.c"], ) diff --git a/applications/examples/example_plugins_advanced/application.fam b/applications/examples/example_plugins_advanced/application.fam index d40c0dde29..0c7e3e3b94 100644 --- a/applications/examples/example_plugins_advanced/application.fam +++ b/applications/examples/example_plugins_advanced/application.fam @@ -5,6 +5,7 @@ App( entry_point="example_advanced_plugins_app", stack_size=2 * 1024, fap_category="Examples", + sources=["*.c*", "!plugin*.c"], ) App( diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index a8faa629ce..01c02ec23d 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -17,5 +17,6 @@ App( apptype=FlipperAppType.STARTUP, targets=["f7"], entry_point="ibutton_on_system_start", + sources=["ibutton_cli.c"], order=60, ) diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 055d6c3e29..575bebbe48 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -7,6 +7,7 @@ App( icon="A_Infrared_14", stack_size=3 * 1024, order=40, + sources=["*.c", "!infrared_cli.c"], resources="resources", fap_libs=["assets"], fap_icon="icon.png", @@ -18,5 +19,10 @@ App( apptype=FlipperAppType.STARTUP, targets=["f7"], entry_point="infrared_on_system_start", + sources=[ + "infrared_cli.c", + "infrared_brute_force.c", + "infrared_signal.c", + ], order=20, ) diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index cad07fbf77..c067d786fc 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -17,5 +17,6 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="lfrfid_on_system_start", + sources=["lfrfid_cli.c"], order=50, ) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 3c8dab2bf1..33a2011a70 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -8,6 +8,11 @@ App( stack_size=5 * 1024, order=30, resources="resources", + sources=[ + "*.c", + "!plugins", + "!nfc_cli.c", + ], fap_libs=["assets"], fap_icon="icon.png", fap_category="NFC", @@ -21,6 +26,7 @@ App( entry_point="all_in_one_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/all_in_one.c"], ) App( @@ -29,6 +35,7 @@ App( entry_point="opal_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/opal.c"], ) App( @@ -37,6 +44,7 @@ App( entry_point="myki_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/myki.c"], ) App( @@ -45,6 +53,7 @@ App( entry_point="troika_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/troika.c"], ) App( @@ -53,6 +62,7 @@ App( entry_point="plantain_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/plantain.c"], ) App( @@ -61,6 +71,7 @@ App( entry_point="two_cities_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/two_cities.c"], ) App( @@ -68,5 +79,6 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="nfc_on_system_start", + sources=["nfc_cli.c"], order=30, ) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index ba9b16abfd..5f9f24dcd3 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -7,6 +7,11 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, + sources=[ + "*.c", + "!subghz_cli.c", + "!helpers/subghz_chat.c", + ], resources="resources", fap_libs=["assets", "hwdrivers"], fap_icon="icon.png", @@ -18,5 +23,6 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="subghz_on_system_start", + sources=["subghz_cli.c", "helpers/subghz_chat.c"], order=40, ) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 0b3217c58c..d190a798ba 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -47,7 +47,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt The following parameters are used only for [FAPs](./AppsOnSDCard.md): -- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. +- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion. - **fap_version**: string, application version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. - **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. - **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. diff --git a/firmware.scons b/firmware.scons index 537774254c..eca6afc4c7 100644 --- a/firmware.scons +++ b/firmware.scons @@ -197,7 +197,7 @@ sources = [apps_c] # Gather sources only from app folders in current configuration sources.extend( itertools.chain.from_iterable( - fwenv.GlobRecursive(source_type, appdir.relpath, exclude=["lib"]) + fwenv.GatherSources([source_type, "!lib"], appdir.relpath) for appdir, source_type in fwenv["APPBUILD"].get_builtin_app_folders() ) ) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 94307539a7..b88fa79291 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -147,16 +147,10 @@ def _build_app(self): CPPPATH=[self.app_work_dir, self.app._appdir], ) - app_sources = list( - itertools.chain.from_iterable( - self.app_env.GlobRecursive( - source_type, - self.app_work_dir, - exclude="lib", - ) - for source_type in self.app.sources - ) + app_sources = self.app_env.GatherSources( + [self.app.sources, "!lib"], self.app_work_dir ) + if not app_sources: raise UserError(f"No source files found for {self.app.appid}") diff --git a/scripts/fbt_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py index e7eb8fb729..de64c76b7b 100644 --- a/scripts/fbt_tools/sconsrecursiveglob.py +++ b/scripts/fbt_tools/sconsrecursiveglob.py @@ -1,7 +1,9 @@ +import itertools + import SCons from fbt.util import GLOB_FILE_EXCLUSION -from SCons.Script import Flatten from SCons.Node.FS import has_glob_magic +from SCons.Script import Flatten def GlobRecursive(env, pattern, node=".", exclude=[]): @@ -23,12 +25,35 @@ def GlobRecursive(env, pattern, node=".", exclude=[]): # Otherwise, just assume that file at path exists else: results.append(node.File(pattern)) + ## Debug # print(f"Glob result for {pattern} from {node}: {results}") return results +def GatherSources(env, sources_list, node="."): + sources_list = list(set(Flatten(sources_list))) + include_sources = list(filter(lambda x: not x.startswith("!"), sources_list)) + exclude_sources = list(x[1:] for x in sources_list if x.startswith("!")) + gathered_sources = list( + itertools.chain.from_iterable( + env.GlobRecursive( + source_type, + node, + exclude=exclude_sources, + ) + for source_type in include_sources + ) + ) + ## Debug + # print( + # f"Gathered sources for {sources_list} from {node}: {list(f.path for f in gathered_sources)}" + # ) + return gathered_sources + + def generate(env): env.AddMethod(GlobRecursive) + env.AddMethod(GatherSources) def exists(env): From aa063285165abf2bef9582f3a01b422cedf651e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 1 Nov 2023 16:24:11 +0900 Subject: [PATCH 017/111] Furi, FuriHal: remove FreeRTOS headers leaks (#3179) * Furi: remove direct FreeRTOS timers use * Furi: eliminate FreeRTOS headers leak. What did it cost? Everything... * SubGhz: proper public api for protocols. Format Sources. * Furi: slightly less redundant declarations * Desktop: proper types in printf * Sync API Symbols * Furi: add timer reset and fix dolphin service, fix unit tests * Furi: proper timer restart method naming and correct behavior in timer stopped state. --------- Co-authored-by: hedger --- applications/debug/direct_draw/direct_draw.c | 2 +- applications/debug/unit_tests/rpc/rpc_test.c | 10 +- applications/debug/unit_tests/test_index.c | 4 +- applications/main/infrared/infrared_app.c | 5 +- applications/main/nfc/nfc_app.c | 5 +- applications/main/subghz/views/receiver.c | 6 +- .../desktop/animations/animation_manager.c | 3 +- .../desktop/animations/animation_storage.c | 4 +- .../animations/views/bubble_animation_view.c | 8 +- .../views/one_shot_animation_view.c | 13 +- .../desktop/scenes/desktop_scene_locked.c | 1 - .../desktop/scenes/desktop_scene_pin_input.c | 23 ++-- .../scenes/desktop_scene_pin_timeout.c | 1 - .../desktop/views/desktop_view_locked.c | 19 ++- .../desktop/views/desktop_view_main.c | 21 ++-- .../desktop/views/desktop_view_pin_input.c | 27 ++-- .../desktop/views/desktop_view_pin_timeout.c | 17 ++- applications/services/dolphin/dolphin.c | 43 ++++--- applications/services/dolphin/dolphin_i.h | 6 +- applications/services/gui/icon_animation.c | 8 +- applications/services/input/input.c | 20 +-- applications/services/rpc/rpc.c | 3 +- applications/services/rpc/rpc.h | 2 +- applications/services/rpc/rpc_cli.c | 1 - applications/system/updater/updater.c | 1 - furi/core/base.h | 1 + furi/core/check.c | 2 - furi/core/common_defines.h | 2 - furi/core/critical.c | 3 + furi/core/event_flag.c | 1 + furi/core/kernel.c | 7 ++ furi/core/kernel.h | 6 + furi/core/memmgr_heap.c | 4 +- furi/core/message_queue.c | 3 +- furi/core/mutex.c | 1 + furi/core/semaphore.c | 1 + furi/core/stream_buffer.c | 1 + furi/core/thread.c | 4 +- furi/core/thread.h | 6 +- furi/core/timer.c | 39 ++++++ furi/core/timer.h | 27 ++++ furi/flipper.c | 2 + furi/furi.c | 4 +- furi/furi.h | 3 - lib/infrared/worker/infrared_worker.c | 6 +- lib/lfrfid/lfrfid_worker.c | 4 +- .../iso14443_4a/iso14443_4a_poller_i.h | 2 - .../iso14443_4b/iso14443_4b_poller_i.h | 2 - lib/print/wrappers.h | 3 +- lib/subghz/SConscript | 1 + lib/subghz/protocols/bin_raw.h | 5 +- lib/subghz/protocols/keeloq.h | 21 +--- lib/subghz/protocols/public_api.h | 63 ++++++++++ lib/subghz/protocols/secplus_v1.h | 9 +- lib/subghz/protocols/secplus_v2.h | 21 +--- lib/subghz/subghz_protocol_registry.h | 23 ---- lib/toolbox/buffer_stream.c | 2 +- lib/toolbox/buffer_stream.h | 2 +- targets/f18/api_symbols.csv | 114 +---------------- targets/f7/api_symbols.csv | 115 ++---------------- targets/f7/ble_glue/gap.c | 2 - targets/f7/furi_hal/furi_hal_flash.c | 3 + targets/f7/furi_hal/furi_hal_os.c | 3 + targets/f7/furi_hal/furi_hal_power.c | 8 +- targets/f7/furi_hal/furi_hal_spi.c | 2 +- targets/f7/inc/FreeRTOSConfig.h | 3 +- targets/f7/inc/furi_config.h | 3 + targets/f7/src/main.c | 1 - 68 files changed, 316 insertions(+), 472 deletions(-) create mode 100644 lib/subghz/protocols/public_api.h create mode 100644 targets/f7/inc/furi_config.h diff --git a/applications/debug/direct_draw/direct_draw.c b/applications/debug/direct_draw/direct_draw.c index 71c65eab13..63e03530a7 100644 --- a/applications/debug/direct_draw/direct_draw.c +++ b/applications/debug/direct_draw/direct_draw.c @@ -71,7 +71,7 @@ static void direct_draw_run(DirectDraw* instance) { size_t counter = 0; float fps = 0; - vTaskPrioritySet(furi_thread_get_current_id(), FuriThreadPriorityIdle); + furi_thread_set_current_priority(FuriThreadPriorityIdle); do { size_t elapsed = DWT->CYCCNT - start; diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 645e75e844..5659ba877d 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -18,6 +18,8 @@ #include #include #include + +#include #include LIST_DEF(MsgList, PB_Main, M_POD_OPLIST) @@ -36,7 +38,7 @@ typedef struct { FuriStreamBuffer* output_stream; SemaphoreHandle_t close_session_semaphore; SemaphoreHandle_t terminate_semaphore; - TickType_t timeout; + uint32_t timeout; } RpcSessionContext; static RpcSessionContext rpc_session[TEST_RPC_SESSIONS]; @@ -544,7 +546,7 @@ static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_ RpcSessionContext* session_context = istream->state; size_t bytes_received = 0; - TickType_t now = xTaskGetTickCount(); + uint32_t now = furi_get_tick(); int32_t time_left = session_context->timeout - now; time_left = MAX(time_left, 0); bytes_received = @@ -688,7 +690,7 @@ static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t ses furi_check(!MsgList_empty_p(expected_msg_list)); furi_check(session < TEST_RPC_SESSIONS); - rpc_session[session].timeout = xTaskGetTickCount() + MAX_RECEIVE_OUTPUT_TIMEOUT; + rpc_session[session].timeout = furi_get_tick() + MAX_RECEIVE_OUTPUT_TIMEOUT; pb_istream_t istream = { .callback = test_rpc_pb_stream_read, .state = &rpc_session[session], @@ -712,7 +714,7 @@ static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t ses pb_release(&PB_Main_msg, &result); } - rpc_session[session].timeout = xTaskGetTickCount() + 50; + rpc_session[session].timeout = furi_get_tick() + 50; if(pb_decode_ex(&istream, &PB_Main_msg, &result, PB_DECODE_DELIMITED)) { mu_fail("decoded more than expected"); } diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index edaf950c54..7c1b6b4447 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -65,8 +65,8 @@ const UnitTest unit_tests[] = { void minunit_print_progress() { static const char progress[] = {'\\', '|', '/', '-'}; static uint8_t progress_counter = 0; - static TickType_t last_tick = 0; - TickType_t current_tick = xTaskGetTickCount(); + static uint32_t last_tick = 0; + uint32_t current_tick = furi_get_tick(); if(current_tick - last_tick > 20) { last_tick = current_tick; printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index e29eda30fb..7abb4e4eb6 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -384,18 +384,17 @@ void infrared_play_notification_message( } void infrared_show_loading_popup(const InfraredApp* infrared, bool show) { - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); ViewStack* view_stack = infrared->view_stack; Loading* loading = infrared->loading; if(show) { // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); view_stack_add_view(view_stack, loading_get_view(loading)); } else { view_stack_remove_view(view_stack, loading_get_view(loading)); // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); } } diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index fe680aa32d..9e0de8891b 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -411,15 +411,14 @@ bool nfc_load_from_file_select(NfcApp* instance) { void nfc_show_loading_popup(void* context, bool show) { NfcApp* nfc = context; - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); if(show) { // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading); } else { // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); } } diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index e1014b8110..23fa26c772 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -91,7 +91,7 @@ void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool loc SubGhzViewReceiverModel * model, { model->bar_show = SubGhzViewReceiverBarShowLock; }, true); - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + furi_timer_start(subghz_receiver->timer, 1000); } else { with_view_model( subghz_receiver->view, @@ -316,7 +316,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowToUnlockPress; }, true); if(subghz_receiver->lock_count == 0) { - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + furi_timer_start(subghz_receiver->timer, 1000); } if(event->key == InputKeyBack && event->type == InputTypeShort) { subghz_receiver->lock_count++; @@ -330,7 +330,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowUnlock; }, true); //subghz_receiver->lock = false; - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(650)); + furi_timer_start(subghz_receiver->timer, 650); } return true; diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index 873fb6aa2c..44c0c228c4 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -450,7 +449,7 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma animation_manager->state = AnimationManagerStateFreezedIdle; animation_manager->freezed_animation_time_left = - xTimerGetExpiryTime(animation_manager->idle_animation_timer) - xTaskGetTickCount(); + furi_timer_get_expire_time(animation_manager->idle_animation_timer) - furi_get_tick(); if(animation_manager->freezed_animation_time_left < 0) { animation_manager->freezed_animation_time_left = 0; } diff --git a/applications/services/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c index 2c16cf726d..c99cc7b5d0 100644 --- a/applications/services/desktop/animations/animation_storage.c +++ b/applications/services/desktop/animations/animation_storage.c @@ -304,7 +304,7 @@ static bool animation_storage_load_frames( if(file_info.size > max_filesize) { FURI_LOG_E( TAG, - "Filesize %lld, max: %d (width %d, height %d)", + "Filesize %llu, max: %zu (width %u, height %u)", file_info.size, max_filesize, width, @@ -329,7 +329,7 @@ static bool animation_storage_load_frames( if(!frames_ok) { FURI_LOG_E( TAG, - "Load \'%s\' failed, %dx%d, size: %lld", + "Load \'%s\' failed, %ux%u, size: %llu", furi_string_get_cstr(filename), width, height, diff --git a/applications/services/desktop/animations/views/bubble_animation_view.c b/applications/services/desktop/animations/views/bubble_animation_view.c index 30a165087b..9585b2771e 100644 --- a/applications/services/desktop/animations/views/bubble_animation_view.c +++ b/applications/services/desktop/animations/views/bubble_animation_view.c @@ -23,7 +23,7 @@ typedef struct { uint8_t active_bubbles; uint8_t passive_bubbles; uint8_t active_shift; - TickType_t active_ended_at; + uint32_t active_ended_at; Icon* freeze_frame; } BubbleAnimationViewModel; @@ -154,7 +154,7 @@ static void bubble_animation_activate(BubbleAnimationView* view, bool force) { if(model->current != NULL) { if(!force) { if((model->active_ended_at + model->current->active_cooldown * 1000) > - xTaskGetTickCount()) { + furi_get_tick()) { activate = false; } else if(model->active_shift) { activate = false; @@ -215,7 +215,7 @@ static void bubble_animation_next_frame(BubbleAnimationViewModel* model) { model->active_cycle = 0; model->current_frame = 0; model->current_bubble = bubble_animation_pick_bubble(model, false); - model->active_ended_at = xTaskGetTickCount(); + model->active_ended_at = furi_get_tick(); } if(model->current_bubble) { @@ -355,7 +355,7 @@ void bubble_animation_view_set_animation( furi_assert(model); model->current = new_animation; - model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000); + model->active_ended_at = furi_get_tick() - (model->current->active_cooldown * 1000); model->active_bubbles = 0; model->passive_bubbles = 0; for(int i = 0; i < new_animation->frame_bubble_sequences_count; ++i) { diff --git a/applications/services/desktop/animations/views/one_shot_animation_view.c b/applications/services/desktop/animations/views/one_shot_animation_view.c index 077f82d092..004fcde7b5 100644 --- a/applications/services/desktop/animations/views/one_shot_animation_view.c +++ b/applications/services/desktop/animations/views/one_shot_animation_view.c @@ -1,7 +1,6 @@ #include "one_shot_animation_view.h" #include -#include #include #include #include @@ -11,7 +10,7 @@ typedef void (*OneShotInteractCallback)(void*); struct OneShotView { View* view; - TimerHandle_t update_timer; + FuriTimer* update_timer; OneShotInteractCallback interact_callback; void* interact_callback_context; }; @@ -22,8 +21,8 @@ typedef struct { bool block_input; } OneShotViewModel; -static void one_shot_view_update_timer_callback(TimerHandle_t xTimer) { - OneShotView* view = (void*)pvTimerGetTimerID(xTimer); +static void one_shot_view_update_timer_callback(void* context) { + OneShotView* view = context; OneShotViewModel* model = view_get_model(view->view); if((model->index + 1) < model->icon->frame_count) { @@ -81,7 +80,7 @@ OneShotView* one_shot_view_alloc(void) { OneShotView* view = malloc(sizeof(OneShotView)); view->view = view_alloc(); view->update_timer = - xTimerCreate(NULL, 1000, pdTRUE, view, one_shot_view_update_timer_callback); + furi_timer_alloc(one_shot_view_update_timer_callback, FuriTimerTypePeriodic, view); view_allocate_model(view->view, ViewModelTypeLocking, sizeof(OneShotViewModel)); view_set_context(view->view, view); @@ -94,7 +93,7 @@ OneShotView* one_shot_view_alloc(void) { void one_shot_view_free(OneShotView* view) { furi_assert(view); - xTimerDelete(view->update_timer, portMAX_DELAY); + furi_timer_free(view->update_timer); view_free(view->view); view->view = NULL; free(view); @@ -120,7 +119,7 @@ void one_shot_view_start_animation(OneShotView* view, const Icon* icon) { model->icon = icon; model->block_input = true; view_commit_model(view->view, true); - xTimerChangePeriod(view->update_timer, 1000 / model->icon->frame_rate, portMAX_DELAY); + furi_timer_start(view->update_timer, 1000 / model->icon->frame_rate); } View* one_shot_view_get_view(OneShotView* view) { diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index bbed560015..034eedb8ac 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -3,7 +3,6 @@ #include #include #include -#include #include "../desktop.h" #include "../desktop_i.h" diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c index e062c1b97d..0e248def60 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_input.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -20,7 +19,7 @@ #define INPUT_PIN_VIEW_TIMEOUT 15000 typedef struct { - TimerHandle_t timer; + FuriTimer* timer; } DesktopScenePinInputState; static void desktop_scene_locked_light_red(bool value) { @@ -33,17 +32,16 @@ static void desktop_scene_locked_light_red(bool value) { furi_record_close(RECORD_NOTIFICATION); } -static void - desktop_scene_pin_input_set_timer(Desktop* desktop, bool enable, TickType_t new_period) { +static void desktop_scene_pin_input_set_timer(Desktop* desktop, bool enable, uint32_t new_period) { furi_assert(desktop); DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state( desktop->scene_manager, DesktopScenePinInput); furi_assert(state); if(enable) { - xTimerChangePeriod(state->timer, new_period, portMAX_DELAY); + furi_timer_start(state->timer, new_period); } else { - xTimerStop(state->timer, portMAX_DELAY); + furi_timer_stop(state->timer); } } @@ -64,8 +62,8 @@ static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* } } -static void desktop_scene_pin_input_timer_callback(TimerHandle_t timer) { - Desktop* desktop = pvTimerGetTimerID(timer); +static void desktop_scene_pin_input_timer_callback(void* context) { + Desktop* desktop = context; view_dispatcher_send_custom_event( desktop->view_dispatcher, DesktopPinInputEventResetWrongPinLabel); @@ -84,7 +82,7 @@ void desktop_scene_pin_input_on_enter(void* context) { DesktopScenePinInputState* state = malloc(sizeof(DesktopScenePinInputState)); state->timer = - xTimerCreate(NULL, 10000, pdFALSE, desktop, desktop_scene_pin_input_timer_callback); + furi_timer_alloc(desktop_scene_pin_input_timer_callback, FuriTimerTypeOnce, desktop); scene_manager_set_scene_state(desktop->scene_manager, DesktopScenePinInput, (uint32_t)state); desktop_view_pin_input_hide_pin(desktop->pin_input_view, true); @@ -149,10 +147,7 @@ void desktop_scene_pin_input_on_exit(void* context) { DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state( desktop->scene_manager, DesktopScenePinInput); - xTimerStop(state->timer, portMAX_DELAY); - while(xTimerIsTimerActive(state->timer)) { - furi_delay_tick(1); - } - xTimerDelete(state->timer, portMAX_DELAY); + + furi_timer_free(state->timer); free(state); } diff --git a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c index 2f009e7d2a..e3336ad76d 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c @@ -1,6 +1,5 @@ #include #include -#include #include #include "../desktop_i.h" diff --git a/applications/services/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c index 3cee25425e..8df889dddd 100644 --- a/applications/services/desktop/views/desktop_view_locked.c +++ b/applications/services/desktop/views/desktop_view_locked.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "../desktop_i.h" @@ -29,7 +28,7 @@ struct DesktopViewLocked { DesktopViewLockedCallback callback; void* context; - TimerHandle_t timer; + FuriTimer* timer; uint8_t lock_count; uint32_t lock_lastpress; }; @@ -58,8 +57,8 @@ void desktop_view_locked_set_callback( locked_view->context = context; } -static void locked_view_timer_callback(TimerHandle_t timer) { - DesktopViewLocked* locked_view = pvTimerGetTimerID(timer); +static void locked_view_timer_callback(void* context) { + DesktopViewLocked* locked_view = context; locked_view->callback(DesktopLockedEventUpdate, locked_view->context); } @@ -90,7 +89,7 @@ static void desktop_view_locked_update_hint_icon_timeout(DesktopViewLocked* lock model->view_state = DesktopViewLockedStateLockedHintShown; } view_commit_model(locked_view->view, change_state); - xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(LOCKED_HINT_TIMEOUT_MS), portMAX_DELAY); + furi_timer_start(locked_view->timer, LOCKED_HINT_TIMEOUT_MS); } void desktop_view_locked_update(DesktopViewLocked* locked_view) { @@ -110,7 +109,7 @@ void desktop_view_locked_update(DesktopViewLocked* locked_view) { view_commit_model(locked_view->view, true); if(view_state != DesktopViewLockedStateDoorsClosing) { - xTimerStop(locked_view->timer, portMAX_DELAY); + furi_timer_stop(locked_view->timer); } } @@ -148,7 +147,7 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) { furi_assert(context); bool is_changed = false; - const uint32_t press_time = xTaskGetTickCount(); + const uint32_t press_time = furi_get_tick(); DesktopViewLocked* locked_view = context; DesktopViewLockedModel* model = view_get_model(locked_view->view); if(model->view_state == DesktopViewLockedStateUnlockedHintShown && @@ -196,7 +195,7 @@ DesktopViewLocked* desktop_view_locked_alloc() { DesktopViewLocked* locked_view = malloc(sizeof(DesktopViewLocked)); locked_view->view = view_alloc(); locked_view->timer = - xTimerCreate(NULL, 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback); + furi_timer_alloc(locked_view_timer_callback, FuriTimerTypePeriodic, locked_view); view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopViewLockedModel)); view_set_context(locked_view->view, locked_view); @@ -219,7 +218,7 @@ void desktop_view_locked_close_doors(DesktopViewLocked* locked_view) { model->view_state = DesktopViewLockedStateDoorsClosing; model->door_offset = DOOR_OFFSET_START; view_commit_model(locked_view->view, true); - xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(DOOR_MOVING_INTERVAL_MS), portMAX_DELAY); + furi_timer_start(locked_view->timer, DOOR_MOVING_INTERVAL_MS); } void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) { @@ -236,7 +235,7 @@ void desktop_view_locked_unlock(DesktopViewLocked* locked_view) { model->view_state = DesktopViewLockedStateUnlockedHintShown; model->pin_locked = false; view_commit_model(locked_view->view, true); - xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(UNLOCKED_HINT_TIMEOUT_MS), portMAX_DELAY); + furi_timer_start(locked_view->timer, UNLOCKED_HINT_TIMEOUT_MS); } bool desktop_view_locked_is_locked_hint_visible(DesktopViewLocked* locked_view) { diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index d323567e79..5e16f60862 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -13,14 +13,14 @@ struct DesktopMainView { View* view; DesktopMainViewCallback callback; void* context; - TimerHandle_t poweroff_timer; + FuriTimer* poweroff_timer; bool dummy_mode; }; #define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 5000 -static void desktop_main_poweroff_timer_callback(TimerHandle_t timer) { - DesktopMainView* main_view = pvTimerGetTimerID(timer); +static void desktop_main_poweroff_timer_callback(void* context) { + DesktopMainView* main_view = context; main_view->callback(DesktopMainEventOpenPowerOff, main_view->context); } @@ -90,12 +90,9 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { if(event->key == InputKeyBack) { if(event->type == InputTypePress) { - xTimerChangePeriod( - main_view->poweroff_timer, - pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT), - portMAX_DELAY); + furi_timer_start(main_view->poweroff_timer, DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT); } else if(event->type == InputTypeRelease) { - xTimerStop(main_view->poweroff_timer, portMAX_DELAY); + furi_timer_stop(main_view->poweroff_timer); } } @@ -109,12 +106,8 @@ DesktopMainView* desktop_main_alloc() { view_set_context(main_view->view, main_view); view_set_input_callback(main_view->view, desktop_main_input_callback); - main_view->poweroff_timer = xTimerCreate( - NULL, - pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT), - pdFALSE, - main_view, - desktop_main_poweroff_timer_callback); + main_view->poweroff_timer = + furi_timer_alloc(desktop_main_poweroff_timer_callback, FuriTimerTypeOnce, main_view); return main_view; } diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index 93bbffedc6..0894bb776f 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -4,7 +4,6 @@ #include #include #include -#include #include "desktop_view_pin_input.h" #include @@ -21,7 +20,7 @@ struct DesktopViewPinInput { DesktopViewPinInputCallback timeout_callback; DesktopViewPinInputDoneCallback done_callback; void* context; - TimerHandle_t timer; + FuriTimer* timer; }; typedef struct { @@ -90,7 +89,7 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) { pin_input->back_callback(pin_input->context); } - xTimerStart(pin_input->timer, 0); + furi_timer_start(pin_input->timer, NO_ACTIVITY_TIMEOUT); return true; } @@ -170,8 +169,8 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) { } } -void desktop_view_pin_input_timer_callback(TimerHandle_t timer) { - DesktopViewPinInput* pin_input = pvTimerGetTimerID(timer); +void desktop_view_pin_input_timer_callback(void* context) { + DesktopViewPinInput* pin_input = context; if(pin_input->timeout_callback) { pin_input->timeout_callback(pin_input->context); @@ -180,12 +179,12 @@ void desktop_view_pin_input_timer_callback(TimerHandle_t timer) { static void desktop_view_pin_input_enter(void* context) { DesktopViewPinInput* pin_input = context; - xTimerStart(pin_input->timer, portMAX_DELAY); + furi_timer_start(pin_input->timer, NO_ACTIVITY_TIMEOUT); } static void desktop_view_pin_input_exit(void* context) { DesktopViewPinInput* pin_input = context; - xTimerStop(pin_input->timer, portMAX_DELAY); + furi_timer_stop(pin_input->timer); } DesktopViewPinInput* desktop_view_pin_input_alloc(void) { @@ -195,12 +194,8 @@ DesktopViewPinInput* desktop_view_pin_input_alloc(void) { view_set_context(pin_input->view, pin_input); view_set_draw_callback(pin_input->view, desktop_view_pin_input_draw); view_set_input_callback(pin_input->view, desktop_view_pin_input_input); - pin_input->timer = xTimerCreate( - NULL, - pdMS_TO_TICKS(NO_ACTIVITY_TIMEOUT), - pdFALSE, - pin_input, - desktop_view_pin_input_timer_callback); + pin_input->timer = + furi_timer_alloc(desktop_view_pin_input_timer_callback, FuriTimerTypeOnce, pin_input); view_set_enter_callback(pin_input->view, desktop_view_pin_input_enter); view_set_exit_callback(pin_input->view, desktop_view_pin_input_exit); @@ -216,11 +211,7 @@ DesktopViewPinInput* desktop_view_pin_input_alloc(void) { void desktop_view_pin_input_free(DesktopViewPinInput* pin_input) { furi_assert(pin_input); - xTimerStop(pin_input->timer, portMAX_DELAY); - while(xTimerIsTimerActive(pin_input->timer)) { - furi_delay_tick(1); - } - xTimerDelete(pin_input->timer, portMAX_DELAY); + furi_timer_free(pin_input->timer); view_free(pin_input->view); free(pin_input); diff --git a/applications/services/desktop/views/desktop_view_pin_timeout.c b/applications/services/desktop/views/desktop_view_pin_timeout.c index e64c264ffd..f24ecc8ead 100644 --- a/applications/services/desktop/views/desktop_view_pin_timeout.c +++ b/applications/services/desktop/views/desktop_view_pin_timeout.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -13,7 +12,7 @@ struct DesktopViewPinTimeout { View* view; - TimerHandle_t timer; + FuriTimer* timer; DesktopViewPinTimeoutDoneCallback callback; void* context; }; @@ -32,8 +31,8 @@ void desktop_view_pin_timeout_set_callback( instance->context = context; } -static void desktop_view_pin_timeout_timer_callback(TimerHandle_t timer) { - DesktopViewPinTimeout* instance = pvTimerGetTimerID(timer); +static void desktop_view_pin_timeout_timer_callback(void* context) { + DesktopViewPinTimeout* instance = context; bool stop = false; DesktopViewPinTimeoutModel* model = view_get_model(instance->view); @@ -45,7 +44,7 @@ static void desktop_view_pin_timeout_timer_callback(TimerHandle_t timer) { view_commit_model(instance->view, true); if(stop) { - xTimerStop(instance->timer, portMAX_DELAY); + furi_timer_stop(instance->timer); instance->callback(instance->context); } } @@ -73,15 +72,15 @@ static void desktop_view_pin_timeout_draw(Canvas* canvas, void* _model) { void desktop_view_pin_timeout_free(DesktopViewPinTimeout* instance) { view_free(instance->view); - xTimerDelete(instance->timer, portMAX_DELAY); + furi_timer_free(instance->timer); free(instance); } DesktopViewPinTimeout* desktop_view_pin_timeout_alloc(void) { DesktopViewPinTimeout* instance = malloc(sizeof(DesktopViewPinTimeout)); - instance->timer = xTimerCreate( - NULL, pdMS_TO_TICKS(1000), pdTRUE, instance, desktop_view_pin_timeout_timer_callback); + instance->timer = + furi_timer_alloc(desktop_view_pin_timeout_timer_callback, FuriTimerTypePeriodic, instance); instance->view = view_alloc(); view_allocate_model(instance->view, ViewModelTypeLockFree, sizeof(DesktopViewPinTimeoutModel)); @@ -101,7 +100,7 @@ void desktop_view_pin_timeout_start(DesktopViewPinTimeout* instance, uint32_t ti model->time_left = time_left; view_commit_model(instance->view, true); - xTimerStart(instance->timer, portMAX_DELAY); + furi_timer_start(instance->timer, 1000); } View* desktop_view_pin_timeout_get_view(DesktopViewPinTimeout* instance) { diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 579b400ad0..5b526ed3a6 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -1,7 +1,6 @@ #include "dolphin/dolphin.h" #include "dolphin/helpers/dolphin_state.h" #include "dolphin_i.h" -#include "portmacro.h" #include "projdefs.h" #include #include @@ -45,8 +44,8 @@ void dolphin_flush(Dolphin* dolphin) { dolphin_event_send_wait(dolphin, &event); } -void dolphin_butthurt_timer_callback(TimerHandle_t xTimer) { - Dolphin* dolphin = pvTimerGetTimerID(xTimer); +void dolphin_butthurt_timer_callback(void* context) { + Dolphin* dolphin = context; furi_assert(dolphin); DolphinEvent event; @@ -54,8 +53,8 @@ void dolphin_butthurt_timer_callback(TimerHandle_t xTimer) { dolphin_event_send_async(dolphin, &event); } -void dolphin_flush_timer_callback(TimerHandle_t xTimer) { - Dolphin* dolphin = pvTimerGetTimerID(xTimer); +void dolphin_flush_timer_callback(void* context) { + Dolphin* dolphin = context; furi_assert(dolphin); DolphinEvent event; @@ -63,11 +62,11 @@ void dolphin_flush_timer_callback(TimerHandle_t xTimer) { dolphin_event_send_async(dolphin, &event); } -void dolphin_clear_limits_timer_callback(TimerHandle_t xTimer) { - Dolphin* dolphin = pvTimerGetTimerID(xTimer); +void dolphin_clear_limits_timer_callback(void* context) { + Dolphin* dolphin = context; furi_assert(dolphin); - xTimerChangePeriod(dolphin->clear_limits_timer, HOURS_IN_TICKS(24), portMAX_DELAY); + furi_timer_start(dolphin->clear_limits_timer, HOURS_IN_TICKS(24)); DolphinEvent event; event.type = DolphinEventTypeClearLimits; @@ -80,12 +79,12 @@ Dolphin* dolphin_alloc() { dolphin->state = dolphin_state_alloc(); dolphin->event_queue = furi_message_queue_alloc(8, sizeof(DolphinEvent)); dolphin->pubsub = furi_pubsub_alloc(); - dolphin->butthurt_timer = xTimerCreate( - NULL, HOURS_IN_TICKS(2 * 24), pdTRUE, dolphin, dolphin_butthurt_timer_callback); + dolphin->butthurt_timer = + furi_timer_alloc(dolphin_butthurt_timer_callback, FuriTimerTypePeriodic, dolphin); dolphin->flush_timer = - xTimerCreate(NULL, 30 * 1000, pdFALSE, dolphin, dolphin_flush_timer_callback); - dolphin->clear_limits_timer = xTimerCreate( - NULL, HOURS_IN_TICKS(24), pdTRUE, dolphin, dolphin_clear_limits_timer_callback); + furi_timer_alloc(dolphin_flush_timer_callback, FuriTimerTypeOnce, dolphin); + dolphin->clear_limits_timer = + furi_timer_alloc(dolphin_clear_limits_timer_callback, FuriTimerTypePeriodic, dolphin); return dolphin; } @@ -125,14 +124,14 @@ FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) { static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { furi_assert(dolphin); - TickType_t now_ticks = xTaskGetTickCount(); - TickType_t timer_expires_at = xTimerGetExpiryTime(dolphin->clear_limits_timer); + uint32_t now_ticks = furi_get_tick(); + uint32_t timer_expires_at = furi_timer_get_expire_time(dolphin->clear_limits_timer); if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) { FuriHalRtcDateTime date; furi_hal_rtc_get_datetime(&date); - TickType_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000; - TickType_t time_to_clear_limits = 0; + uint32_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000; + uint32_t time_to_clear_limits = 0; if(date.hour < 5) { time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms; @@ -140,7 +139,7 @@ static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { time_to_clear_limits = HOURS_IN_TICKS(24 + 5) - now_time_in_ms; } - xTimerChangePeriod(dolphin->clear_limits_timer, time_to_clear_limits, portMAX_DELAY); + furi_timer_start(dolphin->clear_limits_timer, time_to_clear_limits); } } @@ -156,9 +155,9 @@ int32_t dolphin_srv(void* p) { furi_record_create(RECORD_DOLPHIN, dolphin); dolphin_state_load(dolphin->state); - xTimerReset(dolphin->butthurt_timer, portMAX_DELAY); + furi_timer_stop(dolphin->butthurt_timer); dolphin_update_clear_limits_timer_period(dolphin); - xTimerReset(dolphin->clear_limits_timer, portMAX_DELAY); + furi_timer_stop(dolphin->clear_limits_timer); DolphinEvent event; while(1) { @@ -168,8 +167,8 @@ int32_t dolphin_srv(void* p) { dolphin_state_on_deed(dolphin->state, event.deed); DolphinPubsubEvent event = DolphinPubsubEventUpdate; furi_pubsub_publish(dolphin->pubsub, &event); - xTimerReset(dolphin->butthurt_timer, portMAX_DELAY); - xTimerReset(dolphin->flush_timer, portMAX_DELAY); + furi_timer_restart(dolphin->butthurt_timer); + furi_timer_restart(dolphin->flush_timer); } else if(event.type == DolphinEventTypeStats) { event.stats->icounter = dolphin->state->data.icounter; event.stats->butthurt = dolphin->state->data.butthurt; diff --git a/applications/services/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h index ceeff1e1a9..2d716c1813 100644 --- a/applications/services/dolphin/dolphin_i.h +++ b/applications/services/dolphin/dolphin_i.h @@ -30,9 +30,9 @@ struct Dolphin { // Queue FuriMessageQueue* event_queue; FuriPubSub* pubsub; - TimerHandle_t butthurt_timer; - TimerHandle_t flush_timer; - TimerHandle_t clear_limits_timer; + FuriTimer* butthurt_timer; + FuriTimer* flush_timer; + FuriTimer* clear_limits_timer; }; Dolphin* dolphin_alloc(); diff --git a/applications/services/gui/icon_animation.c b/applications/services/gui/icon_animation.c index b63d233f3d..a39ef2e254 100644 --- a/applications/services/gui/icon_animation.c +++ b/applications/services/gui/icon_animation.c @@ -15,7 +15,6 @@ IconAnimation* icon_animation_alloc(const Icon* icon) { void icon_animation_free(IconAnimation* instance) { furi_assert(instance); icon_animation_stop(instance); - while(xTimerIsTimerActive(instance->timer) == pdTRUE) furi_delay_tick(1); furi_timer_free(instance->timer); free(instance); } @@ -67,10 +66,9 @@ void icon_animation_start(IconAnimation* instance) { instance->animating = true; furi_assert(instance->icon->frame_rate); furi_check( - xTimerChangePeriod( + furi_timer_start( instance->timer, - (furi_kernel_get_tick_frequency() / instance->icon->frame_rate), - portMAX_DELAY) == pdPASS); + (furi_kernel_get_tick_frequency() / instance->icon->frame_rate)) == FuriStatusOk); } } @@ -78,7 +76,7 @@ void icon_animation_stop(IconAnimation* instance) { furi_assert(instance); if(instance->animating) { instance->animating = false; - furi_check(xTimerStop(instance->timer, portMAX_DELAY) == pdPASS); + furi_timer_stop(instance->timer); instance->frame = 0; } } diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 8da0a34003..216aa39b2e 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -6,20 +6,6 @@ static Input* input = NULL; -inline static void input_timer_start(FuriTimer* timer_id, uint32_t ticks) { - TimerHandle_t hTimer = (TimerHandle_t)timer_id; - furi_check(xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS); -} - -inline static void input_timer_stop(FuriTimer* timer_id) { - TimerHandle_t hTimer = (TimerHandle_t)timer_id; - furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - // xTimerStop is not actually stopping timer, - // Instead it places stop event into timer queue - // This code ensures that timer is stopped - while(xTimerIsTimerActive(hTimer) == pdTRUE) furi_delay_tick(1); -} - void input_press_timer_callback(void* arg) { InputPinState* input_pin = arg; InputEvent event; @@ -123,10 +109,12 @@ int32_t input_srv(void* p) { input->counter++; input->pin_states[i].counter = input->counter; event.sequence_counter = input->pin_states[i].counter; - input_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS); + furi_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS); } else { event.sequence_counter = input->pin_states[i].counter; - input_timer_stop(input->pin_states[i].press_timer); + furi_timer_stop(input->pin_states[i].press_timer); + while(furi_timer_is_running(input->pin_states[i].press_timer)) + furi_delay_tick(1); if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { event.type = InputTypeShort; furi_pubsub_publish(input->event_pubsub, &event); diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 3e9665ad8d..826f222537 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -6,7 +6,6 @@ #include #include -#include #include @@ -162,7 +161,7 @@ void rpc_session_set_terminated_callback( * odd: client sends close request and sends command after. */ size_t - rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { + rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, uint32_t timeout) { furi_assert(session); furi_assert(encoded_bytes); diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index d11fdc1624..863bca355b 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -124,7 +124,7 @@ void rpc_session_set_terminated_callback( * * @return actually consumed bytes */ -size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickType_t timeout); +size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, uint32_t timeout); /** Get available size of RPC buffer * diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index f1c139b5f9..4612752a83 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -2,7 +2,6 @@ #include #include #include -#include #define TAG "RpcCli" diff --git a/applications/system/updater/updater.c b/applications/system/updater/updater.c index e749f3ce6e..4c7fd29e9c 100644 --- a/applications/system/updater/updater.c +++ b/applications/system/updater/updater.c @@ -5,7 +5,6 @@ #include #include #include -#include #include static bool updater_custom_event_callback(void* context, uint32_t event) { diff --git a/furi/core/base.h b/furi/core/base.h index 29e8741920..642ff2b6cd 100644 --- a/furi/core/base.h +++ b/furi/core/base.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { diff --git a/furi/core/check.c b/furi/core/check.c index ea1de71425..b56db65637 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -55,8 +55,6 @@ PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[13] = {0}; : "memory"); extern size_t xPortGetTotalHeapSize(void); -extern size_t xPortGetFreeHeapSize(void); -extern size_t xPortGetMinimumEverFreeHeapSize(void); static void __furi_put_uint32_as_text(uint32_t data) { char tmp_str[] = "-2147483648"; diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 5bd218d357..2b30c3b06d 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -2,8 +2,6 @@ #include "core_defines.h" #include -#include -#include #ifdef __cplusplus extern "C" { diff --git a/furi/core/critical.c b/furi/core/critical.c index 57fe2403be..3bef2be38e 100644 --- a/furi/core/critical.c +++ b/furi/core/critical.c @@ -1,5 +1,8 @@ #include "common_defines.h" +#include +#include + __FuriCriticalInfo __furi_critical_enter(void) { __FuriCriticalInfo info; diff --git a/furi/core/event_flag.c b/furi/core/event_flag.c index 9406a581f7..96b9591877 100644 --- a/furi/core/event_flag.c +++ b/furi/core/event_flag.c @@ -2,6 +2,7 @@ #include "common_defines.h" #include "check.h" +#include #include #define FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U diff --git a/furi/core/kernel.c b/furi/core/kernel.c index 7928ad11c3..89a50a9b52 100644 --- a/furi/core/kernel.c +++ b/furi/core/kernel.c @@ -5,6 +5,9 @@ #include +#include +#include + #include CMSIS_device_header bool furi_kernel_is_irq_or_masked() { @@ -31,6 +34,10 @@ bool furi_kernel_is_irq_or_masked() { return (irq); } +bool furi_kernel_is_running() { + return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING; +} + int32_t furi_kernel_lock() { furi_assert(!furi_kernel_is_irq_or_masked()); diff --git a/furi/core/kernel.h b/furi/core/kernel.h index 371f76c1f7..c962402efd 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -27,6 +27,12 @@ extern "C" { */ bool furi_kernel_is_irq_or_masked(); +/** Check if kernel is running + * + * @return true if running, false otherwise + */ +bool furi_kernel_is_running(); + /** Lock kernel, pause process scheduling * * @warning This should never be called in interrupt request context. diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index b8baf9c7c6..a3e127c3c1 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -47,8 +47,8 @@ all the API functions to use the MPU wrappers. That should only be done when task.h is included from an application file. */ #define MPU_WRAPPERS_INCLUDED_FROM_API_FILE -#include "FreeRTOS.h" -#include "task.h" +#include +#include #undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE diff --git a/furi/core/message_queue.c b/furi/core/message_queue.c index ddf56f0064..e20fa420a0 100644 --- a/furi/core/message_queue.c +++ b/furi/core/message_queue.c @@ -1,8 +1,9 @@ #include "kernel.h" #include "message_queue.h" +#include "check.h" + #include #include -#include "check.h" FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) { furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (msg_count > 0U) && (msg_size > 0U)); diff --git a/furi/core/mutex.c b/furi/core/mutex.c index 9fb964a1e5..8794e10dc3 100644 --- a/furi/core/mutex.c +++ b/furi/core/mutex.c @@ -2,6 +2,7 @@ #include "check.h" #include "common_defines.h" +#include #include FuriMutex* furi_mutex_alloc(FuriMutexType type) { diff --git a/furi/core/semaphore.c b/furi/core/semaphore.c index 8c99bfc541..1f1a07780c 100644 --- a/furi/core/semaphore.c +++ b/furi/core/semaphore.c @@ -2,6 +2,7 @@ #include "check.h" #include "common_defines.h" +#include #include FuriSemaphore* furi_semaphore_alloc(uint32_t max_count, uint32_t initial_count) { diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index bf483948be..a13d256b11 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -2,6 +2,7 @@ #include "check.h" #include "stream_buffer.h" #include "common_defines.h" + #include #include diff --git a/furi/core/thread.c b/furi/core/thread.c index de50bde7a4..db4feeb4e1 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -7,11 +7,13 @@ #include "mutex.h" #include "string.h" -#include #include "log.h" #include #include +#include +#include + #define TAG "FuriThread" #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers diff --git a/furi/core/thread.h b/furi/core/thread.h index 692f2a1008..44d66fb21a 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -8,6 +8,9 @@ #include "base.h" #include "common_defines.h" +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -28,7 +31,8 @@ typedef enum { FuriThreadPriorityNormal = 16, /**< Normal */ FuriThreadPriorityHigh = 17, /**< High */ FuriThreadPriorityHighest = 18, /**< Highest */ - FuriThreadPriorityIsr = (configMAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */ + FuriThreadPriorityIsr = + (FURI_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */ } FuriThreadPriority; /** FuriThread anonymous structure */ diff --git a/furi/core/timer.c b/furi/core/timer.c index 7743ffe701..0a89b89201 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -97,6 +97,23 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { return (stat); } +FuriStatus furi_timer_restart(FuriTimer* instance) { + furi_assert(!furi_kernel_is_irq_or_masked()); + furi_assert(instance); + + TimerHandle_t hTimer = (TimerHandle_t)instance; + FuriStatus stat; + + if(xTimerReset(hTimer, portMAX_DELAY) == pdPASS) { + stat = FuriStatusOk; + } else { + stat = FuriStatusErrorResource; + } + + /* Return execution status */ + return (stat); +} + FuriStatus furi_timer_stop(FuriTimer* instance) { furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); @@ -125,6 +142,15 @@ uint32_t furi_timer_is_running(FuriTimer* instance) { return (uint32_t)xTimerIsTimerActive(hTimer); } +uint32_t furi_timer_get_expire_time(FuriTimer* instance) { + furi_assert(!furi_kernel_is_irq_or_masked()); + furi_assert(instance); + + TimerHandle_t hTimer = (TimerHandle_t)instance; + + return (uint32_t)xTimerGetExpiryTime(hTimer); +} + void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg) { BaseType_t ret = pdFAIL; if(furi_kernel_is_irq_or_masked()) { @@ -133,4 +159,17 @@ void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context ret = xTimerPendFunctionCall(callback, context, arg, FuriWaitForever); } furi_check(ret == pdPASS); +} + +void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) { + furi_assert(!furi_kernel_is_irq_or_masked()); + TaskHandle_t task_handle = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(priority == FuriTimerThreadPriorityNormal) { + vTaskPrioritySet(task_handle, configTIMER_TASK_PRIORITY); + } else if(priority == FuriTimerThreadPriorityElevated) { + vTaskPrioritySet(task_handle, configMAX_PRIORITIES - 1); + } else { + furi_crash(); + } } \ No newline at end of file diff --git a/furi/core/timer.h b/furi/core/timer.h index 3f43de5fd9..47b44c71a6 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -40,6 +40,14 @@ void furi_timer_free(FuriTimer* instance); */ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); +/** Restart timer with previous timeout value + * + * @param instance The pointer to FuriTimer instance + * + * @return The furi status. + */ +FuriStatus furi_timer_restart(FuriTimer* instance); + /** Stop timer * * @param instance The pointer to FuriTimer instance @@ -56,10 +64,29 @@ FuriStatus furi_timer_stop(FuriTimer* instance); */ uint32_t furi_timer_is_running(FuriTimer* instance); +/** Get timer expire time + * + * @param instance The Timer instance + * + * @return expire tick + */ +uint32_t furi_timer_get_expire_time(FuriTimer* instance); + typedef void (*FuriTimerPendigCallback)(void* context, uint32_t arg); void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg); +typedef enum { + FuriTimerThreadPriorityNormal, /**< Lower then other threads */ + FuriTimerThreadPriorityElevated, /**< Same as other threads */ +} FuriTimerThreadPriority; + +/** Set Timer thread priority + * + * @param[in] priority The priority + */ +void furi_timer_set_thread_priority(FuriTimerThreadPriority priority); + #ifdef __cplusplus } #endif diff --git a/furi/flipper.c b/furi/flipper.c index 8806ce27fb..b29424a9f4 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -5,6 +5,8 @@ #include #include +#include + #define TAG "Flipper" static void flipper_print_version(const char* target, const Version* version) { diff --git a/furi/furi.c b/furi/furi.c index cc0e3f4f12..6247e259fb 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -1,6 +1,8 @@ #include "furi.h" #include -#include "queue.h" + +#include +#include void furi_init() { furi_assert(!furi_kernel_is_irq_or_masked()); diff --git a/furi/furi.h b/furi/furi.h index b1299c9a95..422509055f 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -21,9 +21,6 @@ #include -// FreeRTOS timer, REMOVE AFTER REFACTORING -#include - // Workaround for math.h leaking through HAL in older versions #include diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 5e3257e260..38392fc06e 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -165,7 +165,7 @@ static int32_t infrared_worker_rx_thread(void* thread_context) { InfraredWorker* instance = thread_context; uint32_t events = 0; LevelDuration level_duration; - TickType_t last_blink_time = 0; + uint32_t last_blink_time = 0; while(1) { events = furi_thread_flags_wait(INFRARED_WORKER_ALL_RX_EVENTS, 0, FuriWaitForever); @@ -173,8 +173,8 @@ static int32_t infrared_worker_rx_thread(void* thread_context) { if(events & INFRARED_WORKER_RX_RECEIVED) { if(!instance->rx.overrun && instance->blink_enable && - ((xTaskGetTickCount() - last_blink_time) > 80)) { - last_blink_time = xTaskGetTickCount(); + ((furi_get_tick() - last_blink_time) > 80)) { + last_blink_time = furi_get_tick(); notification_message(instance->notification, &sequence_blink_blue_10); } if(instance->signal.timings_cnt == 0) diff --git a/lib/lfrfid/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c index cbc7b02e37..ffaa8ee925 100644 --- a/lib/lfrfid/lfrfid_worker.c +++ b/lib/lfrfid/lfrfid_worker.c @@ -1,7 +1,7 @@ +#include "lfrfid_worker_i.h" + #include #include -#include -#include "lfrfid_worker_i.h" typedef enum { LFRFIDEventStopThread = (1 << 0), diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h index 1113d381c2..ce878cb40a 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h @@ -43,8 +43,6 @@ struct Iso14443_4aPoller { void* context; }; -Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); - const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance); Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h index 4df00adcf1..bd55c61882 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h @@ -40,8 +40,6 @@ struct Iso14443_4bPoller { void* context; }; -Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error); - const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance); Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); diff --git a/lib/print/wrappers.h b/lib/print/wrappers.h index b6f0f004b6..7c0d1f92eb 100644 --- a/lib/print/wrappers.h +++ b/lib/print/wrappers.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -6,7 +8,6 @@ extern "C" { #endif -void _putchar(char character); int __wrap_printf(const char* format, ...); int __wrap_vsnprintf(char* str, size_t size, const char* format, va_list args); int __wrap_puts(const char* str); diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 82c925c1a3..35850aa83e 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -12,6 +12,7 @@ env.Append( File("subghz_tx_rx_worker.h"), File("transmitter.h"), File("protocols/raw.h"), + File("protocols/public_api.h"), File("blocks/const.h"), File("blocks/decoder.h"), File("blocks/encoder.h"), diff --git a/lib/subghz/protocols/bin_raw.h b/lib/subghz/protocols/bin_raw.h index 82775e5759..26cc6ec3a2 100644 --- a/lib/subghz/protocols/bin_raw.h +++ b/lib/subghz/protocols/bin_raw.h @@ -1,6 +1,7 @@ #pragma once #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_BIN_RAW_NAME "BinRAW" @@ -80,10 +81,6 @@ void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t du */ uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context); -void subghz_protocol_decoder_bin_raw_data_input_rssi( - SubGhzProtocolDecoderBinRAW* instance, - float rssi); - /** * Serialize data SubGhzProtocolDecoderBinRAW. * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index 59cd9cf986..4abd14413b 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -1,6 +1,7 @@ #pragma once #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_KEELOQ_NAME "KeeLoq" @@ -24,26 +25,6 @@ void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment); */ void subghz_protocol_encoder_keeloq_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 28 bit - * @param btn Button number, 4 bit - * @param cnt Container value, 16 bit - * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_keeloq_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - const char* manufacture_name, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h new file mode 100644 index 0000000000..174f175cdf --- /dev/null +++ b/lib/subghz/protocols/public_api.h @@ -0,0 +1,63 @@ +#pragma once + +#include "../types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 32 bit + * @param btn Button number, 8 bit + * @param cnt Container value, 28 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Container value, 16 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi); + +/** + * Validation of fixed parts SubGhzProtocolDecoderSecPlus_v1. + * @param fixed fixed parts + * @return true On success + */ +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index 3490f2ca54..e01f8bcdaa 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -1,5 +1,7 @@ #pragma once + #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_SECPLUS_V1_NAME "Security+ 1.0" @@ -100,13 +102,6 @@ SubGhzProtocolStatus subghz_protocol_decoder_secplus_v1_serialize( SubGhzProtocolStatus subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); -/** - * Validation of fixed parts SubGhzProtocolDecoderSecPlus_v1. - * @param fixed fixed parts - * @return true On success - */ -bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); - /** * Getting a textual representation of the received data. * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index 0eea732af1..9eb912a27e 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -1,5 +1,7 @@ #pragma once + #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_SECPLUS_V2_NAME "Security+ 2.0" @@ -45,25 +47,6 @@ void subghz_protocol_encoder_secplus_v2_stop(void* context); */ LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 32 bit - * @param btn Button number, 8 bit - * @param cnt Container value, 28 bit - * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_secplus_v2_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint32_t cnt, - SubGhzRadioPreset* preset); - /** * Allocate SubGhzProtocolDecoderSecPlus_v2. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/subghz_protocol_registry.h b/lib/subghz/subghz_protocol_registry.h index 8e80071b51..2dfa9ce8b6 100644 --- a/lib/subghz/subghz_protocol_registry.h +++ b/lib/subghz/subghz_protocol_registry.h @@ -10,29 +10,6 @@ extern const SubGhzProtocolRegistry subghz_protocol_registry; typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; -bool subghz_protocol_secplus_v2_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint32_t cnt, - SubGhzRadioPreset* preset); - -bool subghz_protocol_keeloq_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - const char* manufacture_name, - SubGhzRadioPreset* preset); - -void subghz_protocol_decoder_bin_raw_data_input_rssi( - SubGhzProtocolDecoderBinRAW* instance, - float rssi); - -bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); - #ifdef __cplusplus } #endif diff --git a/lib/toolbox/buffer_stream.c b/lib/toolbox/buffer_stream.c index 37b2514ef3..e8cb334d51 100644 --- a/lib/toolbox/buffer_stream.c +++ b/lib/toolbox/buffer_stream.c @@ -112,7 +112,7 @@ bool buffer_stream_send_from_isr(BufferStream* buffer_stream, const uint8_t* dat return result; } -Buffer* buffer_stream_receive(BufferStream* buffer_stream, TickType_t timeout) { +Buffer* buffer_stream_receive(BufferStream* buffer_stream, uint32_t timeout) { Buffer* buffer; size_t size = furi_stream_buffer_receive(buffer_stream->stream, &buffer, sizeof(Buffer*), timeout); diff --git a/lib/toolbox/buffer_stream.h b/lib/toolbox/buffer_stream.h index 9db5477532..5c3dc0a340 100644 --- a/lib/toolbox/buffer_stream.h +++ b/lib/toolbox/buffer_stream.h @@ -69,7 +69,7 @@ bool buffer_stream_send_from_isr(BufferStream* buffer_stream, const uint8_t* dat * @param timeout * @return Buffer* */ -Buffer* buffer_stream_receive(BufferStream* buffer_stream, TickType_t timeout); +Buffer* buffer_stream_receive(BufferStream* buffer_stream, uint32_t timeout); /** * @brief Get stream overrun count diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index cb62b40c40..3a2abc9b65 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,41.0,, +Version,+,43.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -417,7 +417,6 @@ Function,-,_perror_r,void,"_reent*, const char*" Function,-,_printf_r,int,"_reent*, const char*, ..." Function,-,_putc_r,int,"_reent*, int, FILE*" Function,-,_putc_unlocked_r,int,"_reent*, int, FILE*" -Function,-,_putchar,void,char Function,-,_putchar_r,int,"_reent*, int" Function,-,_putchar_unlocked_r,int,"_reent*, int" Function,-,_putenv_r,int,"_reent*, char*" @@ -751,8 +750,6 @@ Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, Function,-,drem,double,"double, double" Function,-,dremf,float,"float, float" -Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, -Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" @@ -1342,6 +1339,7 @@ Function,+,furi_hal_vibro_on,void,_Bool Function,-,furi_init,void, Function,+,furi_kernel_get_tick_frequency,uint32_t, Function,+,furi_kernel_is_irq_or_masked,_Bool, +Function,+,furi_kernel_is_running,_Bool, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, @@ -1452,7 +1450,6 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" -Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -1493,8 +1490,11 @@ Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" Function,+,furi_timer_free,void,FuriTimer* +Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" +Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" @@ -1838,8 +1838,6 @@ Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" Function,+,pb_release,void,"const pb_msgdesc_t*, void*" Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" -Function,-,pcTaskGetName,char*,TaskHandle_t -Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -1914,12 +1912,6 @@ Function,-,putchar_unlocked,int,int Function,-,putenv,int,char* Function,-,puts,int,const char* Function,-,putw,int,"int, FILE*" -Function,-,pvPortCalloc,void*,"size_t, size_t" -Function,-,pvPortMalloc,void*,size_t -Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" -Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, -Function,-,pvTimerGetTimerID,void*,const TimerHandle_t -Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" Function,-,quick_exit,void,int @@ -1949,7 +1941,7 @@ Function,-,round,double,double Function,+,roundf,float,float Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* -Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" +Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, uint32_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* Function,+,rpc_session_get_owner,RpcOwner,RpcSession* Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" @@ -2281,67 +2273,10 @@ Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" -Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" -Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" -Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, -Function,-,ulTaskGetIdleRunTimePercent,uint32_t, Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,-,uxListRemove,UBaseType_t,ListItem_t* -Function,-,uxTaskGetNumberOfTasks,UBaseType_t, -Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t -Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t -Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" -Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t -Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t -Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t -Function,-,uxTaskResetEventItemValue,TickType_t, -Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t -Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t -Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vListInitialise,void,List_t* -Function,-,vListInitialiseItem,void,ListItem_t* -Function,-,vListInsert,void,"List_t*, ListItem_t*" -Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" -Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* -Function,-,vPortEndScheduler,void, -Function,+,vPortEnterCritical,void, -Function,+,vPortExitCritical,void, -Function,-,vPortFree,void,void* -Function,-,vPortGetHeapStats,void,HeapStats_t* -Function,-,vPortInitialiseBlocks,void, -Function,-,vPortSuppressTicksAndSleep,void,TickType_t -Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" -Function,-,vTaskDelay,void,const TickType_t -Function,-,vTaskDelete,void,TaskHandle_t -Function,-,vTaskEndScheduler,void, -Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" -Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" -Function,-,vTaskGetRunTimeStats,void,char* -Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* -Function,-,vTaskList,void,char* -Function,-,vTaskMissedYield,void, -Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" -Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" -Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" -Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" -Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" -Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" -Function,-,vTaskResume,void,TaskHandle_t -Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" -Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" -Function,-,vTaskSetTimeOutState,void,TimeOut_t* -Function,-,vTaskStartScheduler,void, -Function,-,vTaskStepTick,void,TickType_t -Function,-,vTaskSuspend,void,TaskHandle_t -Function,-,vTaskSuspendAll,void, -Function,-,vTaskSwitchContext,void, -Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" -Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" -Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* @@ -2456,43 +2391,6 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* -Function,-,xPortGetFreeHeapSize,size_t, -Function,-,xPortGetMinimumEverFreeHeapSize,size_t, -Function,-,xPortStartScheduler,BaseType_t, -Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t -Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" -Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t -Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" -Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" -Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" -Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" -Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" -Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" -Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" -Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" -Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, -Function,+,xTaskGetHandle,TaskHandle_t,const char* -Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, -Function,+,xTaskGetSchedulerState,BaseType_t, -Function,+,xTaskGetTickCount,TickType_t, -Function,-,xTaskGetTickCountFromISR,TickType_t, -Function,-,xTaskIncrementTick,BaseType_t, -Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t -Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t -Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* -Function,-,xTaskResumeAll,BaseType_t, -Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t -Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" -Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" -Function,-,xTimerCreateTimerTask,BaseType_t, -Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" -Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t -Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t -Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t -Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, -Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t -Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" -Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" Function,-,y0,double,double Function,-,y0f,float,float Function,-,y1,double,double diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9d9c1a01b3..0f81f11911 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,41.0,, +Version,+,43.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -179,6 +179,7 @@ Header,+,lib/subghz/blocks/math.h,, Header,+,lib/subghz/devices/cc1101_configs.h,, Header,+,lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h,, Header,+,lib/subghz/environment.h,, +Header,+,lib/subghz/protocols/public_api.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/registry.h,, @@ -485,7 +486,6 @@ Function,-,_perror_r,void,"_reent*, const char*" Function,-,_printf_r,int,"_reent*, const char*, ..." Function,-,_putc_r,int,"_reent*, int, FILE*" Function,-,_putc_unlocked_r,int,"_reent*, int, FILE*" -Function,-,_putchar,void,char Function,-,_putchar_r,int,"_reent*, int" Function,-,_putchar_unlocked_r,int,"_reent*, int" Function,-,_putenv_r,int,"_reent*, char*" @@ -839,8 +839,6 @@ Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, Function,-,drem,double,"double, double" Function,-,dremf,float,"float, float" -Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, -Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" @@ -1536,6 +1534,7 @@ Function,+,furi_hal_vibro_on,void,_Bool Function,-,furi_init,void, Function,+,furi_kernel_get_tick_frequency,uint32_t, Function,+,furi_kernel_is_irq_or_masked,_Bool, +Function,+,furi_kernel_is_running,_Bool, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, @@ -1646,7 +1645,6 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" -Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -1687,8 +1685,11 @@ Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" Function,+,furi_timer_free,void,FuriTimer* +Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" +Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" @@ -2380,8 +2381,6 @@ Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" Function,+,pb_release,void,"const pb_msgdesc_t*, void*" Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" -Function,-,pcTaskGetName,char*,TaskHandle_t -Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -2456,12 +2455,6 @@ Function,-,putchar_unlocked,int,int Function,-,putenv,int,char* Function,-,puts,int,const char* Function,-,putw,int,"int, FILE*" -Function,-,pvPortCalloc,void*,"size_t, size_t" -Function,-,pvPortMalloc,void*,size_t -Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" -Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, -Function,-,pvTimerGetTimerID,void*,const TimerHandle_t -Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" Function,-,quick_exit,void,int @@ -2491,7 +2484,7 @@ Function,-,round,double,double Function,+,roundf,float,float Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* -Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" +Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, uint32_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* Function,+,rpc_session_get_owner,RpcOwner,RpcSession* Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" @@ -3011,67 +3004,10 @@ Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" -Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" -Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" -Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, -Function,-,ulTaskGetIdleRunTimePercent,uint32_t, Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,-,uxListRemove,UBaseType_t,ListItem_t* -Function,-,uxTaskGetNumberOfTasks,UBaseType_t, -Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t -Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t -Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" -Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t -Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t -Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t -Function,-,uxTaskResetEventItemValue,TickType_t, -Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t -Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t -Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vListInitialise,void,List_t* -Function,-,vListInitialiseItem,void,ListItem_t* -Function,-,vListInsert,void,"List_t*, ListItem_t*" -Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" -Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* -Function,-,vPortEndScheduler,void, -Function,+,vPortEnterCritical,void, -Function,+,vPortExitCritical,void, -Function,-,vPortFree,void,void* -Function,-,vPortGetHeapStats,void,HeapStats_t* -Function,-,vPortInitialiseBlocks,void, -Function,-,vPortSuppressTicksAndSleep,void,TickType_t -Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" -Function,-,vTaskDelay,void,const TickType_t -Function,-,vTaskDelete,void,TaskHandle_t -Function,-,vTaskEndScheduler,void, -Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" -Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" -Function,-,vTaskGetRunTimeStats,void,char* -Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* -Function,-,vTaskList,void,char* -Function,-,vTaskMissedYield,void, -Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" -Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" -Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" -Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" -Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" -Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" -Function,-,vTaskResume,void,TaskHandle_t -Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" -Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" -Function,-,vTaskSetTimeOutState,void,TimeOut_t* -Function,-,vTaskStartScheduler,void, -Function,-,vTaskStepTick,void,TickType_t -Function,-,vTaskSuspend,void,TaskHandle_t -Function,-,vTaskSuspendAll,void, -Function,-,vTaskSwitchContext,void, -Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" -Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" -Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* @@ -3186,43 +3122,6 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* -Function,-,xPortGetFreeHeapSize,size_t, -Function,-,xPortGetMinimumEverFreeHeapSize,size_t, -Function,-,xPortStartScheduler,BaseType_t, -Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t -Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" -Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t -Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" -Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" -Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" -Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" -Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" -Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" -Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" -Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" -Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, -Function,+,xTaskGetHandle,TaskHandle_t,const char* -Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, -Function,+,xTaskGetSchedulerState,BaseType_t, -Function,+,xTaskGetTickCount,TickType_t, -Function,-,xTaskGetTickCountFromISR,TickType_t, -Function,-,xTaskIncrementTick,BaseType_t, -Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t -Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t -Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* -Function,-,xTaskResumeAll,BaseType_t, -Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t -Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" -Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" -Function,-,xTimerCreateTimerTask,BaseType_t, -Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" -Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t -Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t -Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t -Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, -Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t -Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" -Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" Function,-,y0,double,double Function,-,y0f,float,float Function,-,y1,double,double diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 360c1f6b68..f0533567ea 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -532,8 +532,6 @@ void gap_thread_stop() { // Free resources furi_mutex_free(gap->state_mutex); furi_message_queue_free(gap->command_queue); - furi_timer_stop(gap->advertise_timer); - while(xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) furi_delay_tick(1); furi_timer_free(gap->advertise_timer); free(gap); gap = NULL; diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index bc65b29eb6..284d48bf53 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -11,6 +11,9 @@ #include +#include +#include + #define TAG "FuriHalFlash" #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" diff --git a/targets/f7/furi_hal/furi_hal_os.c b/targets/f7/furi_hal/furi_hal_os.c index 046cf79dc1..ea835b95fb 100644 --- a/targets/f7/furi_hal/furi_hal_os.c +++ b/targets/f7/furi_hal/furi_hal_os.c @@ -10,6 +10,9 @@ #include +#include +#include + #define TAG "FuriHalOs" #define FURI_HAL_IDLE_TIMER_CLK_HZ 32768 diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index 0eb93e664b..119dee81f9 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -459,10 +459,10 @@ void furi_hal_power_disable_external_3_3v() { } void furi_hal_power_suppress_charge_enter() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); bool disable_charging = furi_hal_power.suppress_charge == 0; furi_hal_power.suppress_charge++; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); if(disable_charging) { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -472,10 +472,10 @@ void furi_hal_power_suppress_charge_enter() { } void furi_hal_power_suppress_charge_exit() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); furi_hal_power.suppress_charge--; bool enable_charging = furi_hal_power.suppress_charge == 0; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); if(enable_charging) { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index 5c33760ea1..98ca71af35 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -200,7 +200,7 @@ bool furi_hal_spi_bus_trx_dma( furi_assert(size > 0); // If scheduler is not running, use blocking mode - if(xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) { + if(furi_kernel_is_running()) { return furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, size, timeout_ms); } diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 024d43a6de..3bc57f8f3b 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -3,13 +3,14 @@ #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include #pragma GCC diagnostic ignored "-Wredundant-decls" -extern uint32_t SystemCoreClock; #endif #ifndef CMSIS_device_header #define CMSIS_device_header "stm32wbxx.h" #endif /* CMSIS_device_header */ +#include CMSIS_device_header + #define configENABLE_FPU 1 #define configENABLE_MPU 0 diff --git a/targets/f7/inc/furi_config.h b/targets/f7/inc/furi_config.h new file mode 100644 index 0000000000..c935611ab7 --- /dev/null +++ b/targets/f7/inc/furi_config.h @@ -0,0 +1,3 @@ +#pragma once + +#define FURI_CONFIG_THREAD_MAX_PRIORITIES (32) \ No newline at end of file diff --git a/targets/f7/src/main.c b/targets/f7/src/main.c index 2c353f52b2..ca705fe5e4 100644 --- a/targets/f7/src/main.c +++ b/targets/f7/src/main.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #define TAG "Main" From 47cc05dab4beee6c1a7620c45b15abfb48b9916e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 2 Nov 2023 00:23:02 +0900 Subject: [PATCH 018/111] [FL-3655] Dolphin: Extreme butthurt loop fix (#3184) * Furi: change timer restart function signature, explicitly require timer time. Dolphin: fix incorrect timer usage caused by refactoring. * Format Sources * Furi: update timer documentation --- applications/services/dolphin/dolphin.c | 8 ++++---- furi/core/timer.c | 13 +++++++++---- furi/core/timer.h | 5 +++-- targets/f18/api_symbols.csv | 4 ++-- targets/f7/api_symbols.csv | 4 ++-- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 5b526ed3a6..bef7c4a285 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -155,9 +155,9 @@ int32_t dolphin_srv(void* p) { furi_record_create(RECORD_DOLPHIN, dolphin); dolphin_state_load(dolphin->state); - furi_timer_stop(dolphin->butthurt_timer); + furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24)); dolphin_update_clear_limits_timer_period(dolphin); - furi_timer_stop(dolphin->clear_limits_timer); + furi_timer_restart(dolphin->clear_limits_timer, HOURS_IN_TICKS(24)); DolphinEvent event; while(1) { @@ -167,8 +167,8 @@ int32_t dolphin_srv(void* p) { dolphin_state_on_deed(dolphin->state, event.deed); DolphinPubsubEvent event = DolphinPubsubEventUpdate; furi_pubsub_publish(dolphin->pubsub, &event); - furi_timer_restart(dolphin->butthurt_timer); - furi_timer_restart(dolphin->flush_timer); + furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24)); + furi_timer_restart(dolphin->flush_timer, 30 * 1000); } else if(event.type == DolphinEventTypeStats) { event.stats->icounter = dolphin->state->data.icounter; event.stats->butthurt = dolphin->state->data.butthurt; diff --git a/furi/core/timer.c b/furi/core/timer.c index 0a89b89201..17347e5c7d 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -51,7 +51,7 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co callb = (TimerCallback_t*)((uint32_t)callb | 1U); // TimerCallback function is always provided as a callback and is used to call application // specified function with its context both stored in structure callb. - hTimer = xTimerCreate(NULL, 1, reload, callb, TimerCallback); + hTimer = xTimerCreate(NULL, portMAX_DELAY, reload, callb, TimerCallback); furi_check(hTimer); /* Return timer ID */ @@ -83,6 +83,7 @@ void furi_timer_free(FuriTimer* instance) { FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); + furi_assert(ticks < portMAX_DELAY); TimerHandle_t hTimer = (TimerHandle_t)instance; FuriStatus stat; @@ -97,14 +98,16 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { return (stat); } -FuriStatus furi_timer_restart(FuriTimer* instance) { +FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks) { furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); + furi_assert(ticks < portMAX_DELAY); TimerHandle_t hTimer = (TimerHandle_t)instance; FuriStatus stat; - if(xTimerReset(hTimer, portMAX_DELAY) == pdPASS) { + if(xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS && + xTimerReset(hTimer, portMAX_DELAY) == pdPASS) { stat = FuriStatusOk; } else { stat = FuriStatusErrorResource; @@ -163,7 +166,9 @@ void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) { furi_assert(!furi_kernel_is_irq_or_masked()); - TaskHandle_t task_handle = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle(); + furi_check(task_handle); // Don't call this method before timer task start if(priority == FuriTimerThreadPriorityNormal) { vTaskPrioritySet(task_handle, configTIMER_TASK_PRIORITY); diff --git a/furi/core/timer.h b/furi/core/timer.h index 47b44c71a6..d27ef5025e 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -34,7 +34,7 @@ void furi_timer_free(FuriTimer* instance); /** Start timer * * @param instance The pointer to FuriTimer instance - * @param[in] ticks The ticks + * @param[in] ticks The interval in ticks * * @return The furi status. */ @@ -43,10 +43,11 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); /** Restart timer with previous timeout value * * @param instance The pointer to FuriTimer instance + * @param[in] ticks The interval in ticks * * @return The furi status. */ -FuriStatus furi_timer_restart(FuriTimer* instance); +FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); /** Stop timer * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 3a2abc9b65..5f8e836c00 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,43.2,, +Version,+,44.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1493,7 +1493,7 @@ Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" -Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_restart,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0f81f11911..57adf96aec 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,43.2,, +Version,+,44.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1688,7 +1688,7 @@ Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" -Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_restart,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* From 0131eb3aa29568d593d8a581d6d7d2c0bd10c2df Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:10:16 +0300 Subject: [PATCH 019/111] [FL-3656] Fix crash when exiting write mode (#3191) --- .../main/ibutton/ibutton_custom_event.h | 8 +++-- .../main/ibutton/scenes/ibutton_scene_write.c | 29 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/applications/main/ibutton/ibutton_custom_event.h b/applications/main/ibutton/ibutton_custom_event.h index 28bcb94a0d..1ac50ee3cf 100644 --- a/applications/main/ibutton/ibutton_custom_event.h +++ b/applications/main/ibutton/ibutton_custom_event.h @@ -1,6 +1,6 @@ #pragma once -enum iButtonCustomEvent { +typedef enum { // Reserve first 100 events for button types and indexes, starting from 0 iButtonCustomEventReserved = 100, @@ -10,8 +10,12 @@ enum iButtonCustomEvent { iButtonCustomEventByteEditResult, iButtonCustomEventWorkerEmulated, iButtonCustomEventWorkerRead, + iButtonCustomEventWorkerWriteOK, + iButtonCustomEventWorkerWriteSameKey, + iButtonCustomEventWorkerWriteNoDetect, + iButtonCustomEventWorkerWriteCannotWrite, iButtonCustomEventRpcLoad, iButtonCustomEventRpcExit, iButtonCustomEventRpcSessionClose, -}; +} iButtonCustomEvent; diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 541aa1c52b..63be635069 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -5,9 +5,26 @@ typedef enum { iButtonSceneWriteStateBlinkYellow, } iButtonSceneWriteState; +static inline iButtonCustomEvent + ibutton_scene_write_to_custom_event(iButtonWorkerWriteResult result) { + switch(result) { + case iButtonWorkerWriteOK: + return iButtonCustomEventWorkerWriteOK; + case iButtonWorkerWriteSameKey: + return iButtonCustomEventWorkerWriteSameKey; + case iButtonWorkerWriteNoDetect: + return iButtonCustomEventWorkerWriteNoDetect; + case iButtonWorkerWriteCannotWrite: + return iButtonCustomEventWorkerWriteCannotWrite; + default: + furi_crash(); + } +} + static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult result) { iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); + view_dispatcher_send_custom_event( + ibutton->view_dispatcher, ibutton_scene_write_to_custom_event(result)); } void ibutton_scene_write_on_enter(void* context) { @@ -61,16 +78,14 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; - if((event.event == iButtonWorkerWriteOK) || (event.event == iButtonWorkerWriteSameKey)) { + if((event.event == iButtonCustomEventWorkerWriteOK) || + (event.event == iButtonCustomEventWorkerWriteSameKey)) { scene_manager_next_scene(scene_manager, iButtonSceneWriteSuccess); - } else if(event.event == iButtonWorkerWriteNoDetect) { + } else if(event.event == iButtonCustomEventWorkerWriteNoDetect) { ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink); - } else if(event.event == iButtonWorkerWriteCannotWrite) { + } else if(event.event == iButtonCustomEventWorkerWriteCannotWrite) { ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink); } - - } else if(event.type == SceneManagerEventTypeTick) { - consumed = true; } return consumed; From 0d94abf85693c0eb9eee53f8d0a83692e6390ff8 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 2 Nov 2023 17:28:39 +0400 Subject: [PATCH 020/111] fbt: dist improvements (#3186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: MENUEXTERNAL apps now respect build configuration; fixed `fap_deploy` * fbt, ufbt: better message for missing app manifests (shows real paths) Co-authored-by: あく --- SConstruct | 1 + scripts/fbt/appmanifest.py | 22 +++++++++++++--------- scripts/fbt_tools/fbt_apps.py | 7 ++++++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/SConstruct b/SConstruct index faccdfa5b3..97d7e5e5e2 100644 --- a/SConstruct +++ b/SConstruct @@ -185,6 +185,7 @@ fap_deploy = distenv.PhonyTarget( ], source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")), ) +Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"]) # Target for bundling core2 package for qFlipper diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 3a3640d425..bef4eb02b1 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -240,12 +240,12 @@ class AppBuildset: FlipperAppType.SETTINGS, FlipperAppType.STARTUP, ) - EXTERNAL_APP_TYPES = ( - FlipperAppType.EXTERNAL, - FlipperAppType.MENUEXTERNAL, - FlipperAppType.PLUGIN, - FlipperAppType.DEBUG, - ) + EXTERNAL_APP_TYPES_MAP = { + FlipperAppType.EXTERNAL: True, + FlipperAppType.PLUGIN: True, + FlipperAppType.DEBUG: True, + FlipperAppType.MENUEXTERNAL: False, + } @staticmethod def print_writer(message): @@ -318,8 +318,8 @@ def _process_deps(self): def _process_ext_apps(self): extapps = [ app - for apptype in self.EXTERNAL_APP_TYPES - for app in self.get_apps_of_type(apptype, True) + for (apptype, global_lookup) in self.EXTERNAL_APP_TYPES_MAP.items() + for app in self.get_apps_of_type(apptype, global_lookup) ] extapps.extend(map(self.appmgr.get, self._extra_ext_appnames)) @@ -407,10 +407,14 @@ def get_sdk_headers(self): return sdk_headers def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): + """Looks up apps of given type in current app set. If all_known is true, + ignores app set and checks all loaded apps' manifests.""" return sorted( filter( lambda app: app.apptype == apptype, - self.appmgr.known_apps.values() if all_known else self._apps, + self.appmgr.known_apps.values() + if all_known + else map(self.appmgr.get, self.appnames), ), key=lambda app: app.order, ) diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 9a07180552..edce194f07 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -21,8 +21,13 @@ def LoadAppManifest(env, entry): APP_MANIFEST_NAME = "application.fam" manifest_glob = entry.glob(APP_MANIFEST_NAME) if len(manifest_glob) == 0: + try: + disk_node = next(filter(lambda d: d.exists(), entry.get_all_rdirs())) + except Exception: + disk_node = entry + raise FlipperManifestException( - f"Folder {entry}: manifest {APP_MANIFEST_NAME} is missing" + f"App folder '{disk_node.abspath}': missing manifest ({APP_MANIFEST_NAME})" ) app_manifest_file_path = manifest_glob[0].rfile().abspath From 085c90af40f66350af26038342b3c3f2fe6e64d6 Mon Sep 17 00:00:00 2001 From: DerSkythe <31771569+derskythe@users.noreply.github.com> Date: Sun, 5 Nov 2023 17:59:22 +0400 Subject: [PATCH 021/111] fix: invariant format of log time data #3195 (#3202) --- scripts/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/version.py b/scripts/version.py index e68f7b41d7..4b1c739bc5 100755 --- a/scripts/version.py +++ b/scripts/version.py @@ -46,7 +46,7 @@ def get_version_info(self): ) else: commit_date = datetime.strptime( - self._exec_git("log -1 --format=%cd").strip(), + self._exec_git("log -1 --format=%cd --date=default").strip(), "%a %b %d %H:%M:%S %Y %z", ) From 19f524ec430ff772c3a3a3060eedaa3eda458d36 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:27:39 +0400 Subject: [PATCH 022/111] merge pt1 --- firmware/targets/f7/furi_hal/furi_hal_subghz_i.h | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 firmware/targets/f7/furi_hal/furi_hal_subghz_i.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h b/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h deleted file mode 100644 index e7fe2602f7..0000000000 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void furi_hal_subghz_set_dangerous_frequency(bool state_i); \ No newline at end of file From 9ed23799eb9374fbaf6d433ed8bfa78fe9fdd94f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:08:17 +0400 Subject: [PATCH 023/111] merge port p2 --- targets/f7/ble_glue/gap.c | 63 ++++++++++++++-- targets/f7/furi_hal/furi_hal_infrared.c | 95 +++++++++++++++---------- targets/f7/furi_hal/furi_hal_subghz.c | 79 +++++++++++++++++--- targets/f7/furi_hal/furi_hal_subghz_i.h | 3 + targets/f7/furi_hal/furi_hal_version.c | 47 ++++++++++-- 5 files changed, 228 insertions(+), 59 deletions(-) create mode 100644 targets/f7/furi_hal/furi_hal_subghz_i.h diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index f0533567ea..0ada37048d 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -28,6 +28,8 @@ typedef struct { GapConfig* config; GapConnectionParams connection_params; GapState state; + int8_t conn_rssi; + uint32_t time_rssi_sample; FuriMutex* state_mutex; GapEventCallback on_event_cb; void* context; @@ -56,6 +58,19 @@ static Gap* gap = NULL; static void gap_advertise_start(GapState new_state); static int32_t gap_app(void* context); +/** function for updating rssi informations in global Gap object + * +*/ +static inline void fetch_rssi() { + uint8_t ret_rssi = 127; + if(hci_read_rssi(gap->service.connection_handle, &ret_rssi) == BLE_STATUS_SUCCESS) { + gap->conn_rssi = (int8_t)ret_rssi; + gap->time_rssi_sample = furi_get_tick(); + return; + } + FURI_LOG_D(TAG, "Failed to read RSSI"); +} + static void gap_verify_connection_parameters(Gap* gap) { furi_assert(gap); @@ -128,6 +143,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->connection_params.supervisor_timeout = event->Supervision_Timeout; FURI_LOG_I(TAG, "Connection parameters event complete"); gap_verify_connection_parameters(gap); + + // Save rssi for current connection + fetch_rssi(); break; } @@ -162,6 +180,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->service.connection_handle = event->Connection_Handle; gap_verify_connection_parameters(gap); + + // Save rssi for current connection + fetch_rssi(); // Start pairing by sending security request aci_gap_slave_security_req(event->Connection_Handle); } break; @@ -242,6 +263,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { pairing_complete->Status); aci_gap_terminate(gap->service.connection_handle, 5); } else { + // Save RSSI + fetch_rssi(); + FURI_LOG_I(TAG, "Pairing complete"); GapEvent event = {.type = GapEventTypeConnected}; gap->on_event_cb(event, gap->context); //-V595 @@ -310,7 +334,7 @@ static void gap_init_svc(Gap* gap) { // Initialize GATT interface aci_gatt_init(); // Initialize GAP interface - // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME + // Skip first symbol AD_TYPE_COMPLETE_LOCAL_NAME char* name = gap->service.adv_name + 1; aci_gap_init( GAP_PERIPHERAL_ROLE, @@ -346,21 +370,34 @@ static void gap_init_svc(Gap* gap) { hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability bool keypress_supported = false; + // New things below + uint8_t conf_mitm = CFG_MITM_PROTECTION; + uint8_t conf_used_fixed_pin = CFG_USED_FIXED_PIN; + bool conf_bonding = gap->config->bonding_mode; + if(gap->config->pairing_method == GapPairingPinCodeShow) { aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) { aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; + } else if(gap->config->pairing_method == GapPairingNone) { + // Just works pairing method (IOS accept it, it seems android and linux doesn't) + conf_mitm = 0; + conf_used_fixed_pin = 0; + conf_bonding = false; + // if just works isn't supported, we want the numeric comparaison method + aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); + keypress_supported = true; } // Setup authentication aci_gap_set_authentication_requirement( - gap->config->bonding_mode, - CFG_MITM_PROTECTION, + conf_bonding, + conf_mitm, CFG_SC_SUPPORT, keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - CFG_USED_FIXED_PIN, + conf_used_fixed_pin, // 0x0 for no pin 0, CFG_IDENTITY_ADDRESS); // Configure whitelist @@ -463,7 +500,7 @@ void gap_stop_advertising() { furi_mutex_release(gap->state_mutex); } -static void gap_advetise_timer_callback(void* context) { +static void gap_advertise_timer_callback(void* context) { UNUSED(context); GapCommand command = GapCommandAdvLowPower; furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); @@ -477,7 +514,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap = malloc(sizeof(Gap)); gap->config = config; // Create advertising timer - gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); + gap->advertise_timer = furi_timer_alloc(gap_advertise_timer_callback, FuriTimerTypeOnce, NULL); // Initialization of GATT & GAP layer gap->service.adv_name = config->adv_name; gap_init_svc(gap); @@ -489,6 +526,9 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->service.connection_handle = 0xFFFF; gap->enable_adv = true; + gap->conn_rssi = 127; + gap->time_rssi_sample = 0; + // Thread configuration gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); @@ -508,6 +548,17 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { return true; } +// Get RSSI +uint32_t gap_get_remote_conn_rssi(int8_t* rssi) { + if(gap && gap->state == GapStateConnected) { + fetch_rssi(); + *rssi = gap->conn_rssi; + + if(gap->time_rssi_sample) return furi_get_tick() - gap->time_rssi_sample; + } + return 0; +} + GapState gap_get_state() { GapState state; if(gap) { diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index 3b20b6bc3a..9c0d84c550 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -9,12 +9,6 @@ #include #include -// #define INFRARED_TX_DEBUG - -#if defined INFRARED_TX_DEBUG -#define gpio_infrared_tx gpio_ext_pa7 -#endif - #define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200 #define INFRARED_POLARITY_SHIFT 1 @@ -84,6 +78,7 @@ typedef enum { static volatile InfraredState furi_hal_infrared_state = InfraredStateIdle; static InfraredTimTx infrared_tim_tx; static InfraredTimRx infrared_tim_rx; +static bool infrared_external_output; static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift); static void furi_hal_infrared_async_tx_free_resources(void); @@ -94,6 +89,14 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void); static void furi_hal_infrared_tx_dma_polarity_isr(); static void furi_hal_infrared_tx_dma_isr(); +void furi_hal_infrared_set_debug_out(bool enable) { + infrared_external_output = enable; +} + +bool furi_hal_infrared_get_debug_out_status(void) { + return infrared_external_output; +} + static void furi_hal_infrared_tim_rx_isr() { static uint32_t previous_captured_ch2 = 0; @@ -348,27 +351,29 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc LL_TIM_SetAutoReload( INFRARED_DMA_TIMER, __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(INFRARED_DMA_TIMER), freq)); -#if defined INFRARED_TX_DEBUG - LL_TIM_OC_SetCompareCH1( - INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); - LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); - /* LL_TIM_OCMODE_PWM2 set by DMA */ - LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); - LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); - LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N); - LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER); -#else - LL_TIM_OC_SetCompareCH3( - INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); - LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); - /* LL_TIM_OCMODE_PWM2 set by DMA */ - LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); - LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH); - LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); - LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N); - LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER); -#endif + if(infrared_external_output) { + LL_TIM_OC_SetCompareCH1( + INFRARED_DMA_TIMER, + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); + /* LL_TIM_OCMODE_PWM2 set by DMA */ + LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); + LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N); + LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER); + } else { + LL_TIM_OC_SetCompareCH3( + INFRARED_DMA_TIMER, + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); + /* LL_TIM_OCMODE_PWM2 set by DMA */ + LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); + LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); + LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N); + LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER); + } LL_TIM_DisableMasterSlaveMode(INFRARED_DMA_TIMER); LL_TIM_EnableAllOutputs(INFRARED_DMA_TIMER); LL_TIM_DisableIT_UPDATE(INFRARED_DMA_TIMER); @@ -377,11 +382,11 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { LL_DMA_InitTypeDef dma_config = {0}; -#if defined INFRARED_TX_DEBUG - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR1); -#else - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR2); -#endif + if(infrared_external_output) { + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR1); + } else { + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR2); + } dma_config.MemoryOrM2MDstAddress = (uint32_t)NULL; dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.Mode = LL_DMA_MODE_NORMAL; @@ -575,7 +580,11 @@ static void furi_hal_infrared_async_tx_free_resources(void) { (furi_hal_infrared_state == InfraredStateIdle) || (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); - furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + if(infrared_external_output) { + furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + } else { + furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + } furi_hal_interrupt_set_isr(INFRARED_DMA_CH1_IRQ, NULL, NULL); furi_hal_interrupt_set_isr(INFRARED_DMA_CH2_IRQ, NULL, NULL); @@ -636,10 +645,22 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { furi_delay_us(5); LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* DMA -> TIMx_RCR */ furi_delay_us(5); - LL_GPIO_ResetOutputPin( - gpio_infrared_tx.port, gpio_infrared_tx.pin); /* when disable it prevents false pulse */ - furi_hal_gpio_init_ex( - &gpio_infrared_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); + if(infrared_external_output) { + LL_GPIO_ResetOutputPin( + gpio_ext_pa7.port, gpio_ext_pa7.pin); /* when disable it prevents false pulse */ + furi_hal_gpio_init_ex( + &gpio_ext_pa7, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); + } else { + LL_GPIO_ResetOutputPin( + gpio_infrared_tx.port, + gpio_infrared_tx.pin); /* when disable it prevents false pulse */ + furi_hal_gpio_init_ex( + &gpio_infrared_tx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedHigh, + GpioAltFn1TIM1); + } FURI_CRITICAL_ENTER(); LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* TIMx_RCR -> Repetition counter */ diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index f751463532..7db0329c7b 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -1,6 +1,6 @@ #include #include -#include + #include #include #include @@ -51,14 +51,41 @@ typedef struct { volatile SubGhzState state; volatile SubGhzRegulation regulation; const GpioPin* async_mirror_pin; + + int8_t rolling_counter_mult; + bool ext_power_amp : 1; + bool dangerous_frequency_i : 1; } FuriHalSubGhz; volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, .async_mirror_pin = NULL, + .rolling_counter_mult = 1, + .ext_power_amp = false, + .dangerous_frequency_i = false, }; +int8_t furi_hal_subghz_get_rolling_counter_mult(void) { + return furi_hal_subghz.rolling_counter_mult; +} + +void furi_hal_subghz_set_rolling_counter_mult(int8_t mult) { + furi_hal_subghz.rolling_counter_mult = mult; +} + +void furi_hal_subghz_set_dangerous_frequency(bool state_i) { + furi_hal_subghz.dangerous_frequency_i = state_i; +} + +void furi_hal_subghz_set_ext_power_amp(bool enabled) { + furi_hal_subghz.ext_power_amp = enabled; +} + +bool furi_hal_subghz_get_ext_power_amp() { + return furi_hal_subghz.ext_power_amp; +} + void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } @@ -207,6 +234,7 @@ bool furi_hal_subghz_rx_pipe_not_empty() { cc1101_read_reg( &furi_hal_spi_bus_handle_subghz, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + // TODO: Find reason why RXFIFO_OVERFLOW doesnt work correctly if(status->NUM_RXBYTES > 0) { return true; } else { @@ -291,10 +319,15 @@ uint8_t furi_hal_subghz_get_lqi() { return data[0] & 0x7F; } +/* + Modified by @tkerby & MX to the full YARD Stick One extended range of 281-361 MHz, 378-481 MHz, and 749-962 MHz. + These changes are at your own risk. The PLL may not lock and FZ devs have warned of possible damage! + */ + bool furi_hal_subghz_is_frequency_valid(uint32_t value) { - if(!(value >= 299999755 && value <= 348000335) && - !(value >= 386999938 && value <= 464000000) && - !(value >= 778999847 && value <= 928000000)) { + if(!(value >= 281000000 && value <= 361000000) && + !(value >= 378000000 && value <= 481000000) && + !(value >= 749000000 && value <= 962000000)) { return false; } @@ -302,12 +335,13 @@ bool furi_hal_subghz_is_frequency_valid(uint32_t value) { } uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) { + // Set these values to the extended frequency range only. They dont define if you can transmit but do select the correct RF path value = furi_hal_subghz_set_frequency(value); - if(value >= 299999755 && value <= 348000335) { + if(value >= 281000000 && value <= 361000000) { furi_hal_subghz_set_path(FuriHalSubGhzPath315); - } else if(value >= 386999938 && value <= 464000000) { + } else if(value >= 378000000 && value <= 481000000) { furi_hal_subghz_set_path(FuriHalSubGhzPath433); - } else if(value >= 778999847 && value <= 928000000) { + } else if(value >= 749000000 && value <= 962000000) { furi_hal_subghz_set_path(FuriHalSubGhzPath868); } else { furi_crash("SubGhz: Incorrect frequency during set."); @@ -315,8 +349,27 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) { return value; } +bool furi_hal_subghz_is_tx_allowed(uint32_t value) { + bool allow_extended_for_int = furi_hal_subghz.dangerous_frequency_i; + + if(!(allow_extended_for_int) && + !(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350 + !(value >= 386999938 && value <= 467750000) && // was increased from 464 to 467.75 + !(value >= 778999847 && value <= 928000000)) { + FURI_LOG_I(TAG, "Frequency blocked - outside default range"); + return false; + } else if( + (allow_extended_for_int) && // + !furi_hal_subghz_is_frequency_valid(value)) { + FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); + return false; + } + + return true; +} + uint32_t furi_hal_subghz_set_frequency(uint32_t value) { - if(furi_hal_region_is_frequency_allowed(value)) { + if(furi_hal_subghz_is_tx_allowed(value)) { furi_hal_subghz.regulation = SubGhzRegulationTxRx; } else { furi_hal_subghz.regulation = SubGhzRegulationOnlyRx; @@ -431,7 +484,8 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* TIM_InitStruct.Prescaler = 64 - 1; TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; TIM_InitStruct.Autoreload = 0x7FFFFFFE; - TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV4; // Clock division for capture filter + // Clock division for capture filter + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV4; LL_TIM_Init(TIM2, &TIM_InitStruct); // Timer: advanced @@ -477,7 +531,7 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* // Switch to RX furi_hal_subghz_rx(); - //Clear the variable after the end of the session + // Clear the variable after the end of the session furi_hal_subghz_capture_delta_duration = 0; } @@ -695,6 +749,11 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* // Start debug if(furi_hal_subghz_start_debug()) { const GpioPin* gpio = furi_hal_subghz.async_mirror_pin; + // //Preparing bit mask + // //Debug pin is may be only PORTB! (PB0, PB1, .., PB15) + // furi_hal_subghz_debug_gpio_buff[0] = 0; + // furi_hal_subghz_debug_gpio_buff[1] = 0; + furi_hal_subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; furi_hal_subghz_debug_gpio_buff[1] = gpio->pin; diff --git a/targets/f7/furi_hal/furi_hal_subghz_i.h b/targets/f7/furi_hal/furi_hal_subghz_i.h new file mode 100644 index 0000000000..e7fe2602f7 --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_subghz_i.h @@ -0,0 +1,3 @@ +#pragma once + +void furi_hal_subghz_set_dangerous_frequency(bool state_i); \ No newline at end of file diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index e4364a5189..46a15bde8b 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -90,7 +90,7 @@ typedef struct { static FuriHalVersion furi_hal_version = {0}; -static void furi_hal_version_set_name(const char* name) { +void furi_hal_version_set_name(const char* name) { if(name != NULL) { strlcpy(furi_hal_version.name, name, FURI_HAL_VERSION_ARRAY_NAME_LENGTH); snprintf( @@ -106,8 +106,16 @@ static void furi_hal_version_set_name(const char* name) { // BLE Mac address uint32_t udn = LL_FLASH_GetUDN(); + if(version_get_custom_name(NULL) != NULL) { + udn = *((uint32_t*)version_get_custom_name(NULL)); + } + uint32_t company_id = LL_FLASH_GetSTCompanyID(); - uint32_t device_id = LL_FLASH_GetDeviceID(); + // uint32_t device_id = LL_FLASH_GetDeviceID(); + // Some flippers return 0x27 (flippers with chip revision 2003 6495) instead of 0x26 (flippers with chip revision 2001 6495) + // Mobile apps expects it to return 0x26 + // Hardcoded here temporarily until mobile apps is updated to handle 0x27 + uint32_t device_id = 0x26; furi_hal_version.ble_mac[0] = (uint8_t)(udn & 0x000000FF); furi_hal_version.ble_mac[1] = (uint8_t)((udn & 0x0000FF00) >> 8); furi_hal_version.ble_mac[2] = (uint8_t)((udn & 0x00FF0000) >> 16); @@ -129,7 +137,11 @@ static void furi_hal_version_load_otp_v0() { furi_hal_version.board_body = otp->board_body; furi_hal_version.board_connect = otp->board_connect; - furi_hal_version_set_name(otp->name); + if(version_get_custom_name(NULL) != NULL) { + furi_hal_version_set_name(version_get_custom_name(NULL)); + } else { + furi_hal_version_set_name(otp->name); + } } static void furi_hal_version_load_otp_v1() { @@ -143,7 +155,11 @@ static void furi_hal_version_load_otp_v1() { furi_hal_version.board_color = otp->board_color; furi_hal_version.board_region = otp->board_region; - furi_hal_version_set_name(otp->name); + if(version_get_custom_name(NULL) != NULL) { + furi_hal_version_set_name(version_get_custom_name(NULL)); + } else { + furi_hal_version_set_name(otp->name); + } } static void furi_hal_version_load_otp_v2() { @@ -163,7 +179,11 @@ static void furi_hal_version_load_otp_v2() { if(otp->board_color != 0xFF) { furi_hal_version.board_color = otp->board_color; furi_hal_version.board_region = otp->board_region; - furi_hal_version_set_name(otp->name); + if(version_get_custom_name(NULL) != NULL) { + furi_hal_version_set_name(version_get_custom_name(NULL)); + } else { + furi_hal_version_set_name(otp->name); + } } else { furi_hal_version.board_color = 0; furi_hal_version.board_region = 0; @@ -239,11 +259,19 @@ uint8_t furi_hal_version_get_hw_connect() { } FuriHalVersionRegion furi_hal_version_get_hw_region() { + return FuriHalVersionRegionUnknown; +} + +FuriHalVersionRegion furi_hal_version_get_hw_region_otp() { return furi_hal_version.board_region; } const char* furi_hal_version_get_hw_region_name() { - switch(furi_hal_version_get_hw_region()) { + return "R00"; +} + +const char* furi_hal_version_get_hw_region_name_otp() { + switch(furi_hal_version_get_hw_region_otp()) { case FuriHalVersionRegionUnknown: return "R00"; case FuriHalVersionRegionEuRu: @@ -290,6 +318,13 @@ size_t furi_hal_version_uid_size() { return 64 / 8; } +const uint8_t* furi_hal_version_uid_default() { + return (const uint8_t*)UID64_BASE; +} + const uint8_t* furi_hal_version_uid() { + if(version_get_custom_name(NULL) != NULL) { + return (const uint8_t*)&(*((uint32_t*)version_get_custom_name(NULL))); + } return (const uint8_t*)UID64_BASE; } From 5921eb3d27582ab476abf0e53692b1747d295b5b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:26:11 +0400 Subject: [PATCH 024/111] return some stuff --- .../main/subghz/subghz_dangerous_freq.c | 2 +- furi/core/common_defines.h | 2 + furi/furi.h | 3 + targets/f7/api_symbols.csv | 179 +++++++++++++++++- targets/f7/src/main.c | 1 + 5 files changed, 181 insertions(+), 6 deletions(-) diff --git a/applications/main/subghz/subghz_dangerous_freq.c b/applications/main/subghz/subghz_dangerous_freq.c index 8552c5f164..9722d28b6c 100644 --- a/applications/main/subghz/subghz_dangerous_freq.c +++ b/applications/main/subghz/subghz_dangerous_freq.c @@ -1,7 +1,7 @@ #include #include -#include +#include #include diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 2b30c3b06d..5bd218d357 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -2,6 +2,8 @@ #include "core_defines.h" #include +#include +#include #ifdef __cplusplus extern "C" { diff --git a/furi/furi.h b/furi/furi.h index 422509055f..b1299c9a95 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -21,6 +21,9 @@ #include +// FreeRTOS timer, REMOVE AFTER REFACTORING +#include + // Workaround for math.h leaking through HAL in older versions #include diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 57adf96aec..863ae3ef0f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,44.0,, +Version,+,41.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -172,6 +172,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/subghz/blocks/const.h,, +Header,+,lib/subghz/blocks/custom_btn.h,, Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, @@ -183,6 +184,7 @@ Header,+,lib/subghz/protocols/public_api.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/registry.h,, +Header,+,lib/subghz/subghz_file_encoder_worker.h,, Header,+,lib/subghz/subghz_protocol_registry.h,, Header,+,lib/subghz/subghz_setting.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, @@ -666,11 +668,20 @@ Function,+,ble_glue_start,_Bool, Function,+,ble_glue_thread_stop,void, Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" +Function,+,bt_disable_peer_key_update,void,Bt* Function,+,bt_disconnect,void,Bt* +Function,+,bt_enable_peer_key_update,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_get_profile_adv_name,const char*,Bt* +Function,+,bt_get_profile_mac_address,const uint8_t*,Bt* +Function,+,bt_get_profile_pairing_method,GapPairing,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_remote_rssi,_Bool,"Bt*, uint8_t*" Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_set_profile_adv_name,void,"Bt*, const char*, ..." +Function,+,bt_set_profile_mac_address,void,"Bt*, const uint8_t[6]" +Function,+,bt_set_profile_pairing_method,void,"Bt*, GapPairing" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -692,6 +703,7 @@ Function,+,button_panel_free,void,ButtonPanel* Function,+,button_panel_get_view,View*,ButtonPanel* Function,+,button_panel_reserve,void,"ButtonPanel*, size_t, size_t" Function,+,button_panel_reset,void,ButtonPanel* +Function,+,button_panel_reset_selection,void,ButtonPanel* Function,+,byte_input_alloc,ByteInput*, Function,+,byte_input_free,void,ByteInput* Function,+,byte_input_get_view,View*,ByteInput* @@ -702,6 +714,7 @@ Function,-,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* Function,+,canvas_current_font_height,uint8_t,const Canvas* +Function,+,canvas_current_font_width,uint8_t,const Canvas* Function,+,canvas_draw_bitmap,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_draw_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_circle,void,"Canvas*, uint8_t, uint8_t, uint8_t" @@ -711,6 +724,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_icon_bitmap,void,"Canvas*, uint8_t, uint8_t, int16_t, int16_t, const Icon*" Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation" Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" @@ -839,6 +853,8 @@ Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, Function,-,drem,double,"double, double" Function,-,dremf,float,"float, float" +Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, +Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" @@ -852,6 +868,7 @@ Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" Function,+,elements_progress_bar_with_text,void,"Canvas*, uint8_t, uint8_t, uint8_t, float, const char*" Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" +Function,+,elements_scrollable_text_line_str,void,"Canvas*, uint8_t, uint8_t, uint8_t, const char*, size_t, _Bool, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -1072,16 +1089,24 @@ Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, ui Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" Function,+,furi_hal_bt_clear_white_list,_Bool, +Function,+,furi_hal_bt_custom_adv_set,_Bool,"const uint8_t*, size_t" +Function,+,furi_hal_bt_custom_adv_start,_Bool,"uint16_t, uint16_t, uint8_t, const uint8_t[( 6 )], uint8_t" +Function,+,furi_hal_bt_custom_adv_stop,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode +Function,-,furi_hal_bt_get_conn_rssi,uint32_t,uint8_t* Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,+,furi_hal_bt_get_profile_adv_name,const char*,FuriHalBtProfile +Function,+,furi_hal_bt_get_profile_mac_addr,const uint8_t*,FuriHalBtProfile +Function,-,furi_hal_bt_get_profile_pairing_method,GapPairing,FuriHalBtProfile Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, +Function,+,furi_hal_bt_hid_get_led_state,uint8_t, Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t Function,+,furi_hal_bt_hid_kb_release_all,_Bool, @@ -1096,11 +1121,13 @@ Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_connected,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, +Function,+,furi_hal_bt_reverse_mac_addr,void,uint8_t[( 6 )] Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus @@ -1108,6 +1135,9 @@ Function,+,furi_hal_bt_serial_start,void, Function,+,furi_hal_bt_serial_stop,void, Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" +Function,+,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( ( 1 + 8 + ( 8 + 1 ) ) + 1 )]" +Function,+,furi_hal_bt_set_profile_mac_addr,void,"FuriHalBtProfile, const uint8_t[( 6 )]" +Function,+,furi_hal_bt_set_profile_pairing_method,void,"FuriHalBtProfile, GapPairing" Function,+,furi_hal_bt_start_advertising,void, Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" @@ -1259,7 +1289,9 @@ Function,+,furi_hal_infrared_async_tx_set_signal_sent_isr_callback,void,"FuriHal Function,+,furi_hal_infrared_async_tx_start,void,"uint32_t, float" Function,+,furi_hal_infrared_async_tx_stop,void, Function,+,furi_hal_infrared_async_tx_wait_termination,void, +Function,+,furi_hal_infrared_get_debug_out_status,_Bool, Function,+,furi_hal_infrared_is_busy,_Bool, +Function,+,furi_hal_infrared_set_debug_out,void,_Bool Function,-,furi_hal_init,void, Function,-,furi_hal_init_early,void, Function,-,furi_hal_interrupt_init,void, @@ -1366,7 +1398,6 @@ Function,+,furi_hal_random_init,void, Function,+,furi_hal_region_get,const FuriHalRegion*, Function,+,furi_hal_region_get_band,const FuriHalRegionBand*,uint32_t Function,+,furi_hal_region_get_name,const char*, -Function,-,furi_hal_region_init,void, Function,+,furi_hal_region_is_frequency_allowed,_Bool,uint32_t Function,+,furi_hal_region_is_provisioned,_Bool, Function,+,furi_hal_region_set,void,FuriHalRegion* @@ -1461,13 +1492,16 @@ Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, Function,+,furi_hal_subghz_get_data_gpio,const GpioPin*, +Function,+,furi_hal_subghz_get_ext_power_amp,_Bool, Function,+,furi_hal_subghz_get_lqi,uint8_t, +Function,+,furi_hal_subghz_get_rolling_counter_mult,int8_t, Function,+,furi_hal_subghz_get_rssi,float, Function,+,furi_hal_subghz_idle,void, Function,-,furi_hal_subghz_init,void, Function,+,furi_hal_subghz_is_async_tx_complete,_Bool, Function,+,furi_hal_subghz_is_frequency_valid,_Bool,uint32_t Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, +Function,+,furi_hal_subghz_is_tx_allowed,_Bool,uint32_t Function,+,furi_hal_subghz_load_custom_preset,void,const uint8_t* Function,+,furi_hal_subghz_load_patable,void,const uint8_t[8] Function,+,furi_hal_subghz_load_registers,void,const uint8_t* @@ -1476,9 +1510,11 @@ Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* +Function,+,furi_hal_subghz_set_ext_power_amp,void,_Bool Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath +Function,+,furi_hal_subghz_set_rolling_counter_mult,void,int8_t Function,+,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_sleep,void, Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" @@ -1517,6 +1553,8 @@ Function,+,furi_hal_version_get_hw_connect,uint8_t, Function,+,furi_hal_version_get_hw_display,FuriHalVersionDisplay, Function,+,furi_hal_version_get_hw_region,FuriHalVersionRegion, Function,+,furi_hal_version_get_hw_region_name,const char*, +Function,+,furi_hal_version_get_hw_region_name_otp,const char*, +Function,+,furi_hal_version_get_hw_region_otp,FuriHalVersionRegion, Function,+,furi_hal_version_get_hw_target,uint8_t, Function,+,furi_hal_version_get_hw_timestamp,uint32_t, Function,+,furi_hal_version_get_hw_version,uint8_t, @@ -1527,7 +1565,9 @@ Function,+,furi_hal_version_get_model_name,const char*, Function,+,furi_hal_version_get_name_ptr,const char*, Function,+,furi_hal_version_get_otp_version,FuriHalVersionOtpVersion, Function,-,furi_hal_version_init,void, +Function,-,furi_hal_version_set_name,void,const char* Function,+,furi_hal_version_uid,const uint8_t*, +Function,+,furi_hal_version_uid_default,const uint8_t*, Function,+,furi_hal_version_uid_size,size_t, Function,-,furi_hal_vibro_init,void, Function,+,furi_hal_vibro_on,void,_Bool @@ -1645,6 +1685,7 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" +Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -1698,6 +1739,7 @@ Function,-,gamma,double,double Function,-,gamma_r,double,"double, int*" Function,-,gammaf,float,float Function,-,gammaf_r,float,"float, int*" +Function,-,gap_get_remote_conn_rssi,uint32_t,int8_t* Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1948,7 +1990,7 @@ Function,-,isupper,int,int Function,-,isupper_l,int,"int, locale_t" Function,-,isxdigit,int,int Function,-,isxdigit_l,int,"int, locale_t" -Function,-,itoa,char*,"int, char*, int" +Function,+,itoa,char*,"int, char*, int" Function,-,j0,double,double Function,-,j0f,float,float Function,-,j1,double,double @@ -2381,6 +2423,8 @@ Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" Function,+,pb_release,void,"const pb_msgdesc_t*, void*" Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" +Function,-,pcTaskGetName,char*,TaskHandle_t +Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -2413,6 +2457,7 @@ Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,PowerBootMode +Function,-,power_trigger_ui_update,void,Power* Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" @@ -2455,6 +2500,12 @@ Function,-,putchar_unlocked,int,int Function,-,putenv,int,char* Function,-,puts,int,const char* Function,-,putw,int,"int, FILE*" +Function,-,pvPortCalloc,void*,"size_t, size_t" +Function,-,pvPortMalloc,void*,size_t +Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" +Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, +Function,-,pvTimerGetTimerID,void*,const TimerHandle_t +Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" Function,-,quick_exit,void,int @@ -2676,7 +2727,7 @@ Function,-,stpncpy,char*,"char*, const char*, size_t" Function,-,strcasecmp,int,"const char*, const char*" Function,-,strcasecmp_l,int,"const char*, const char*, locale_t" Function,+,strcasestr,char*,"const char*, const char*" -Function,-,strcat,char*,"char*, const char*" +Function,+,strcat,char*,"char*, const char*" Function,+,strchr,char*,"const char*, int" Function,-,strchrnul,char*,"const char*, int" Function,+,strcmp,int,"const char*, const char*" @@ -2750,7 +2801,7 @@ Function,-,strtod,double,"const char*, char**" Function,-,strtod_l,double,"const char*, char**, locale_t" Function,+,strtof,float,"const char*, char**" Function,-,strtof_l,float,"const char*, char**, locale_t" -Function,-,strtok,char*,"char*, const char*" +Function,+,strtok,char*,"char*, const char*" Function,-,strtok_r,char*,"char*, const char*, char**" Function,+,strtol,long,"const char*, char**, int" Function,-,strtol_l,long,"const char*, char**, int, locale_t" @@ -2770,6 +2821,11 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_custom_btn_get,uint8_t, +Function,+,subghz_custom_btn_get_original,uint8_t, +Function,+,subghz_custom_btn_is_allowed,_Bool, +Function,+,subghz_custom_btn_set,_Bool,uint8_t +Function,+,subghz_custom_btns_reset,void, Function,-,subghz_device_cc1101_ext_ep,const FlipperAppPluginDescriptor*, Function,+,subghz_devices_begin,_Bool,const SubGhzDevice* Function,+,subghz_devices_deinit,void, @@ -2810,16 +2866,26 @@ Function,+,subghz_environment_get_nice_flor_s_rainbow_table_file_name,const char Function,+,subghz_environment_get_protocol_name_registry,const char*,"SubGhzEnvironment*, size_t" Function,+,subghz_environment_get_protocol_registry,const SubGhzProtocolRegistry*,SubGhzEnvironment* Function,+,subghz_environment_load_keystore,_Bool,"SubGhzEnvironment*, const char*" +Function,+,subghz_environment_reset_keeloq,void,SubGhzEnvironment* Function,+,subghz_environment_set_alutech_at_4n_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_protocol_registry,void,"SubGhzEnvironment*, const SubGhzProtocolRegistry*" +Function,+,subghz_file_encoder_worker_alloc,SubGhzFileEncoderWorker*, +Function,+,subghz_file_encoder_worker_callback_end,void,"SubGhzFileEncoderWorker*, SubGhzFileEncoderWorkerCallbackEnd, void*" +Function,+,subghz_file_encoder_worker_free,void,SubGhzFileEncoderWorker* +Function,+,subghz_file_encoder_worker_get_level_duration,LevelDuration,void* +Function,+,subghz_file_encoder_worker_get_text_progress,void,"SubGhzFileEncoderWorker*, FuriString*" +Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker* +Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*" +Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker* Function,-,subghz_keystore_alloc,SubGhzKeystore*, Function,-,subghz_keystore_free,void,SubGhzKeystore* Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* Function,-,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" +Function,-,subghz_keystore_reset_kl,void,SubGhzKeystore* Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" @@ -2879,6 +2945,7 @@ Function,+,subghz_receiver_search_decoder_base_by_name,SubGhzProtocolDecoderBase Function,+,subghz_receiver_set_filter,void,"SubGhzReceiver*, SubGhzProtocolFlag" Function,+,subghz_receiver_set_rx_callback,void,"SubGhzReceiver*, SubGhzReceiverCallback, void*" Function,+,subghz_setting_alloc,SubGhzSetting*, +Function,+,subghz_setting_customs_presets_to_log,uint8_t,SubGhzSetting* Function,+,subghz_setting_delete_custom_preset,_Bool,"SubGhzSetting*, const char*" Function,+,subghz_setting_free,void,SubGhzSetting* Function,+,subghz_setting_get_default_frequency,uint32_t,SubGhzSetting* @@ -2895,6 +2962,7 @@ Function,+,subghz_setting_get_preset_data_size,size_t,"SubGhzSetting*, size_t" Function,+,subghz_setting_get_preset_name,const char*,"SubGhzSetting*, size_t" Function,+,subghz_setting_load,void,"SubGhzSetting*, const char*" Function,+,subghz_setting_load_custom_preset,_Bool,"SubGhzSetting*, const char*, FlipperFormat*" +Function,+,subghz_setting_set_default_frequency,void,"SubGhzSetting*, uint32_t" Function,+,subghz_transmitter_alloc_init,SubGhzTransmitter*,"SubGhzEnvironment*, const char*" Function,+,subghz_transmitter_deserialize,SubGhzProtocolStatus,"SubGhzTransmitter*, FlipperFormat*" Function,+,subghz_transmitter_free,void,SubGhzTransmitter* @@ -2921,14 +2989,17 @@ Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPair Function,+,subghz_worker_start,void,SubGhzWorker* Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_lockable_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_free,void,Submenu* Function,+,submenu_get_view,View*,Submenu* Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" +Function,+,submenu_set_orientation,void,"Submenu*, ViewOrientation" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* Function,+,t5577_write,void,LFRFIDT5577* +Function,+,t5577_write_with_pass,void,"LFRFIDT5577*, uint32_t" Function,-,tan,double,double Function,-,tanf,float,float Function,-,tanh,double,double @@ -2965,6 +3036,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tgamma,double,double @@ -3004,10 +3076,67 @@ Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" +Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" +Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" +Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, +Function,-,ulTaskGetIdleRunTimePercent,uint32_t, Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" +Function,-,uxListRemove,UBaseType_t,ListItem_t* +Function,-,uxTaskGetNumberOfTasks,UBaseType_t, +Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t +Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t +Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" +Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t +Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t +Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t +Function,-,uxTaskResetEventItemValue,TickType_t, +Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t +Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t +Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" +Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" +Function,-,vListInitialise,void,List_t* +Function,-,vListInitialiseItem,void,ListItem_t* +Function,-,vListInsert,void,"List_t*, ListItem_t*" +Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" +Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* +Function,-,vPortEndScheduler,void, +Function,+,vPortEnterCritical,void, +Function,+,vPortExitCritical,void, +Function,-,vPortFree,void,void* +Function,-,vPortGetHeapStats,void,HeapStats_t* +Function,-,vPortInitialiseBlocks,void, +Function,-,vPortSuppressTicksAndSleep,void,TickType_t +Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" +Function,-,vTaskDelay,void,const TickType_t +Function,-,vTaskDelete,void,TaskHandle_t +Function,-,vTaskEndScheduler,void, +Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" +Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" +Function,-,vTaskGetRunTimeStats,void,char* +Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* +Function,-,vTaskList,void,char* +Function,-,vTaskMissedYield,void, +Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" +Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" +Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" +Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" +Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" +Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" +Function,-,vTaskResume,void,TaskHandle_t +Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" +Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" +Function,-,vTaskSetTimeOutState,void,TimeOut_t* +Function,-,vTaskStartScheduler,void, +Function,-,vTaskStepTick,void,TickType_t +Function,-,vTaskSuspend,void,TaskHandle_t +Function,-,vTaskSuspendAll,void, +Function,-,vTaskSwitchContext,void, +Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" +Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" +Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* @@ -3027,6 +3156,7 @@ Function,+,variable_item_list_set_enter_callback,void,"VariableItemList*, Variab Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t" Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" +Function,+,variable_item_set_locked,void,"VariableItem*, _Bool, const char*" Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" @@ -3036,6 +3166,7 @@ Function,-,vdiprintf,int,"int, const char*, __gnuc_va_list" Function,-,vdprintf,int,"int, const char*, __gnuc_va_list" Function,+,version_get,const Version*, Function,+,version_get_builddate,const char*,const Version* +Function,+,version_get_custom_name,const char*,const Version* Function,+,version_get_dirty_flag,_Bool,const Version* Function,+,version_get_firmware_origin,const char*,const Version* Function,+,version_get_git_origin,const char*,const Version* @@ -3044,6 +3175,7 @@ Function,+,version_get_gitbranchnum,const char*,const Version* Function,+,version_get_githash,const char*,const Version* Function,+,version_get_target,uint8_t,const Version* Function,+,version_get_version,const char*,const Version* +Function,-,version_set_custom_name,void,"Version*, const char*" Function,-,vfiprintf,int,"FILE*, const char*, __gnuc_va_list" Function,-,vfiscanf,int,"FILE*, const char*, __gnuc_va_list" Function,-,vfprintf,int,"FILE*, const char*, __gnuc_va_list" @@ -3122,6 +3254,43 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* +Function,-,xPortGetFreeHeapSize,size_t, +Function,-,xPortGetMinimumEverFreeHeapSize,size_t, +Function,-,xPortStartScheduler,BaseType_t, +Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t +Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" +Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t +Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" +Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" +Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" +Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" +Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" +Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" +Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" +Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" +Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, +Function,+,xTaskGetHandle,TaskHandle_t,const char* +Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, +Function,+,xTaskGetSchedulerState,BaseType_t, +Function,+,xTaskGetTickCount,TickType_t, +Function,-,xTaskGetTickCountFromISR,TickType_t, +Function,-,xTaskIncrementTick,BaseType_t, +Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t +Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t +Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* +Function,-,xTaskResumeAll,BaseType_t, +Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t +Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" +Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" +Function,-,xTimerCreateTimerTask,BaseType_t, +Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" +Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t +Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t +Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t +Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, +Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t +Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" +Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" Function,-,y0,double,double Function,-,y0f,float,float Function,-,y1,double,double diff --git a/targets/f7/src/main.c b/targets/f7/src/main.c index ca705fe5e4..2c353f52b2 100644 --- a/targets/f7/src/main.c +++ b/targets/f7/src/main.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #define TAG "Main" From ebe95a92d1855ddeeecd6b052f68ae3a60721685 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:38:22 +0400 Subject: [PATCH 025/111] infrared subghz merge fixes --- applications/main/infrared/infrared_app.c | 9 +++ applications/main/infrared/infrared_app_i.h | 3 + .../scenes/infrared_scene_debug_settings.c | 12 ++-- .../scenes/infrared_scene_universal_fan.c | 4 +- applications/main/subghz/application.fam | 8 +-- .../resources/subghz/assets/alutech_at_4n | 6 -- .../resources/subghz/assets/keeloq_mfcodes | 59 ------------------- .../subghz/assets/keeloq_mfcodes_user.example | 16 ----- .../resources/subghz/assets/nice_flor_s | 6 -- .../subghz/assets/setting_user.example | 29 --------- applications/main/subghz/views/receiver.c | 6 +- 11 files changed, 24 insertions(+), 134 deletions(-) delete mode 100644 applications/main/subghz/resources/subghz/assets/alutech_at_4n delete mode 100644 applications/main/subghz/resources/subghz/assets/keeloq_mfcodes delete mode 100644 applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example delete mode 100644 applications/main/subghz/resources/subghz/assets/nice_flor_s delete mode 100644 applications/main/subghz/resources/subghz/assets/setting_user.example diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 7abb4e4eb6..a764d85d8b 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -158,6 +158,12 @@ static InfraredApp* infrared_alloc() { view_dispatcher_add_view( view_dispatcher, InfraredViewDialogEx, dialog_ex_get_view(infrared->dialog_ex)); + infrared->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + infrared->view_dispatcher, + InfraredViewVariableItemList, + variable_item_list_get_view(infrared->variable_item_list)); + infrared->button_menu = button_menu_alloc(); view_dispatcher_add_view( view_dispatcher, InfraredViewButtonMenu, button_menu_get_view(infrared->button_menu)); @@ -208,6 +214,9 @@ static void infrared_free(InfraredApp* infrared) { view_dispatcher_remove_view(view_dispatcher, InfraredViewDialogEx); dialog_ex_free(infrared->dialog_ex); + view_dispatcher_remove_view(infrared->view_dispatcher, InfraredViewVariableItemList); + variable_item_list_free(infrared->variable_item_list); + view_dispatcher_remove_view(view_dispatcher, InfraredViewButtonMenu); button_menu_free(infrared->button_menu); diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index c9dfe3ab86..7e3732c612 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,7 @@ struct InfraredApp { DialogEx* dialog_ex; /**< Standard view for displaying dialogs. */ ButtonMenu* button_menu; /**< Custom view for interacting with IR remotes. */ Popup* popup; /**< Standard view for displaying messages. */ + VariableItemList* variable_item_list; ViewStack* view_stack; /**< Standard view for displaying stacked interfaces. */ InfraredDebugView* debug_view; /**< Custom view for displaying debug information. */ @@ -140,6 +142,7 @@ typedef enum { InfraredViewStack, InfraredViewDebugView, InfraredViewMove, + InfraredViewVariableItemList, } InfraredView; /** diff --git a/applications/main/infrared/scenes/infrared_scene_debug_settings.c b/applications/main/infrared/scenes/infrared_scene_debug_settings.c index bef666275a..badfd172a4 100644 --- a/applications/main/infrared/scenes/infrared_scene_debug_settings.c +++ b/applications/main/infrared/scenes/infrared_scene_debug_settings.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include uint8_t value_index_ir; @@ -10,7 +10,7 @@ const char* const infrared_debug_cfg_variables_text[] = { }; static void infrared_scene_debug_settings_changed(VariableItem* item) { - Infrared* infrared = variable_item_get_context(item); + InfraredApp* infrared = variable_item_get_context(item); value_index_ir = variable_item_get_current_value_index(item); UNUSED(infrared); @@ -35,12 +35,12 @@ static void infrared_scene_debug_settings_power_changed(VariableItem* item) { } static void infrared_debug_settings_start_var_list_enter_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_debug_settings_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; VariableItemList* variable_item_list = infrared->variable_item_list; @@ -72,7 +72,7 @@ void infrared_scene_debug_settings_on_enter(void* context) { } bool infrared_scene_debug_settings_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; UNUSED(infrared); UNUSED(event); @@ -80,6 +80,6 @@ bool infrared_scene_debug_settings_on_event(void* context, SceneManagerEvent eve } void infrared_scene_debug_settings_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; variable_item_list_reset(infrared->variable_item_list); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_fan.c b/applications/main/infrared/scenes/infrared_scene_universal_fan.c index 1622c5458e..9074b5508f 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_fan.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_fan.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_fan_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index eed6a3c32a..7494e6fba5 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -17,12 +17,6 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=1, - sources=[ - "*.c", - "!subghz_cli.c", - "!helpers/subghz_chat.c", - ], - resources="resources", fap_libs=["assets", "hwdrivers"], fap_icon="icon.png", fap_category="Sub-GHz", @@ -33,7 +27,7 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="subghz_on_system_start", - sources=["subghz_cli.c", "helpers/subghz_chat.c"], + requires=["subghz"], order=40, ) diff --git a/applications/main/subghz/resources/subghz/assets/alutech_at_4n b/applications/main/subghz/resources/subghz/assets/alutech_at_4n deleted file mode 100644 index 5d7beacec4..0000000000 --- a/applications/main/subghz/resources/subghz/assets/alutech_at_4n +++ /dev/null @@ -1,6 +0,0 @@ -Filetype: Flipper SubGhz Keystore RAW File -Version: 0 -Encryption: 1 -IV: 88 64 A6 A6 44 47 67 8A D6 32 36 F6 B9 06 57 31 -Encrypt_data: RAW -E811BD4F0955D217AE6677906E799D45D8DAAFD1F7923E1660B5E24574631B60 \ No newline at end of file diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes deleted file mode 100644 index 27ab5aaafd..0000000000 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ /dev/null @@ -1,59 +0,0 @@ -Filetype: Flipper SubGhz Keystore File -Version: 0 -Encryption: 1 -IV: AD 0B A4 A1 51 C0 C0 41 36 78 26 82 17 24 9D 62 -0D18FF475E57A67CC5C0B430664E8EF6E07CB6AF72454995F17DE84E2E876D87 -C9BC55E1E3A9B312E341D7E2663C66C2479D5C51AE2EB83BAE47D8C6C79DB8A0 -776E01A7B4FCB929FA59CFE1F16D2D600F6FD9FD8BE1E9E41667144E61E023E4 -C354F854F14AAC08C6A51606582CD73EECEED54779927DC1E04A0D72C4E6A58E -09BA4DC551CDB0141F4A053133DBF9B7B99CBAA402C7B6B2AC8ADB516CA2FCB29FF9744DD95FB009BED6A09AE3303317 -675777F65042358100A9A2BE142C42E10CC5CF98CA6BFF82284FFD9BF7E7FB11 -4739972DCC08EFF0E8547694A67E116F3EC8638F286E333E7D89F2D61218F65D -FE8D5A0A9ED36545004CEE70D3C1FE477F34212364CF7C9EBA0FDD4F6B8F1D3E -453D5C6FE0EA7FD779B54696E66DE6BD6AEF3B1EE347142A3DB7505609077219 -A52339EE40D046E7DF6C130CEC7F9F686753A73A5F72EF344E01B00C3A3CF5F2 -94029CE386CC0403984F7C9D80B40FF12BE6E50412891FD45B347742340D1E6E679102DEC6F7D36C36863701A2BBDA7B -793633A3D97D704779E2E841A5F616ED0BB0BD50740C0B196D72C6C6453124183CA93ADCAC1A5042EBD579AC238B9DBC -B1C6EBB7320CAE97E28A244228B288BEEA44C9FD262E2C448219DAC6E194CB14 -A6C374B3FB7E8BB6F3052ED9DB67E5BCFA6CCC42F5A0D7F20BEDB7C0B58592A5 -DF4FCBDCA3414B7551797A856048625186B2EC7B836EB35B4B080CEE4A72C39F -4127F58BDEB18D79EA8317B1858F7575CF4B648E3C53338975B4A093FA9730C0 -294659EBD39D7CC01DC4E0E3B0B90A3555704E736D3BE4485BAAACBC58A84652CF3A7DA3C271E3198C72561332F2A141 -9E2CC77756BE7AB3D500C0B4A91271C0A4DED5BCB78484217EA922B7878AA8F5 -BF13D9949C783CF6B2F0E489A6912E56DDD9183D651D7F36FD0342981596A85C -8441C62006E1334FC31F53DAFF5281260C463AF3E92B1E797CB67882C49566846825606804C14C49FFD440F4E05CE692 -BE7E62E9CFA1C703CCB971F2B0C6C0F339C78A951CA8DB287B40C3BA76EA7179E8B62C29EAC8293FD218CF981BB84BEF -DA53C52101FAC8FD8F9545768CF1DB59F3B31C9249A2FC64B66DB259C721ABE1 -C9842AD0A8A8E94FCE94F46BDE89DAA8D11C41F9C83A7F6394D595028829AB92 -904A94542BCD85F72D470640A220FD28EBC337C0673C189C96BFB8CE373B4F84 -EEEE187B03FE77B955F22214707017DB8A60B2E3979D5C2F6BED61D0623D4D50 -CEF91BE6EBD8F331E2422B6B948053E8DA143F3DD907C922E0328D49B0ADA8A6 -A6F0F8A4F03B45062CEA86B291E80EE15906C0B226ABDB77E222EB95B026952D -62280870E48A2E8B8A18AD5DFF0089E927BEACE1F81A8BB1A8A2BE8EB0E92BFD -C9A3DE8165D47D1D41715F00192313755226C1B0A3D04BCD7A121B1CB4999FBD -3CAEEB62FCF601F34D3320E3EACE9EFC5D627BF69FEE965F8B84A258A765B6FE -8D83CC36514CE5CC46B181AE28089BE5BC99CE3096C569751B4E07A7D956AFA3 -0F81DDB18F60E238C7FF751E70431AA5328EFDBBDFF03D5F360A524AA968CEE9 -1F1E498A279E9BA15CB6BB836E90FEE522F2BE16572A4D057DF9C2B104487458 -68C60FF95056BE4D814CE9B8A4175625DB3E365712C2D3E42CEEBA2E0977CDB9 -D95FB21943FCAD5A21F157E629314986D92F568FD3067B65911135E6335FB7E8 -17908EF142C4B6740CBE7DCA43DA3C23C7912739299786473A994E5752E7E9459B23653EE0D7775FCECB7B7608CB0495 -6D17B6BA17DBFFAA4E90953CE6A73AB967076B8FBC14A9361D93A01C0850CC38 -8723740BCC0A5CCEE52B6EF73DD441672FB6728965CC588044C78D495AB0675F -CA548FA44A444C8F45490A11DBC8FB24E6DB38F910EC60520A3890C8B45664FE -591356440344AAFEC21FAA0C85B6A8F354D45074932E37E0972F851E08937469 -DE3A54CAAC8014625EE502F547A93754AFAD0A7EB6028599D03CCB0473BC8D5C -B2C2F6F971034E1591AD374793D6D17A595D6544DF5A9585780C6B2E3505BCFF -54447BE6C626D1CA37FD799B76B35ED266D12757B5DA1AB9277F671BBF7F07B461D0CE26593F1372354979E836972F85 -45FBD88AA7C26B967BC3638F6083A6B83AA82D5B974B37DF1C3F52839DDA020C -33B9890FBE46FDB7AEE404B71C893DC20059F96224CF48F284A66D3A8D91918E -CCEA5BC148BC84DB4825320A2B8D39A30BAB4641FFEE33BD4B8700339F15892D -4BC4E0D1263E9B02DA401C884923778D1A6FACACFFE7F660381ECD64CCA5CCD2 -0284EA911FB1B37F623F92B1662E10D79AEBD0009C107A9C554D417F7553B28E -FE48F26C44FA4C3EB3B2F3497FE99AC30A0A7BF9FF261740E177D7A2A5BD7880 -1EA96FEBB62543A8731D19BBDD1C7FE323CADBAB7242E5A8F4B1D706964B4C32 -4AB7EEF02FD59EA5D16837A50282A6254B93A34F31FE9335DA9FABEF76EA714591029C64967506B99860358E5CBF4EDA -A0F25C5387FE9B871E246F33FE64396A5DC0A9BC9AD9DF9E219F3482ADD6497E -0130F99FA395F4364491E6718B53E9D6983B68E29A70035B158CA7629C31C33E -3CDAEDA88534C2E31D1235C99DEC466221C89F63E1F8F59C9CE224573E39E2D4 -F5ED2863D8B51DA620484CDE23D590F4A208DA6D155E645FCD6BCF5FEB39EE551EAE9C0366F7FFD4CBF1DCA063D154E1 diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example deleted file mode 100644 index df1e9ff612..0000000000 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example +++ /dev/null @@ -1,16 +0,0 @@ -# Rename or Create new file with name keeloq_mfcodes_user and copy contents of that file, remove sample keys and add yours -# keep empty line at the end of that file! -# -# for adding manufacture keys -# AABBCCDDEEFFAABB:X:NAME\r\n -# AABBCCDDEEFFAABB - man 64 bit -# X - encryption method - 1 - Simple Learning, 2 - Normal_Learning, 3 - Secure_Learning, -# 4 - Magic_xor_type1 Learning, 5 - FAAC SLH, -# 6 - Magic Serial typ1, 7 - Magic Serial typ2, 8 - Magic Serial typ3 -# 0 - iterates over both previous and man in direct and reverse byte sequence -# NAME - name (string without spaces) max 64 characters long -Filetype: Flipper SubGhz Keystore File -Version: 0 -Encryption: 0 -AABBCCDDEEFFAABB:1:Test1 -AABBCCDDEEFFAABB:1:Test2 diff --git a/applications/main/subghz/resources/subghz/assets/nice_flor_s b/applications/main/subghz/resources/subghz/assets/nice_flor_s deleted file mode 100644 index 2e4e2fa8c6..0000000000 --- a/applications/main/subghz/resources/subghz/assets/nice_flor_s +++ /dev/null @@ -1,6 +0,0 @@ -Filetype: Flipper SubGhz Keystore RAW File -Version: 0 -Encryption: 1 -IV: 33 69 62 1D AD 20 1B B8 A1 6C CF 6F 6B 6F D5 18 -Encrypt_data: RAW -9C1D6E6733912B28AC0FF1A191660810BDFF00D19BF7839AFF5B2AAFBBC91A38 \ No newline at end of file diff --git a/applications/main/subghz/resources/subghz/assets/setting_user.example b/applications/main/subghz/resources/subghz/assets/setting_user.example deleted file mode 100644 index 5034659be6..0000000000 --- a/applications/main/subghz/resources/subghz/assets/setting_user.example +++ /dev/null @@ -1,29 +0,0 @@ -# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user -Filetype: Flipper SubGhz Setting File -Version: 1 -# Add Standard frequencies included with firmware and place user frequencies after them -#Add_standard_frequencies: true - -# Default Frequency: used as default for "Read" and "Read Raw" -#Default_frequency: 433920000 - -# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer" -#Frequency: 300000000 -#Frequency: 310000000 -#Frequency: 320000000 - -# Frequencies used for hopping mode (keep this list small or flipper will miss signal) -#Hopper_frequency: 300000000 -#Hopper_frequency: 310000000 -#Hopper_frequency: 310000000 - -# Custom preset -# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register - -#Custom_preset_name: AM_1 -#Custom_preset_module: CC1101 -#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 - -#Custom_preset_name: AM_2 -#Custom_preset_module: CC1101 -#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index f1d0324fcc..010e470622 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -111,7 +111,7 @@ void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool loc SubGhzViewReceiverModel * model, { model->bar_show = SubGhzViewReceiverBarShowLock; }, true); - furi_timer_start(subghz_receiver->timer, 1000); + furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); } else { with_view_model( subghz_receiver->view, @@ -453,7 +453,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowToUnlockPress; }, true); if(subghz_receiver->lock_count == 0) { - furi_timer_start(subghz_receiver->timer, 1000); + furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); } if(event->key == InputKeyBack && event->type == InputTypeShort) { subghz_receiver->lock_count++; @@ -467,7 +467,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowUnlock; }, true); //subghz_receiver->lock = false; - furi_timer_start(subghz_receiver->timer, 650); + furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(650)); } return true; From e244bedd64c6ab454ee7d95ae057c39b6ca68db1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:41:14 +0400 Subject: [PATCH 026/111] resources rework pt1 --- applications/main/subghz/application.fam | 1 + .../resources/subghz/assets/alutech_at_4n | 6 + .../subghz/assets/dangerous_settings | 0 .../resources/subghz/assets/keeloq_mfcodes | 59 + .../subghz/assets/keeloq_mfcodes_user.example | 16 + .../resources/subghz/assets/nice_flor_s | 6 + .../subghz/assets/setting_user.example | 29 + .../ibtnfuzzer/example_uids_cyfral.txt | 8 - .../ibtnfuzzer/example_uids_ds1990.txt | 11 - .../ibtnfuzzer/example_uids_metakom.txt | 9 - assets/resources/infrared/assets/fans.ir | 2105 ----------------- .../resources/infrared/assets/projectors.ir | 1497 ------------ .../resources/music_player/Marble_Machine.fmf | 6 - .../rfidfuzzer/example_uids_em4100.txt | 8 - .../rfidfuzzer/example_uids_h10301.txt | 8 - .../rfidfuzzer/example_uids_hidprox.txt | 8 - .../resources/rfidfuzzer/example_uids_pac.txt | 8 - .../subplaylist/example_playlist.txt | 5 - assets/resources/swd_scripts/100us.swd | 1 - assets/resources/swd_scripts/call_test_1.swd | 6 - assets/resources/swd_scripts/call_test_2.swd | 7 - .../swd_scripts/dump_0x00000000_1k.swd | 6 - .../swd_scripts/dump_0x00000000_4b.swd | 6 - assets/resources/swd_scripts/dump_STM32.swd | 6 - assets/resources/swd_scripts/goto_test.swd | 7 - assets/resources/swd_scripts/halt.swd | 11 - assets/resources/swd_scripts/reset.swd | 8 - assets/resources/swd_scripts/test_write.swd | 3 - 28 files changed, 117 insertions(+), 3734 deletions(-) create mode 100644 applications/main/subghz/resources/subghz/assets/alutech_at_4n rename {assets => applications/main/subghz}/resources/subghz/assets/dangerous_settings (100%) create mode 100644 applications/main/subghz/resources/subghz/assets/keeloq_mfcodes create mode 100644 applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example create mode 100644 applications/main/subghz/resources/subghz/assets/nice_flor_s create mode 100644 applications/main/subghz/resources/subghz/assets/setting_user.example delete mode 100644 assets/resources/ibtnfuzzer/example_uids_cyfral.txt delete mode 100644 assets/resources/ibtnfuzzer/example_uids_ds1990.txt delete mode 100644 assets/resources/ibtnfuzzer/example_uids_metakom.txt delete mode 100644 assets/resources/infrared/assets/fans.ir delete mode 100644 assets/resources/infrared/assets/projectors.ir delete mode 100644 assets/resources/music_player/Marble_Machine.fmf delete mode 100644 assets/resources/rfidfuzzer/example_uids_em4100.txt delete mode 100644 assets/resources/rfidfuzzer/example_uids_h10301.txt delete mode 100644 assets/resources/rfidfuzzer/example_uids_hidprox.txt delete mode 100644 assets/resources/rfidfuzzer/example_uids_pac.txt delete mode 100644 assets/resources/subplaylist/example_playlist.txt delete mode 100644 assets/resources/swd_scripts/100us.swd delete mode 100644 assets/resources/swd_scripts/call_test_1.swd delete mode 100644 assets/resources/swd_scripts/call_test_2.swd delete mode 100644 assets/resources/swd_scripts/dump_0x00000000_1k.swd delete mode 100644 assets/resources/swd_scripts/dump_0x00000000_4b.swd delete mode 100644 assets/resources/swd_scripts/dump_STM32.swd delete mode 100644 assets/resources/swd_scripts/goto_test.swd delete mode 100644 assets/resources/swd_scripts/halt.swd delete mode 100644 assets/resources/swd_scripts/reset.swd delete mode 100644 assets/resources/swd_scripts/test_write.swd diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 7494e6fba5..6ec3012477 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -17,6 +17,7 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=1, + resources="resources", fap_libs=["assets", "hwdrivers"], fap_icon="icon.png", fap_category="Sub-GHz", diff --git a/applications/main/subghz/resources/subghz/assets/alutech_at_4n b/applications/main/subghz/resources/subghz/assets/alutech_at_4n new file mode 100644 index 0000000000..5d7beacec4 --- /dev/null +++ b/applications/main/subghz/resources/subghz/assets/alutech_at_4n @@ -0,0 +1,6 @@ +Filetype: Flipper SubGhz Keystore RAW File +Version: 0 +Encryption: 1 +IV: 88 64 A6 A6 44 47 67 8A D6 32 36 F6 B9 06 57 31 +Encrypt_data: RAW +E811BD4F0955D217AE6677906E799D45D8DAAFD1F7923E1660B5E24574631B60 \ No newline at end of file diff --git a/assets/resources/subghz/assets/dangerous_settings b/applications/main/subghz/resources/subghz/assets/dangerous_settings similarity index 100% rename from assets/resources/subghz/assets/dangerous_settings rename to applications/main/subghz/resources/subghz/assets/dangerous_settings diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes new file mode 100644 index 0000000000..27ab5aaafd --- /dev/null +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -0,0 +1,59 @@ +Filetype: Flipper SubGhz Keystore File +Version: 0 +Encryption: 1 +IV: AD 0B A4 A1 51 C0 C0 41 36 78 26 82 17 24 9D 62 +0D18FF475E57A67CC5C0B430664E8EF6E07CB6AF72454995F17DE84E2E876D87 +C9BC55E1E3A9B312E341D7E2663C66C2479D5C51AE2EB83BAE47D8C6C79DB8A0 +776E01A7B4FCB929FA59CFE1F16D2D600F6FD9FD8BE1E9E41667144E61E023E4 +C354F854F14AAC08C6A51606582CD73EECEED54779927DC1E04A0D72C4E6A58E +09BA4DC551CDB0141F4A053133DBF9B7B99CBAA402C7B6B2AC8ADB516CA2FCB29FF9744DD95FB009BED6A09AE3303317 +675777F65042358100A9A2BE142C42E10CC5CF98CA6BFF82284FFD9BF7E7FB11 +4739972DCC08EFF0E8547694A67E116F3EC8638F286E333E7D89F2D61218F65D +FE8D5A0A9ED36545004CEE70D3C1FE477F34212364CF7C9EBA0FDD4F6B8F1D3E +453D5C6FE0EA7FD779B54696E66DE6BD6AEF3B1EE347142A3DB7505609077219 +A52339EE40D046E7DF6C130CEC7F9F686753A73A5F72EF344E01B00C3A3CF5F2 +94029CE386CC0403984F7C9D80B40FF12BE6E50412891FD45B347742340D1E6E679102DEC6F7D36C36863701A2BBDA7B +793633A3D97D704779E2E841A5F616ED0BB0BD50740C0B196D72C6C6453124183CA93ADCAC1A5042EBD579AC238B9DBC +B1C6EBB7320CAE97E28A244228B288BEEA44C9FD262E2C448219DAC6E194CB14 +A6C374B3FB7E8BB6F3052ED9DB67E5BCFA6CCC42F5A0D7F20BEDB7C0B58592A5 +DF4FCBDCA3414B7551797A856048625186B2EC7B836EB35B4B080CEE4A72C39F +4127F58BDEB18D79EA8317B1858F7575CF4B648E3C53338975B4A093FA9730C0 +294659EBD39D7CC01DC4E0E3B0B90A3555704E736D3BE4485BAAACBC58A84652CF3A7DA3C271E3198C72561332F2A141 +9E2CC77756BE7AB3D500C0B4A91271C0A4DED5BCB78484217EA922B7878AA8F5 +BF13D9949C783CF6B2F0E489A6912E56DDD9183D651D7F36FD0342981596A85C +8441C62006E1334FC31F53DAFF5281260C463AF3E92B1E797CB67882C49566846825606804C14C49FFD440F4E05CE692 +BE7E62E9CFA1C703CCB971F2B0C6C0F339C78A951CA8DB287B40C3BA76EA7179E8B62C29EAC8293FD218CF981BB84BEF +DA53C52101FAC8FD8F9545768CF1DB59F3B31C9249A2FC64B66DB259C721ABE1 +C9842AD0A8A8E94FCE94F46BDE89DAA8D11C41F9C83A7F6394D595028829AB92 +904A94542BCD85F72D470640A220FD28EBC337C0673C189C96BFB8CE373B4F84 +EEEE187B03FE77B955F22214707017DB8A60B2E3979D5C2F6BED61D0623D4D50 +CEF91BE6EBD8F331E2422B6B948053E8DA143F3DD907C922E0328D49B0ADA8A6 +A6F0F8A4F03B45062CEA86B291E80EE15906C0B226ABDB77E222EB95B026952D +62280870E48A2E8B8A18AD5DFF0089E927BEACE1F81A8BB1A8A2BE8EB0E92BFD +C9A3DE8165D47D1D41715F00192313755226C1B0A3D04BCD7A121B1CB4999FBD +3CAEEB62FCF601F34D3320E3EACE9EFC5D627BF69FEE965F8B84A258A765B6FE +8D83CC36514CE5CC46B181AE28089BE5BC99CE3096C569751B4E07A7D956AFA3 +0F81DDB18F60E238C7FF751E70431AA5328EFDBBDFF03D5F360A524AA968CEE9 +1F1E498A279E9BA15CB6BB836E90FEE522F2BE16572A4D057DF9C2B104487458 +68C60FF95056BE4D814CE9B8A4175625DB3E365712C2D3E42CEEBA2E0977CDB9 +D95FB21943FCAD5A21F157E629314986D92F568FD3067B65911135E6335FB7E8 +17908EF142C4B6740CBE7DCA43DA3C23C7912739299786473A994E5752E7E9459B23653EE0D7775FCECB7B7608CB0495 +6D17B6BA17DBFFAA4E90953CE6A73AB967076B8FBC14A9361D93A01C0850CC38 +8723740BCC0A5CCEE52B6EF73DD441672FB6728965CC588044C78D495AB0675F +CA548FA44A444C8F45490A11DBC8FB24E6DB38F910EC60520A3890C8B45664FE +591356440344AAFEC21FAA0C85B6A8F354D45074932E37E0972F851E08937469 +DE3A54CAAC8014625EE502F547A93754AFAD0A7EB6028599D03CCB0473BC8D5C +B2C2F6F971034E1591AD374793D6D17A595D6544DF5A9585780C6B2E3505BCFF +54447BE6C626D1CA37FD799B76B35ED266D12757B5DA1AB9277F671BBF7F07B461D0CE26593F1372354979E836972F85 +45FBD88AA7C26B967BC3638F6083A6B83AA82D5B974B37DF1C3F52839DDA020C +33B9890FBE46FDB7AEE404B71C893DC20059F96224CF48F284A66D3A8D91918E +CCEA5BC148BC84DB4825320A2B8D39A30BAB4641FFEE33BD4B8700339F15892D +4BC4E0D1263E9B02DA401C884923778D1A6FACACFFE7F660381ECD64CCA5CCD2 +0284EA911FB1B37F623F92B1662E10D79AEBD0009C107A9C554D417F7553B28E +FE48F26C44FA4C3EB3B2F3497FE99AC30A0A7BF9FF261740E177D7A2A5BD7880 +1EA96FEBB62543A8731D19BBDD1C7FE323CADBAB7242E5A8F4B1D706964B4C32 +4AB7EEF02FD59EA5D16837A50282A6254B93A34F31FE9335DA9FABEF76EA714591029C64967506B99860358E5CBF4EDA +A0F25C5387FE9B871E246F33FE64396A5DC0A9BC9AD9DF9E219F3482ADD6497E +0130F99FA395F4364491E6718B53E9D6983B68E29A70035B158CA7629C31C33E +3CDAEDA88534C2E31D1235C99DEC466221C89F63E1F8F59C9CE224573E39E2D4 +F5ED2863D8B51DA620484CDE23D590F4A208DA6D155E645FCD6BCF5FEB39EE551EAE9C0366F7FFD4CBF1DCA063D154E1 diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example new file mode 100644 index 0000000000..df1e9ff612 --- /dev/null +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example @@ -0,0 +1,16 @@ +# Rename or Create new file with name keeloq_mfcodes_user and copy contents of that file, remove sample keys and add yours +# keep empty line at the end of that file! +# +# for adding manufacture keys +# AABBCCDDEEFFAABB:X:NAME\r\n +# AABBCCDDEEFFAABB - man 64 bit +# X - encryption method - 1 - Simple Learning, 2 - Normal_Learning, 3 - Secure_Learning, +# 4 - Magic_xor_type1 Learning, 5 - FAAC SLH, +# 6 - Magic Serial typ1, 7 - Magic Serial typ2, 8 - Magic Serial typ3 +# 0 - iterates over both previous and man in direct and reverse byte sequence +# NAME - name (string without spaces) max 64 characters long +Filetype: Flipper SubGhz Keystore File +Version: 0 +Encryption: 0 +AABBCCDDEEFFAABB:1:Test1 +AABBCCDDEEFFAABB:1:Test2 diff --git a/applications/main/subghz/resources/subghz/assets/nice_flor_s b/applications/main/subghz/resources/subghz/assets/nice_flor_s new file mode 100644 index 0000000000..2e4e2fa8c6 --- /dev/null +++ b/applications/main/subghz/resources/subghz/assets/nice_flor_s @@ -0,0 +1,6 @@ +Filetype: Flipper SubGhz Keystore RAW File +Version: 0 +Encryption: 1 +IV: 33 69 62 1D AD 20 1B B8 A1 6C CF 6F 6B 6F D5 18 +Encrypt_data: RAW +9C1D6E6733912B28AC0FF1A191660810BDFF00D19BF7839AFF5B2AAFBBC91A38 \ No newline at end of file diff --git a/applications/main/subghz/resources/subghz/assets/setting_user.example b/applications/main/subghz/resources/subghz/assets/setting_user.example new file mode 100644 index 0000000000..5034659be6 --- /dev/null +++ b/applications/main/subghz/resources/subghz/assets/setting_user.example @@ -0,0 +1,29 @@ +# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user +Filetype: Flipper SubGhz Setting File +Version: 1 +# Add Standard frequencies included with firmware and place user frequencies after them +#Add_standard_frequencies: true + +# Default Frequency: used as default for "Read" and "Read Raw" +#Default_frequency: 433920000 + +# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer" +#Frequency: 300000000 +#Frequency: 310000000 +#Frequency: 320000000 + +# Frequencies used for hopping mode (keep this list small or flipper will miss signal) +#Hopper_frequency: 300000000 +#Hopper_frequency: 310000000 +#Hopper_frequency: 310000000 + +# Custom preset +# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register + +#Custom_preset_name: AM_1 +#Custom_preset_module: CC1101 +#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 + +#Custom_preset_name: AM_2 +#Custom_preset_module: CC1101 +#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 diff --git a/assets/resources/ibtnfuzzer/example_uids_cyfral.txt b/assets/resources/ibtnfuzzer/example_uids_cyfral.txt deleted file mode 100644 index 497d2211a3..0000000000 --- a/assets/resources/ibtnfuzzer/example_uids_cyfral.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Example file, P.S. keep empty line at the end! -0000 -F000 -FE00 -CAFE -00CA -FF00 -FFFF diff --git a/assets/resources/ibtnfuzzer/example_uids_ds1990.txt b/assets/resources/ibtnfuzzer/example_uids_ds1990.txt deleted file mode 100644 index 6828bb4238..0000000000 --- a/assets/resources/ibtnfuzzer/example_uids_ds1990.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Example file, P.S. keep empty line at the end! -0000000000000000 -FE00000000000000 -CAFE000000000000 -00CAFE0000000000 -0000CAFE00000000 -000000CAFE000000 -00000000CA000000 -0000000000A00000 -00000000000123FF -FFFFFFFFFFFFFFFF diff --git a/assets/resources/ibtnfuzzer/example_uids_metakom.txt b/assets/resources/ibtnfuzzer/example_uids_metakom.txt deleted file mode 100644 index 911ea73b2c..0000000000 --- a/assets/resources/ibtnfuzzer/example_uids_metakom.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Example file, P.S. keep empty line at the end! -00000000 -F0000000 -E0000000 -FE000000 -CAFE0000 -00CAFE00 -0000CA00 -FFFFFFFF diff --git a/assets/resources/infrared/assets/fans.ir b/assets/resources/infrared/assets/fans.ir deleted file mode 100644 index 938bc96a60..0000000000 --- a/assets/resources/infrared/assets/fans.ir +++ /dev/null @@ -1,2105 +0,0 @@ -Filetype: IR library file -Version: 1 -#Last Updated 1st Oct, 2023 -#Last Checked 1st Oct, 2023 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1301 370 1299 371 558 1107 1301 371 1272 398 557 1108 562 1107 562 1107 561 1108 560 1110 559 1110 1272 7254 1269 423 1244 425 530 1140 1242 427 1241 428 527 1142 527 1142 527 1142 527 1142 527 1142 527 1142 1241 7285 1241 428 1241 428 528 1142 1240 428 1241 428 528 1142 527 1142 527 1142 527 1142 527 1142 527 1142 1240 7284 1240 428 1241 429 527 1142 1241 429 1240 429 527 1142 527 1142 528 1142 527 1142 527 1142 527 1142 1240 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1268 400 1267 424 505 1164 1243 426 1242 427 504 1166 503 1166 503 1166 528 1141 1242 428 503 1166 503 8024 1241 427 1241 428 528 1141 1241 428 1241 428 503 1166 503 1167 502 1167 527 1142 1241 428 503 1166 503 8024 1241 427 1242 427 504 1167 1241 428 1241 428 503 1167 503 1167 503 1166 528 1142 1242 428 503 1167 502 8024 1241 428 1241 428 503 1167 1241 428 1241 428 503 1167 503 1167 503 1167 503 1167 1241 428 528 1142 503 8025 1240 429 1240 429 502 1167 1241 429 1240 429 502 1167 503 1167 503 1167 502 1167 1240 429 502 1168 501 8026 1239 430 1239 430 501 1169 1238 431 1238 430 501 1170 524 1170 475 1171 499 1170 1237 456 475 1195 474 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1242 425 1242 401 554 1140 1242 426 1243 428 504 1165 504 1167 1240 427 530 1141 530 1139 504 1167 527 7998 1241 427 1242 427 503 1166 1241 427 1242 428 529 1141 503 1167 1239 429 528 1141 503 1168 529 1140 528 7999 1240 428 1241 427 504 1167 1240 429 1239 429 528 1142 502 1167 1241 428 503 1167 503 1166 528 1142 528 7999 1240 429 1240 429 501 1168 1240 428 1242 427 528 1143 527 1142 1241 428 503 1167 502 1167 528 1142 502 8026 1239 428 1242 428 528 1142 1215 455 1240 427 504 1169 500 1168 1241 427 503 1168 527 1141 504 1166 530 7999 1239 429 1240 429 503 1167 1240 430 1239 430 502 1168 502 1168 1238 430 503 1169 526 1142 503 1167 502 8026 1239 429 1240 429 502 1168 1239 430 1241 428 502 1168 502 1168 1239 429 502 1167 503 1167 502 1168 502 8025 1239 428 1241 429 527 1143 1239 428 1241 429 502 1168 526 1142 1242 428 502 1167 502 1168 502 1167 502 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9086 4339 707 443 681 445 681 422 704 445 681 445 681 445 681 445 681 445 656 1563 683 1563 682 1563 707 1539 706 1562 682 1562 682 1564 680 1565 679 1566 679 448 679 1567 678 448 679 448 679 448 679 1566 679 448 679 448 678 1566 679 448 678 1566 678 1566 678 1566 678 448 678 1566 679 39846 9058 2132 679 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9083 4342 705 446 679 447 679 447 680 447 680 447 679 425 702 447 679 447 654 1564 681 1564 680 1565 680 1591 680 1565 680 1565 679 1565 679 1566 678 1568 677 1569 676 1569 676 451 676 450 677 450 677 1569 676 451 676 451 675 450 677 450 677 1570 675 1570 675 1569 676 451 676 1569 676 39854 9056 2136 677 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9267 4339 708 443 682 445 682 422 734 416 711 416 711 416 711 415 712 415 658 1563 682 1564 682 1565 681 1587 684 1562 683 1563 683 1563 682 1564 681 1565 680 447 679 447 679 1566 679 1566 680 447 680 447 680 447 680 447 680 1566 679 1566 679 447 680 447 680 1566 679 1566 679 1566 679 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9085 4343 705 446 679 447 680 447 680 447 679 425 701 447 679 423 703 446 655 1563 682 1563 681 1565 679 1589 681 1564 680 1563 680 1564 679 1566 678 449 677 450 676 1568 677 1568 677 1569 676 450 676 450 676 450 677 1568 677 1568 677 450 676 450 676 450 677 1568 676 1568 677 1568 676 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1284 419 1288 442 417 1258 1288 415 1282 448 411 1264 443 1258 439 1263 444 1258 1288 415 444 1258 439 8679 1284 419 1288 441 407 1267 1289 441 1256 447 412 1262 435 1267 440 1262 435 1293 1263 413 435 1267 440 8679 1284 418 1289 441 407 1267 1289 414 1283 447 412 1262 435 1267 440 1261 436 1267 1289 413 435 1267 440 8678 1285 418 1289 440 408 1267 1289 440 1257 447 412 1262 435 1267 440 1262 435 1268 1288 414 434 1268 439 8679 1284 418 1289 440 408 1267 1289 413 1284 445 414 1261 436 1266 441 1260 437 1266 1290 412 436 1266 441 8676 1287 415 1282 448 411 1264 1282 421 1286 443 416 1258 439 1263 434 1267 440 1262 1284 419 440 1262 435 8683 1280 422 1285 444 415 1287 1259 444 1253 449 410 1292 415 1285 412 1264 443 1259 1287 416 443 1259 438 8680 1284 420 1287 414 434 1268 1288 415 1282 447 412 1289 408 1267 440 1262 435 1267 1289 414 434 1268 439 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1283 420 1287 443 416 1259 1287 443 1254 449 410 1292 415 1260 437 1266 1290 439 409 1266 442 1261 436 8684 1290 413 1284 446 413 1263 1283 447 1261 442 417 1258 439 1263 444 1259 1287 442 417 1259 438 1264 443 8677 1287 416 1281 449 410 1265 1281 449 1259 444 415 1260 437 1265 442 1260 1286 443 416 1260 437 1265 442 8676 1288 415 1282 447 412 1263 1283 447 1261 442 417 1257 440 1263 434 1268 1288 441 418 1257 440 1263 434 8685 1289 414 1283 447 412 1263 1283 447 1261 442 417 1258 439 1263 445 1258 1288 442 417 1258 439 1264 444 8676 1288 415 1282 448 411 1264 1282 448 1260 444 415 1259 438 1264 443 1260 1286 417 442 1260 437 1265 442 8677 1286 416 1281 449 410 1265 1281 422 1285 444 415 1260 437 1265 442 1260 1286 417 442 1260 437 1265 442 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1281 422 1285 444 415 1260 1286 444 1263 439 409 1266 441 1261 1285 445 414 1261 436 1266 441 1261 436 8682 1281 421 1286 444 415 1260 1286 445 1262 439 409 1266 441 1261 1285 445 414 1261 436 1266 441 1260 437 8683 1280 422 1286 444 415 1261 1285 445 1262 440 408 1267 440 1262 1284 446 413 1262 435 1268 439 1262 435 8684 1279 423 1284 445 414 1261 1285 445 1263 439 409 1265 442 1260 1286 444 415 1260 437 1265 442 1259 438 8682 1281 421 1286 443 416 1259 1287 443 1254 448 411 1264 443 1259 1287 443 416 1259 438 1265 442 1260 437 8682 1281 421 1286 444 415 1260 1286 444 1253 450 409 1266 441 1261 1285 445 414 1261 436 1266 441 1261 436 8683 1290 413 1284 445 414 1262 1283 446 1261 441 407 1294 413 1262 1284 420 439 1263 434 1267 440 1262 435 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1294 371 1351 322 443 1200 1295 346 1297 373 444 1224 445 1198 1300 345 498 1170 473 1171 496 1173 471 8057 1295 372 1297 347 444 1224 1296 347 1296 349 494 1172 470 1174 1293 376 469 1174 469 1200 468 1175 468 8082 1293 351 1318 349 468 1175 1292 377 1294 349 468 1177 491 1175 1293 350 442 1226 468 1175 468 1201 467 8083 1293 350 1268 401 467 1175 1293 351 1318 350 467 1175 442 1226 1293 350 467 1177 466 1200 468 1176 441 8133 1292 351 1267 400 468 1175 1292 351 1292 376 466 1177 467 1202 1291 352 442 1202 465 1203 441 1201 442 8132 1267 376 1267 402 440 1202 1266 377 1290 379 439 1226 416 1253 1241 402 415 1228 439 1229 414 1228 415 8161 1239 403 1240 404 439 1229 1240 403 1240 429 414 1229 414 1231 1264 404 413 1229 414 1255 413 1229 413 8161 1238 404 1239 405 437 1230 1239 404 1238 430 412 1230 412 1232 1262 405 412 1231 412 1256 412 1231 412 8162 1238 406 1237 406 411 1258 1237 406 1237 431 412 1256 387 1256 1213 456 386 1256 386 1257 412 1256 386 8164 1237 431 1212 431 386 1283 1212 431 1212 432 411 1257 386 1257 1211 457 386 1257 386 1258 411 1258 385 8164 1212 431 1212 432 411 1256 1213 431 1236 433 386 1256 387 1257 1238 430 386 1257 386 1282 386 1256 386 -#ON/Speed_up -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1330 364 1385 320 523 1158 1332 365 1329 364 479 1213 481 1213 481 1213 481 1213 481 1213 1332 390 480 8072 1358 362 1329 364 478 1216 1328 366 1328 367 476 1218 476 1218 476 1218 476 1218 476 1218 1328 367 476 8103 1328 367 1327 367 476 1218 1327 367 1327 367 476 1218 476 1218 476 1218 476 1218 476 1218 1327 367 476 8103 1327 367 1327 367 476 1218 1327 368 1326 367 476 1218 476 1219 475 1219 475 1219 475 1219 1326 368 475 8104 1327 368 1326 368 475 1219 1327 368 1326 368 475 1219 475 1219 475 1219 475 1219 475 1219 1326 368 475 8105 1326 368 1326 368 475 1219 1327 368 1326 368 475 1220 474 1220 474 1220 475 1220 474 1220 1325 368 475 8105 1326 369 1325 369 474 1220 1326 369 1325 369 474 1220 474 1220 474 1220 474 1220 474 1220 1326 369 474 8106 1325 369 1325 369 474 1220 1325 369 1326 369 474 1220 474 1220 474 1220 474 1220 474 1221 1324 370 473 8132 1273 422 1272 422 446 1248 1272 422 1297 397 421 1275 419 1300 419 1276 419 1275 419 1250 1297 398 445 8135 1297 397 1297 397 447 1247 1298 397 1297 397 447 1247 447 1247 447 1247 447 1247 447 1247 1298 397 447 8134 1298 397 1298 397 447 1248 1297 397 1297 397 447 1248 446 1248 446 1248 447 1248 447 1248 1297 397 447 8133 1298 397 1297 397 447 1248 1297 397 1297 397 447 1248 446 1248 447 1248 446 1248 446 1248 1297 398 446 8134 1297 398 1296 398 446 1248 1297 398 1296 398 446 1248 446 1248 446 1248 446 1248 446 1248 1296 398 446 8134 1295 398 1296 398 446 1248 1296 398 1296 398 446 1249 445 1249 445 1249 445 1249 445 1249 1295 399 445 8134 1295 399 1295 399 445 1249 1295 399 1295 399 445 1249 445 1249 445 1250 444 1249 445 1249 1295 399 445 8135 1295 400 1294 400 444 1250 1294 400 1295 400 444 1250 444 1250 444 1250 444 1250 444 1250 1294 401 443 8137 1293 401 1294 401 442 1251 1294 401 1293 401 442 1253 442 1252 442 1252 442 1252 442 1252 1293 427 417 8140 1292 426 1268 427 417 1277 1243 452 1267 427 417 1278 417 1277 417 1278 417 1277 417 1277 1268 427 417 8163 1242 452 1242 452 417 1278 1242 453 1241 453 391 1303 391 1303 392 1303 391 1303 391 1303 1242 453 415 8165 1241 453 1241 453 391 1304 1241 453 1241 454 390 1305 389 1304 390 1304 390 1305 389 1305 1240 480 364 8216 1214 480 1214 480 363 1331 1214 480 1214 480 364 1331 363 1331 363 1331 363 1331 363 1331 1214 481 363 8218 1213 481 1213 481 363 1333 1212 508 1186 508 335 1359 335 1359 335 1359 335 1359 335 1360 1186 508 335 8247 1184 509 1185 535 307 1387 1159 536 1158 589 253 1389 306 1415 278 1416 252 1442 199 1575 1052 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1318 346 1298 324 494 1201 1291 345 1297 375 466 1174 470 1200 442 1201 467 1201 441 1201 466 1204 1265 7229 1373 322 1266 375 466 1202 1267 325 1318 378 465 1200 442 1202 465 1202 442 1201 441 1202 467 1171 1297 7281 1268 325 1399 323 439 1200 1293 348 1351 322 441 1202 522 1145 498 1172 439 1204 496 1171 471 1148 1346 7227 1323 322 1266 325 574 1145 1322 322 1347 322 496 1115 528 1171 497 1145 497 1148 521 1145 497 1145 1291 7285 1322 322 1321 323 521 1147 1321 322 1346 322 496 1172 470 1147 522 1145 498 1146 519 1149 497 1171 1297 7252 1323 322 1321 323 520 1144 1323 322 1343 323 498 1144 498 1147 522 1146 496 1146 571 1123 471 1145 1323 7229 1345 322 1321 322 570 1097 1322 323 1320 323 521 1146 496 1146 519 1148 498 1145 498 1149 520 1145 1322 7229 1347 323 1320 322 464 1202 1323 323 1320 322 522 1145 497 1145 466 1228 471 1116 527 1147 522 1147 1321 7226 1348 322 1266 375 442 1202 1292 348 1295 375 523 1146 442 1201 442 1202 466 1200 442 1200 467 1202 1268 7281 1292 375 1270 346 471 1201 1293 373 1270 374 523 1145 445 1199 443 1200 469 1199 443 1200 524 1144 1269 7258 1292 374 1269 374 468 1200 1269 374 1269 400 443 1200 443 1201 467 1201 498 1146 497 1172 497 1145 1324 -# -name: Power -type: parsed -protocol: NECext -address: 4B 14 00 00 -command: 80 7F 00 00 -# -name: Mode -type: parsed -protocol: NECext -address: 4B 14 00 00 -command: 20 DF 00 00 -# -name: Rotate -type: parsed -protocol: NECext -address: 4B 14 00 00 -command: C0 3F 00 00 -# -name: Timer -type: parsed -protocol: NECext -address: 4B 14 00 00 -command: A0 5F 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9137 4430 630 1594 661 464 659 1625 630 1624 631 1625 630 1596 659 1596 660 1624 631 492 631 1624 632 1623 632 1593 662 1597 658 1624 631 1625 630 1595 660 491 632 1595 661 1595 660 1624 631 1624 631 1596 660 1624 631 1624 631 1593 663 435 689 491 632 434 690 491 632 491 632 463 661 490 634 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9107 4400 660 1622 633 464 659 1623 632 1596 659 1622 633 1622 633 1623 632 1622 633 493 630 1593 662 1595 660 1623 632 1593 662 1623 632 1596 659 1623 632 1622 633 420 703 1594 661 424 699 1592 663 1594 661 1595 660 1597 658 423 700 1623 632 489 634 1593 662 461 662 431 692 488 635 489 634 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9106 4428 632 1595 660 463 660 1595 660 1622 633 1593 662 1622 633 1622 633 1595 660 490 633 1595 660 1595 660 1592 663 1593 662 1623 632 1623 632 1594 661 1623 632 1595 660 1594 661 1596 659 489 634 1595 660 1595 660 1596 659 463 660 490 633 462 662 422 701 1595 660 464 659 490 633 428 695 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9136 4401 659 1624 631 463 660 1624 631 1593 663 1623 632 1624 632 1596 660 1594 661 464 659 1597 658 1593 662 1623 632 1595 660 1623 632 1624 631 1594 662 489 634 1623 633 431 693 1594 662 1623 633 1594 662 1595 661 1593 663 1592 663 461 663 1623 632 463 660 434 689 463 660 463 661 426 698 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9142 4400 661 1593 662 491 632 1624 632 1598 657 1594 662 1594 662 1596 660 1596 660 490 634 1596 660 1596 660 1624 631 1594 662 1623 632 1594 662 1624 632 1624 632 491 632 1624 632 1595 661 1624 631 1624 632 1594 662 1624 631 491 633 1624 632 464 659 492 632 432 692 430 694 464 660 432 692 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9134 4428 632 1597 658 355 768 1624 631 1623 632 1623 632 1597 658 1623 632 1622 633 463 660 1595 660 1622 633 1596 659 1595 660 1622 633 1623 632 1596 659 1622 633 1623 632 436 687 1623 633 1622 633 1622 633 1622 633 1598 657 434 689 489 634 1622 633 466 657 489 634 490 633 490 633 440 684 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8980 4377 620 1599 620 489 620 488 621 488 621 488 622 488 621 488 621 488 621 488 621 1597 622 1598 621 1598 621 1597 622 1597 622 1597 622 1597 622 1597 621 1598 646 463 646 464 645 464 645 465 644 466 643 466 643 466 643 466 643 1575 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 466 643 466 643 466 643 466 643 466 643 466 643 466 644 466 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9011 4375 620 1598 621 462 647 488 621 488 621 488 676 434 621 488 621 489 620 488 621 1598 621 1598 621 1598 621 1598 621 1597 622 1598 621 1597 622 1597 622 1598 621 488 621 489 644 1576 644 466 643 466 643 466 643 466 643 466 644 1576 643 1576 643 466 643 1576 643 1576 643 1576 643 1576 643 1576 643 466 644 466 643 1576 643 466 644 466 643 466 644 466 643 466 643 1575 644 1576 643 466 643 1575 643 1576 643 1576 643 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2262 692 881 585 883 583 903 564 903 1294 882 584 881 584 882 559 820 673 895 1300 882 611 881 584 882 584 882 584 882 585 881 584 881 584 882 585 880 1317 879 586 829 1371 827 640 825 51208 2245 690 772 1424 831 50893 2240 691 772 1429 771 50906 2244 690 773 1400 772 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2264 668 770 721 745 694 772 694 796 1401 826 639 771 694 771 693 772 694 771 1426 798 668 769 1429 795 670 795 1404 794 672 794 1405 767 1431 767 1430 794 672 794 1404 794 1404 794 1404 766 50878 2261 645 817 1405 794 50857 2238 668 795 1428 769 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2264 642 823 669 797 643 822 644 822 1376 822 642 799 668 797 669 823 645 820 1401 796 670 794 1379 792 700 793 1381 790 1407 793 674 791 700 793 1406 792 673 793 673 792 673 793 673 792 50785 2262 666 796 1401 797 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2323 611 796 669 800 664 802 692 773 1424 774 691 801 664 774 691 774 692 773 1423 800 668 822 667 798 1376 769 1427 771 696 769 696 770 1429 795 1404 794 672 794 1404 794 671 795 1404 794 51269 2214 691 769 1427 796 -# -#ON/SPEED -name: Power -type: parsed -protocol: NECext -address: 00 FC 00 00 -command: 84 7B 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1285 426 1258 427 414 1267 1248 436 1258 427 414 1267 406 1276 408 1248 435 1247 437 1271 413 1244 1282 7286 1279 432 1252 433 409 1273 1253 431 1253 432 410 1273 411 1271 413 1243 440 1241 442 1240 433 1275 1251 7292 1283 427 1257 428 413 1268 1258 427 1257 428 414 1268 416 1240 433 1249 434 1248 435 1246 438 1245 1281 7287 1278 406 1278 434 408 1274 1252 432 1252 433 409 1274 410 1246 438 1244 440 1243 440 1241 443 1240 1275 7294 1281 402 1282 430 412 1270 1256 428 1256 429 412 1243 441 1241 443 1240 433 1249 434 1247 436 1246 1280 7288 1277 433 1251 434 408 1248 1278 432 1251 433 409 1273 411 1245 439 1244 440 1242 442 1240 433 1249 1277 7292 1283 400 1304 406 435 1246 1248 436 1279 405 436 1246 406 1249 434 1247 436 1245 438 1244 440 1242 1284 7285 1280 430 1285 400 442 1241 1285 399 1285 400 441 1240 444 1212 440 1241 443 1240 433 1249 434 1273 1284 7259 1306 404 1280 405 436 1245 1281 403 1281 404 438 1244 440 1216 468 1214 438 1244 440 1242 442 1241 1305 7262 1303 408 1276 407 434 1249 1277 407 1276 407 435 1248 436 1220 464 1219 464 1217 466 1242 441 1214 1312 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1280 431 1252 431 410 1244 1281 430 1253 430 411 1270 413 1242 441 1240 433 1249 434 1247 1278 433 408 8129 1277 433 1250 433 408 1274 1251 432 1251 433 409 1273 410 1271 412 1269 414 1241 432 1250 1275 435 406 8133 1283 426 1257 427 414 1267 1248 436 1247 436 405 1276 407 1248 435 1247 436 1245 438 1244 1281 429 412 8126 1280 430 1253 430 411 1270 1255 429 1254 429 412 1269 414 1241 432 1276 407 1248 435 1247 1278 432 410 8129 1277 432 1251 433 408 1273 1252 431 1252 431 411 1272 411 1269 414 1241 442 1240 433 1275 1250 434 408 8130 1276 434 1249 434 407 1275 1250 433 1250 433 408 1274 409 1272 411 1244 439 1268 415 1240 1275 436 405 8133 1283 427 1256 427 414 1267 1248 436 1258 426 405 1277 406 1274 409 1273 410 1245 438 1244 1281 429 412 8125 1281 429 1254 429 412 1269 1256 428 1255 428 413 1268 415 1266 407 1248 435 1247 436 1245 1280 430 411 8127 1279 431 1252 431 411 1271 1254 430 1253 430 412 1270 413 1268 415 1240 433 1275 408 1247 1278 432 410 8128 1278 432 1251 432 410 1272 1253 431 1252 431 410 1271 412 1242 441 1267 406 1250 433 1248 1277 433 409 8129 1277 433 1250 433 409 1272 1253 431 1252 431 410 1271 412 1242 441 1241 432 1250 433 1248 1277 433 409 8129 1277 433 1250 434 408 1246 1279 432 1251 432 409 1245 438 1244 439 1242 441 1240 433 1249 1276 434 407 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2356 608 934 1216 825 1320 826 1348 793 646 824 673 792 667 792 663 792 720 793 692 793 687 818 657 817 652 817 649 816 1310 815 1306 815 102511 2320 668 819 1332 817 1328 817 1325 815 655 815 650 815 645 815 640 815 699 814 671 814 666 814 661 814 657 813 652 813 1337 788 1333 787 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 390 9025 2296 666 821 1329 821 1324 821 1319 821 647 822 642 822 666 793 661 793 1385 793 691 819 660 819 655 818 652 816 1314 815 644 815 1305 815 101869 2318 668 818 1331 817 1328 816 1323 816 654 815 649 815 645 814 640 814 1363 815 670 815 665 814 660 815 655 814 1316 814 645 814 1306 814 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2346 612 875 1275 931 1214 929 1211 875 594 875 588 876 582 822 633 821 691 848 1303 847 659 819 1321 817 652 816 1314 815 1310 814 640 814 101247 2320 667 819 1330 818 1327 816 1324 815 654 815 649 815 644 815 639 815 698 814 1335 815 665 814 1325 814 655 814 1315 815 1310 815 640 814 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2313 646 841 1308 841 1303 841 1298 841 626 843 621 842 614 819 634 820 1359 844 1333 817 1328 815 1324 814 1321 812 1317 812 647 812 1308 811 99132 2317 670 816 1333 815 1330 813 1326 813 657 812 652 812 647 812 642 812 1366 812 1338 811 1332 812 1328 812 1323 811 1317 813 647 812 1307 812 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3533 1663 476 420 450 1234 508 421 449 394 477 420 450 420 474 387 459 421 475 396 558 385 432 386 484 386 485 386 434 1263 479 419 447 395 475 394 476 393 478 393 478 392 478 1264 477 393 478 1264 478 1266 500 387 483 395 475 1268 473 397 474 397 474 397 474 397 474 1268 473 397 474 397 474 397 474 397 474 1268 472 1270 472 398 473 398 473 1269 473 398 473 398 473 397 474 1268 473 1269 473 398 473 398 473 1269 472 398 473 1269 472 398 473 1269 472 398 473 1269 472 398 473 74625 3552 1673 473 397 472 1270 472 399 471 399 471 400 470 400 470 401 470 401 470 401 469 401 470 402 469 402 469 402 469 1272 471 400 470 400 470 401 469 402 468 427 444 427 444 1273 471 400 470 1272 470 1271 471 400 470 401 469 1273 470 401 469 427 443 428 442 428 417 1300 469 426 443 428 417 453 442 428 417 1325 443 1274 468 427 443 428 417 1325 443 427 417 454 416 454 416 1326 417 1325 442 427 417 453 417 1325 417 453 417 1325 418 453 416 1326 416 453 417 1326 416 454 416 -#Timer UP -name: Timer -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 07 00 00 00 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1367 345 1338 347 491 1170 1361 349 1334 350 488 1198 489 1198 488 1199 488 1199 487 1200 1334 352 486 7953 1333 353 1332 353 486 1200 1333 353 1332 353 486 1200 486 1201 486 1200 486 1200 487 1201 1332 353 486 7953 1332 353 1333 353 485 1201 1332 353 1332 353 485 1201 485 1201 485 1201 486 1200 486 1200 1333 353 486 7953 1333 353 1332 354 485 1201 1332 353 1332 353 485 1201 486 1201 485 1201 485 1201 486 1201 1332 354 484 7954 1332 353 1332 353 485 1201 1332 354 1331 354 485 1201 485 1201 486 1201 486 1201 486 1201 1333 354 484 7954 1332 354 1331 354 485 1201 1332 355 1331 354 484 1201 485 1201 486 1202 485 1201 486 1201 1331 355 484 7953 1332 354 1331 354 484 1201 1332 355 1330 355 483 1202 484 1201 486 1201 485 1202 484 1202 1331 355 484 7954 1331 354 1331 354 484 1202 1331 355 1330 354 484 1202 485 1201 485 1202 484 1202 485 1202 1330 355 484 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1367 335 1349 346 492 1166 1365 346 1338 347 491 1170 516 1172 514 1173 1359 349 489 1197 489 1198 488 7950 1334 352 1332 352 486 1200 1333 352 1333 352 486 1200 486 1200 486 1200 1333 352 486 1200 486 1200 487 7953 1332 352 1333 353 485 1200 1333 353 1332 353 485 1200 486 1200 486 1200 1333 353 485 1200 486 1200 487 7951 1333 353 1332 352 486 1200 1333 353 1332 353 486 1200 486 1200 486 1200 1333 353 486 1200 486 1200 486 7952 1333 352 1333 353 485 1200 1333 353 1332 353 486 1200 486 1200 486 1200 1333 353 485 1200 486 1200 486 7952 1332 353 1332 353 485 1200 1333 353 1331 353 485 1200 486 1201 485 1201 1332 353 485 1201 485 1201 486 7952 1331 353 1332 354 484 1201 1332 354 1331 354 484 1201 485 1202 484 1201 1332 354 484 1202 484 1202 485 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1337 374 1311 374 464 1194 1339 373 1340 345 493 1165 521 1165 1365 347 492 1167 519 1168 518 1168 518 7920 1363 347 1336 349 489 1197 1334 352 1333 353 485 1200 486 1200 1333 352 486 1200 486 1200 486 1200 486 -# -name: Power -type: parsed -protocol: NECext -address: BA F0 00 00 -command: 03 FC 00 00 -# -name: Speed_up -type: parsed -protocol: NECext -address: BA F0 00 00 -command: 50 AF 00 00 -# -name: Speed_dn -type: parsed -protocol: NECext -address: BA F0 00 00 -command: 41 BE 00 00 -# -name: Timer -type: parsed -protocol: NECext -address: BA F0 00 00 -command: 40 BF 00 00 -# -name: Power -type: parsed -protocol: NECext -address: BA F0 00 00 -command: 53 AC 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9069 4477 591 1695 622 528 625 525 618 505 617 532 621 527 626 497 625 525 618 532 621 1661 615 1640 595 1688 599 1684 613 1643 592 1691 626 1656 620 1636 620 1663 624 526 617 505 628 523 620 529 624 525 618 504 618 531 622 527 616 1643 623 1662 625 1658 618 1637 598 1685 622 1659 627 1655 570 1685 622 528 625 523 620 502 620 529 624 524 619 503 588 561 623 526 617 1667 619 1639 627 1654 622 1660 616 1638 617 1664 622 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9014 4522 597 1687 610 512 590 559 594 555 619 503 599 550 593 556 597 551 613 509 593 1688 588 1692 615 1639 596 1684 592 1689 587 1666 590 1691 595 1685 591 1689 566 1689 597 551 592 557 638 485 596 553 590 558 595 527 595 554 589 559 594 1686 611 1643 592 1688 588 1692 646 1607 597 1684 592 1688 619 1634 591 558 595 553 590 532 590 558 595 553 590 559 615 506 596 553 590 1693 594 1685 571 1683 593 1686 590 1690 565 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9066 4476 623 1662 625 525 618 504 587 561 623 526 617 531 622 501 621 528 625 524 619 1661 564 1692 625 1655 621 1660 565 1690 627 1654 622 1659 586 1668 618 1663 623 1657 619 1636 620 529 624 524 619 502 620 529 624 524 619 529 614 509 624 525 618 1663 613 1640 626 1655 621 1659 617 1637 619 1662 625 1656 620 1633 623 527 626 523 620 529 614 508 625 524 619 530 623 499 593 557 617 1663 623 1657 619 1635 621 1660 616 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9038 4530 589 1665 590 559 594 555 598 551 613 510 592 558 595 553 590 532 590 559 594 1686 590 1690 617 1637 598 1683 593 1687 620 1634 591 1691 595 1685 622 1632 593 557 596 1684 592 530 592 557 596 552 591 558 616 506 596 553 590 1690 596 526 596 1684 592 1688 588 1692 563 1690 596 1684 592 1687 620 503 589 1691 595 553 621 501 591 558 595 553 590 532 590 558 595 1689 597 551 623 1630 595 1686 590 1689 587 1666 589 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4609 4601 539 1515 545 1511 539 1518 542 1514 546 1510 540 1517 543 2559 541 1515 545 1511 539 2563 537 1519 541 2561 539 1516 544 1512 548 2554 546 1510 540 1515 4604 4605 545 1509 541 1515 545 1538 512 1518 542 1514 546 1511 539 2563 537 1519 541 1515 545 2557 543 1513 547 2555 545 1510 540 1516 544 2559 541 1514 546 13748 9234 2298 545 52033 9228 2304 539 52041 9230 2303 540 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4605 4603 547 1508 541 1515 545 1511 539 1518 542 1514 546 1511 539 2562 538 1518 542 1515 545 1511 538 2564 547 1509 541 1517 543 1513 547 2555 545 2556 544 1512 4607 4603 547 1508 542 1515 545 1511 539 1518 542 1514 546 1511 538 2564 546 1510 539 1517 543 1514 546 2557 543 1513 547 1510 540 1517 543 2559 541 2561 539 13755 9237 2297 546 52031 9229 2306 547 52033 9237 2297 546 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4608 4604 546 1509 541 1516 544 1513 547 1509 540 1516 544 1513 547 2555 545 1512 537 2565 545 1511 539 1518 542 2561 539 1516 544 1513 547 1510 539 2563 537 1517 4612 4599 541 1514 546 1511 539 1518 542 1514 546 1511 539 1518 542 2561 539 1518 542 2561 539 1517 543 1514 546 2557 543 1513 547 1510 540 1516 544 2559 541 13755 9237 2298 545 52044 9237 2298 545 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4606 4604 546 1509 541 1516 544 1512 548 1509 541 1516 544 1512 548 2554 546 1510 540 1517 543 2559 541 1515 545 2557 543 2558 542 1514 546 1511 539 1517 543 1512 4607 4601 539 1516 544 1513 547 1509 541 1516 544 1512 548 1508 542 2561 539 1516 544 1513 547 2555 545 1511 538 2589 522 2555 545 1511 539 1518 542 1515 545 13389 9233 2301 542 51673 9227 2306 547 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1273 401 1272 426 416 1232 1273 427 1247 426 405 1242 442 1231 442 1231 1274 426 405 1242 431 1242 431 7968 1274 400 1273 426 416 1232 1273 426 1247 426 405 1241 432 1241 432 1241 1275 425 406 1267 406 1240 433 7967 1275 398 1275 424 407 1240 1276 424 1249 424 407 1239 434 1238 435 1238 1278 422 409 1237 436 1237 436 7963 1279 394 1279 420 411 1262 1243 430 1243 430 412 1234 439 1234 439 1233 1272 428 414 1232 441 1232 441 7958 1273 426 1248 426 405 1241 1275 426 1248 425 406 1266 407 1240 433 1240 1276 424 407 1265 408 1238 435 7965 1277 422 1251 423 408 1238 1278 423 1250 423 408 1264 409 1238 435 1264 1251 422 409 1264 409 1237 436 7964 1278 422 1251 422 409 1264 1251 422 1251 422 409 1263 410 1262 411 1262 1254 420 411 1262 411 1235 438 7961 1270 429 1244 429 413 1260 1245 428 1245 427 415 1258 415 1231 432 1241 1274 425 406 1267 406 1240 433 7965 1277 423 1250 423 408 1265 1250 423 1250 423 408 1264 409 1237 436 1237 1278 421 410 1262 411 1262 411 7961 1270 429 1244 429 413 1260 1245 429 1244 429 413 1260 413 1233 440 1259 1246 427 415 1259 414 1258 415 7959 1272 427 1246 427 415 1259 1246 427 1246 427 415 1257 406 1267 406 1241 1274 425 406 1266 407 1266 407 7965 1277 422 1251 422 409 1264 1251 421 1252 421 410 1262 411 1261 412 1235 1270 429 413 1260 413 1233 440 7958 1273 426 1247 425 406 1267 1249 425 1248 424 407 1266 407 1265 408 1265 1250 423 408 1264 409 1263 410 7962 1280 420 1253 420 411 1261 1244 430 1243 429 413 1259 414 1232 441 1231 1274 427 415 1257 406 1240 433 7965 1277 423 1250 423 408 1264 1251 422 1251 422 409 1262 411 1235 438 1235 1280 420 411 1260 413 1232 441 7957 1274 426 1247 425 406 1266 1250 425 1248 424 407 1265 408 1238 435 1264 1251 423 408 1263 410 1236 437 -#Osc -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1338 374 1311 374 464 1194 1339 374 1311 374 464 1194 492 1194 492 1194 520 1165 1368 346 492 1167 519 7918 1364 347 1337 348 490 1195 1336 351 1333 352 486 1200 486 1201 485 1201 486 1201 1332 353 485 1201 485 7952 1332 353 1332 353 485 1200 1333 353 1332 353 485 1201 485 1201 485 1201 485 1201 1332 354 485 1201 485 7954 1331 354 1331 354 485 1201 1332 354 1331 354 485 1201 485 1202 485 1202 485 1202 1331 354 485 1201 485 7954 1331 354 1331 354 484 1202 1331 355 1330 355 483 1202 484 1203 483 1203 484 1203 1330 355 484 1203 483 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1280 394 1280 420 411 1236 1269 430 1254 419 412 1235 438 1234 439 1233 440 1233 440 1233 1272 427 415 8311 1279 395 1278 420 411 1236 1269 430 1243 430 412 1234 439 1234 439 1233 440 1233 440 1233 1272 427 415 8312 1277 396 1277 422 409 1264 1252 421 1252 421 410 1236 437 1235 438 1233 440 1259 414 1259 1246 427 415 8312 1277 422 1251 423 408 1264 1251 423 1250 423 408 1238 435 1237 436 1237 436 1263 410 1237 1278 421 410 8315 1274 425 1248 425 406 1240 1276 424 1249 424 407 1265 408 1238 435 1264 409 1237 436 1237 1279 421 410 8318 1271 402 1271 428 414 1233 1272 427 1246 427 415 1231 432 1241 432 1240 433 1240 433 1240 1276 424 407 8319 1271 429 1245 428 414 1233 1272 428 1245 427 415 1231 442 1231 432 1240 433 1240 433 1239 1277 423 408 8318 1271 401 1272 427 415 1232 1273 427 1246 426 405 1267 406 1240 433 1239 434 1238 435 1238 1278 422 409 8317 1272 401 1272 426 405 1242 1274 426 1248 425 406 1240 433 1239 434 1238 435 1238 435 1238 1278 421 410 8317 1272 401 1272 426 405 1242 1274 425 1249 425 406 1240 434 1239 434 1238 435 1238 435 1238 1278 421 410 8316 1273 400 1273 426 405 1241 1275 425 1248 425 406 1239 434 1239 434 1238 435 1238 435 1238 1278 421 410 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1341 395 1311 395 455 1220 1341 395 1311 396 454 1248 456 1248 457 1248 456 1247 457 1247 457 1247 1338 7184 1335 399 1307 399 450 1254 1307 399 1307 400 449 1255 450 1255 450 1254 450 1254 450 1254 450 1254 1307 8332 1306 401 1306 400 449 1256 1306 400 1306 401 449 1255 450 1255 449 1255 449 1255 449 1255 449 1255 1306 7214 1306 400 1307 400 450 1255 1306 400 1306 400 450 1255 449 1255 449 1255 449 1255 449 1255 449 1256 1306 8333 1306 401 1307 401 449 1256 1307 402 1306 402 448 1256 449 1256 448 1256 448 1256 448 1256 448 1256 1307 7215 1305 401 1306 402 448 1256 1306 402 1305 402 448 1257 447 1257 447 1257 447 1256 448 1256 448 1257 1305 8333 1304 402 1305 402 448 1257 1304 402 1304 403 447 1257 447 1257 448 1257 447 1257 447 1257 447 1257 1304 7216 1304 403 1303 403 446 1257 1304 403 1304 403 446 1258 447 1258 446 1257 447 1257 447 1258 447 1257 1304 8334 1304 402 1305 403 446 1257 1304 403 1303 403 447 1258 446 1257 448 1258 446 1258 446 1258 446 1258 1303 7217 1303 404 1303 404 445 1259 1302 404 1302 428 421 1259 446 1283 421 1259 445 1283 421 1283 421 1284 1278 8361 1277 429 1278 429 420 1284 1278 429 1277 429 420 1284 420 1284 420 1284 420 1284 420 1284 419 1285 1277 7243 1277 429 1277 429 421 1284 1277 429 1277 429 420 1284 420 1284 420 1284 420 1284 420 1284 420 1284 1277 8361 1277 430 1276 430 420 1285 1276 430 1277 430 419 1285 419 1285 419 1285 420 1284 420 1285 420 1285 1276 7244 1276 431 1276 430 419 1286 1276 430 1276 431 418 1286 418 1286 418 1286 418 1286 418 1285 419 1285 1276 8363 1275 431 1276 431 418 1286 1275 431 1276 432 417 1286 418 1286 418 1286 418 1286 419 1286 418 1286 1275 7246 1275 432 1275 432 417 1288 1274 457 1249 457 392 1312 392 1312 392 1289 415 1312 392 1312 392 1313 1249 8390 1248 458 1249 458 391 1313 1249 458 1248 458 391 1313 391 1314 390 1313 391 1314 390 1314 390 1314 1248 7273 1247 459 1223 484 390 1315 1247 460 1222 510 339 1365 364 1315 365 1339 390 1315 388 1341 339 1340 1222 8415 1222 484 1222 510 339 1365 1197 510 1197 510 339 1365 340 1365 339 1365 339 1365 339 1365 339 1365 1197 7324 1196 511 1196 511 338 1366 1196 511 1195 511 338 1366 338 1367 337 1367 337 1367 337 1367 337 1367 1195 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1341 368 1338 392 457 1219 1370 364 1341 364 485 1190 540 1164 513 1190 513 1191 512 1194 1365 366 481 8031 1338 370 1336 371 477 1226 1335 371 1335 371 477 1226 477 1226 477 1226 477 1226 477 1226 1334 371 477 9153 1334 371 1334 371 477 1226 1334 371 1335 371 477 1227 476 1227 476 1227 476 1227 476 1227 1334 372 476 8036 1334 372 1333 372 476 1227 1333 372 1333 372 476 1227 476 1227 476 1227 476 1227 476 1227 1334 373 475 9154 1334 373 1333 373 475 1228 1332 373 1332 373 475 1229 475 1228 475 1229 474 1229 474 1229 1331 374 474 8038 1331 374 1332 374 474 1229 1331 374 1331 398 450 1253 450 1253 450 1253 450 1253 450 1253 1307 398 450 9179 1307 398 1307 398 450 1253 1307 399 1306 398 450 1254 449 1254 449 1253 450 1254 449 1254 1306 398 450 8063 1306 398 1307 399 449 1254 1306 399 1306 399 449 1254 449 1254 449 1254 449 1254 449 1254 1307 399 449 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1372 339 1369 363 486 1190 1374 363 1344 363 486 1190 515 1191 513 1191 513 1192 1370 365 483 1220 484 8036 1339 371 1337 371 478 1227 1337 371 1337 371 478 1227 478 1227 478 1227 478 1227 1336 371 478 1227 478 9159 1336 372 1336 371 478 1227 1336 371 1336 372 477 1227 478 1227 477 1227 477 1227 1336 372 477 1228 477 8041 1335 372 1335 372 477 1227 1336 372 1335 372 477 1228 477 1228 477 1228 477 1228 1335 373 476 1228 477 9160 1335 373 1334 373 476 1228 1335 373 1334 373 476 1229 475 1229 476 1229 475 1229 1334 374 475 1229 475 8042 1334 375 1331 398 451 1230 1333 398 1309 398 451 1254 450 1254 451 1253 451 1253 1308 398 450 1254 450 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1342 366 1370 339 511 1191 1373 364 1343 364 511 1165 540 1165 514 1192 1371 364 484 1219 485 1220 484 8036 1339 370 1337 371 478 1227 1336 371 1336 371 478 1227 478 1227 478 1227 1336 372 477 1227 478 1227 478 9162 1336 372 1335 372 477 1228 1335 372 1335 373 476 1228 477 1228 477 1228 1336 372 477 1228 477 1228 476 8044 1336 372 1335 373 476 1228 1335 398 1309 398 451 1254 451 1254 451 1255 1308 400 449 1255 450 1256 449 9188 1306 403 1304 428 421 1260 1303 428 1279 429 419 1284 421 1284 420 1284 1279 429 420 1285 420 1284 420 8099 1276 457 1250 457 391 1313 1225 483 1224 484 364 1338 366 1338 392 1313 1225 484 364 1339 366 1339 390 9244 1225 509 1198 510 338 1366 1197 510 1197 537 311 1367 338 1393 311 1394 1169 591 256 11618 1170 539 1168 565 282 1395 1169 565 1142 538 310 1341 365 1312 419 1259 1305 400 449 1254 450 1255 449 9186 1307 400 1306 400 448 1255 1307 399 1307 400 448 1255 449 1255 449 1255 1307 400 448 1255 449 1255 449 8068 1306 400 1306 400 448 1256 1306 400 1306 401 447 1256 448 1256 448 1256 1306 401 447 1256 448 1257 447 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 170 535 1281 413 1276 418 436 1259 1275 423 1276 444 410 1258 431 1264 435 1260 439 1256 433 1262 437 1258 1276 6529 173 475 1280 414 1275 419 435 1260 1274 450 1249 419 435 1259 430 1265 434 1261 438 1257 432 1263 436 1259 1275 7177 1280 414 1275 445 410 1260 1274 450 1249 445 410 1259 430 1266 433 1262 437 1258 431 1264 435 1260 1274 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1280 415 1274 447 407 1288 1256 442 1247 448 406 1288 411 1284 405 1291 408 1287 412 1283 1251 444 411 8009 1282 413 1276 445 409 1286 1248 450 1249 446 408 1286 403 1292 407 1262 437 1285 404 1291 1253 442 413 8007 1274 446 1253 442 402 1292 1252 447 1252 442 402 1292 407 1288 411 1284 405 1264 435 1286 1248 447 407 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1278 444 1255 439 405 1289 1255 444 1255 439 405 1290 409 1259 1275 447 407 1288 411 1257 432 1263 436 8009 1282 440 1249 446 408 1286 1248 451 1248 446 408 1286 413 1256 1278 444 410 1284 405 1264 435 1261 438 8008 1283 439 1250 444 410 1284 1250 450 1249 445 409 1284 405 1265 1279 442 412 1282 407 1262 437 1258 431 8015 1276 445 1254 440 404 1292 1252 446 1253 441 403 1292 407 1288 1256 439 405 1290 409 1286 403 1292 407 8012 1279 416 1273 448 406 1289 1255 443 1246 449 405 1289 410 1285 1249 446 408 1287 412 1283 406 1289 410 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1278 417 1282 439 405 1290 1254 444 1255 439 405 1289 410 1285 404 1292 1252 442 412 1283 406 1289 410 8010 1280 414 1275 446 408 1287 1247 451 1248 446 408 1286 413 1282 407 1263 1281 440 404 1291 408 1261 438 8007 1273 421 1278 443 411 1284 1249 449 1250 444 410 1258 431 1291 408 1288 1256 439 405 1289 410 1286 403 8017 1273 421 1278 443 411 1284 1249 449 1250 444 410 1285 404 1291 408 1262 1282 440 404 1264 435 1260 439 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1282 413 1276 445 409 1260 1274 450 1249 445 410 1259 430 1265 434 1261 438 1258 1276 445 409 1285 404 8017 1274 420 1279 442 412 1283 1251 448 1251 443 411 1283 406 1290 409 1260 439 1256 1278 443 411 1284 405 8015 1276 418 1281 440 404 1291 1253 446 1253 440 404 1291 408 1287 412 1283 406 1263 1281 440 404 1291 408 8011 1280 414 1275 446 408 1287 1247 452 1247 447 407 1261 438 1283 406 1289 410 1259 1275 447 407 1287 412 8006 1275 446 1253 441 403 1292 1252 447 1252 442 402 1292 407 1288 411 1284 405 1291 1253 442 402 1292 407 -# -name: Power -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 97 00 00 00 -# -name: Mode -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 87 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 86 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 9C 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 95 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 45 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 47 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 19 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 1C 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2229 716 749 747 728 715 750 720 756 714 751 719 757 1447 751 719 756 714 751 1453 755 742 723 747 729 742 754 716 749 721 754 715 729 741 724 1454 754 1450 758 1445 753 1451 757 714 751 50967 2228 717 748 1455 753 51028 2198 746 730 1449 749 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2225 719 756 741 724 720 755 715 750 720 755 716 749 1456 752 719 746 726 749 1456 731 741 755 1451 757 714 751 1455 753 718 757 1448 750 1456 752 720 755 715 750 721 754 1452 756 1449 759 51509 2201 743 732 1473 756 51069 2202 717 758 1448 750 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2234 712 752 743 732 713 751 720 755 716 749 722 753 1453 754 716 759 712 752 1453 754 718 757 1449 758 713 751 1455 752 1454 753 718 757 741 723 748 727 745 730 1449 758 739 725 1455 752 51252 2234 713 751 1454 753 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2229 714 750 747 727 717 757 713 751 719 755 715 749 1455 751 719 755 715 749 1455 751 1453 753 717 757 714 750 1454 752 718 756 714 750 746 728 742 722 722 752 1452 754 716 758 1445 751 50982 2229 716 727 1479 748 51048 2205 712 752 1450 756 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2258 688 781 718 751 694 775 696 784 687 782 690 779 1427 782 690 779 693 776 1431 778 721 748 724 756 716 753 719 750 722 747 725 744 727 773 1407 781 1426 783 1425 774 1433 776 696 773 51015 2224 748 721 1458 783 51084 2249 697 783 1425 784 51089 2253 694 775 1432 756 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2212 836 751 1591 749 825 752 817 750 1577 753 806 750 804 752 796 750 857 751 828 749 825 752 816 751 813 743 816 751 803 743 805 752 102392 2214 834 743 1599 751 823 744 825 752 1575 745 840 727 801 745 804 752 854 744 835 752 822 745 824 743 821 746 813 743 810 746 802 744 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2214 838 748 1596 751 824 751 819 746 1584 753 807 747 807 747 803 751 857 750 830 745 830 745 825 750 814 751 810 745 1574 752 798 746 101740 2213 838 748 1597 750 824 751 819 746 1583 754 806 748 807 747 802 752 856 751 830 745 829 746 824 751 814 751 809 745 1574 753 797 747 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2216 836 750 1594 754 821 754 816 749 1580 747 813 752 803 751 798 746 1626 753 827 748 1591 746 824 751 1578 749 811 754 1566 750 799 745 99411 2218 833 753 1591 746 829 746 823 752 1577 749 810 755 800 754 795 749 1623 745 835 751 1588 749 821 754 1574 752 807 747 1572 755 795 749 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2219 732 750 729 753 722 749 1384 755 1374 755 705 746 708 753 697 754 754 749 731 751 723 749 721 750 715 746 713 748 1370 749 701 749 99627 2216 735 747 732 750 725 747 1387 753 1376 753 706 755 700 751 698 753 755 748 732 750 725 747 722 749 716 745 714 747 1372 747 702 749 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2215 735 746 730 751 722 749 1383 745 1381 747 711 749 704 746 702 748 1422 748 730 751 722 749 720 751 712 748 1373 745 1372 746 702 748 102284 2218 732 749 729 752 720 751 1380 748 1378 750 709 751 701 749 700 750 1420 750 728 753 719 752 717 754 709 752 1370 748 1369 749 699 751 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2221 729 752 725 746 727 754 1377 751 1375 753 706 755 699 751 697 753 753 749 729 752 1384 755 1377 751 713 747 711 749 1367 751 1361 746 101691 2216 734 747 731 750 723 748 1383 745 1381 747 711 749 705 745 703 747 759 754 725 746 1390 749 1383 756 708 752 706 754 1362 745 1366 752 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2220 831 751 1618 729 820 751 818 753 1575 751 835 726 829 721 802 748 885 728 851 720 854 728 815 756 809 752 807 754 1565 750 825 725 101743 2220 830 752 1618 729 819 752 844 727 1574 752 835 726 802 748 801 749 884 729 850 721 827 755 815 746 845 726 806 755 1590 725 798 752 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2218 857 725 1592 754 846 725 844 727 1574 752 834 727 827 723 826 724 883 720 1597 750 851 720 1586 750 840 721 1577 749 831 730 1557 748 99247 2221 854 728 1590 746 854 728 841 720 1583 753 832 729 825 725 824 726 881 722 1596 751 849 722 1585 751 839 722 1576 750 831 719 1568 747 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2217 832 750 1594 753 821 750 819 752 1576 750 809 804 750 800 748 750 1622 756 1587 749 1589 747 1586 750 1578 748 1575 751 1568 747 802 748 101181 2218 831 751 1592 755 819 752 817 754 1574 752 808 753 801 749 800 751 1620 748 1596 751 1587 749 1584 752 1576 750 1574 752 1566 749 800 803 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2274 662 756 711 749 718 752 715 797 669 801 1399 749 718 783 684 797 670 800 1400 748 720 750 716 754 713 747 720 750 718 752 714 777 718 752 1448 752 714 756 711 749 1450 750 1423 777 51569 2192 718 752 1447 753 50954 2223 712 748 1423 798 50911 2245 661 746 1451 749 50917 2218 715 755 1415 754 50965 2191 716 754 1445 755 50923 2223 710 750 1421 800 50893 2190 717 753 1445 776 50902 2242 664 796 1402 777 50907 2217 717 753 1418 803 50880 2223 710 750 1421 800 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2225 712 747 719 751 716 754 713 757 711 748 1451 748 718 752 715 754 712 747 1451 748 718 752 716 754 1445 754 1444 724 1449 750 716 754 713 746 1453 746 720 749 1450 749 1451 727 1446 753 50693 2219 717 752 1445 754 50927 2194 714 756 1442 747 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2216 733 744 734 754 1382 751 1381 752 712 744 714 753 700 746 702 754 753 745 733 755 719 748 720 747 717 750 709 747 706 750 1361 751 99476 2211 738 749 729 748 1389 754 1377 745 718 748 710 746 707 749 699 747 760 748 730 747 726 751 718 748 715 752 706 750 703 753 1359 753 49283 2217 670 745 1350 751 50661 2212 676 749 1345 746 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2213 737 751 727 750 1387 746 1385 748 716 751 707 749 704 752 696 750 1420 754 724 753 720 747 721 746 718 749 1373 749 1367 745 703 753 102141 2215 735 753 726 751 1385 747 1384 748 715 752 706 750 704 752 695 751 1419 755 723 754 720 747 721 746 718 749 1399 723 1367 755 693 753 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 33 2223 753 725 720 748 750 728 743 725 746 722 1458 750 749 750 722 726 747 721 1460 748 724 755 744 724 721 747 724 754 744 724 747 752 720 748 1458 750 722 756 715 753 718 750 1456 752 51488 2221 727 721 1460 748 51156 2203 744 724 1456 752 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 33 2225 718 750 719 749 721 747 722 746 723 745 1458 750 720 748 722 726 744 724 1452 756 741 727 742 726 1451 746 1457 750 1454 754 716 752 717 720 1483 725 745 723 1454 754 1450 747 1456 752 50811 2199 744 745 1433 754 51049 2229 715 743 1435 752 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2221 721 727 1450 779 1452 756 714 744 1487 721 723 756 714 754 715 774 774 726 1505 724 772 748 1482 726 770 751 1454 754 1477 721 1484 745 101867 2197 745 723 1453 776 1455 722 748 720 1483 756 713 755 714 744 725 775 774 726 1478 751 771 729 1475 754 769 720 1483 756 1448 750 1481 727 50476 2201 714 755 1448 781 50602 2221 720 748 1454 775 50604 2250 743 694 1456 773 50628 2226 715 743 1433 775 50652 2191 748 720 1430 778 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2193 750 729 1421 777 1454 754 715 753 1478 751 719 728 715 753 716 773 1510 729 1501 728 1477 752 1505 755 1475 754 1477 721 749 751 772 696 102110 2246 748 720 1455 774 1430 747 723 746 1484 724 745 723 720 748 721 779 1477 752 1478 751 1480 749 1508 752 1478 751 1479 729 741 748 748 721 50743 2195 747 721 1454 775 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2247 750 729 1447 782 1450 748 722 726 1506 723 720 748 722 757 713 776 772 728 769 751 1505 755 1476 753 1452 756 740 749 747 753 1452 756 101517 2220 749 719 1457 772 1433 754 715 753 1478 719 750 729 715 753 716 773 776 724 772 748 1508 752 1479 750 1481 727 769 731 739 750 1454 754 50114 2228 742 726 1450 779 50645 2197 746 722 1454 775 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1275 434 1253 430 408 1275 1251 431 1257 427 411 1272 415 1241 436 1248 439 1244 433 1250 437 1247 1279 7142 1276 433 1255 428 410 1247 1279 430 1247 435 414 1269 408 1249 438 1272 415 1241 436 1248 439 1244 1282 8244 1274 434 1253 429 409 1274 1252 430 1247 435 414 1242 435 1275 412 1270 407 1277 411 1246 441 1242 1274 7146 1282 426 1251 431 407 1276 1250 432 1255 426 412 1271 406 1276 411 1272 415 1242 435 1248 439 1244 1282 8243 1275 433 1255 428 410 1272 1254 429 1248 434 415 1267 410 1273 414 1269 408 1275 412 1245 432 1251 1275 7144 1273 435 1252 430 408 1274 1252 430 1247 436 413 1269 408 1249 438 1245 442 1267 410 1274 413 1270 1256 8241 1277 431 1257 425 413 1270 1256 425 1252 430 408 1275 412 1270 407 1276 411 1246 441 1267 410 1247 1279 7140 1277 431 1256 426 412 1270 1256 425 1252 430 408 1274 413 1270 407 1276 411 1245 442 1267 410 1247 1279 8245 1273 435 1252 430 408 1274 1252 430 1247 435 414 1269 408 1275 412 1270 407 1277 410 1246 441 1268 1248 7145 1273 435 1252 430 408 1274 1252 430 1247 435 414 1242 435 1248 439 1244 433 1250 437 1246 441 1242 1274 8249 1279 403 1274 434 415 1242 1274 434 1253 429 409 1247 440 1242 435 1248 439 1244 433 1250 437 1246 1280 7138 1279 403 1274 434 415 1268 1247 434 1253 429 409 1246 441 1268 409 1248 439 1244 433 1250 437 1245 1281 8241 1277 405 1283 426 412 1270 1256 426 1251 430 408 1274 413 1270 407 1276 411 1271 416 1267 410 1246 1280 7138 1279 429 1248 434 415 1268 1247 434 1274 407 442 1240 416 1267 410 1247 440 1243 434 1249 438 1271 1255 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1279 430 1247 436 413 1270 1256 427 1250 432 406 1277 410 1273 414 1269 408 1275 1251 432 406 1277 410 7983 1284 425 1252 430 408 1275 1251 431 1256 426 412 1270 407 1250 437 1272 415 1242 1274 435 414 1270 407 9092 1275 433 1254 428 410 1273 1253 428 1249 434 415 1268 409 1274 413 1269 408 1276 1250 432 406 1277 410 7984 1283 425 1252 431 407 1275 1251 431 1256 426 412 1270 407 1276 411 1272 415 1241 1275 435 414 1269 408 9090 1277 431 1256 426 412 1270 1256 426 1251 431 407 1275 412 1270 407 1276 411 1272 1254 428 410 1273 414 7978 1278 430 1247 435 414 1269 1247 435 1252 430 408 1274 413 1243 434 1275 412 1271 1255 427 411 1272 415 9082 1275 434 1253 428 410 1273 1253 428 1249 433 405 1277 410 1272 415 1268 409 1274 1252 430 408 1275 412 7980 1276 432 1255 427 411 1271 1255 427 1250 432 406 1276 411 1271 416 1267 410 1247 1279 430 408 1275 412 9085 1282 426 1251 431 407 1276 1250 432 1255 426 412 1270 407 1276 411 1272 415 1268 1247 435 414 1269 408 7985 1282 427 1250 432 406 1277 1249 433 1254 428 410 1272 415 1267 410 1274 413 1269 1257 426 412 1271 406 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9204 4469 604 562 604 562 604 562 604 563 603 564 602 565 601 567 599 568 598 1642 598 1643 607 1634 606 1634 606 1634 606 1635 605 1636 604 1637 603 1638 602 565 601 1641 599 568 598 569 607 560 606 1635 605 588 578 562 604 1637 603 563 603 1638 602 1639 601 1641 599 594 572 1643 607 39280 9217 2212 601 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9225 4468 605 563 603 564 602 565 601 566 600 568 598 569 597 570 606 561 605 1636 604 1638 602 1640 600 1641 599 1643 607 1634 606 1636 603 1637 603 1639 601 1640 600 1641 599 595 571 568 598 569 597 1645 605 588 578 562 604 563 603 564 602 1639 601 1641 599 1643 597 596 580 1635 605 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9214 4475 598 568 598 597 579 560 606 588 578 589 577 563 603 590 576 564 602 1666 574 1667 573 1641 599 1669 571 1643 597 1644 606 1635 605 1663 577 563 603 564 602 591 575 592 574 592 574 593 573 1668 572 594 572 1669 571 1669 571 1670 570 1644 606 1635 605 1636 604 590 576 1637 603 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9275 4475 608 560 606 563 603 565 601 568 598 570 606 563 603 565 601 567 599 1645 605 1638 602 1642 598 1645 605 1639 601 1642 598 1645 605 1637 603 1641 599 569 607 561 605 1639 601 1642 608 560 606 563 603 565 601 568 598 1644 606 1637 603 565 601 567 599 1644 606 1636 604 1639 601 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1279 428 1246 434 413 1265 1246 434 1250 430 407 1271 413 1240 434 1245 439 1240 434 1245 439 1240 1271 7282 1276 430 1244 436 411 1267 1255 426 1248 432 405 1248 437 1243 431 1248 436 1243 431 1248 436 1243 1279 7276 1272 434 1250 430 407 1271 1251 430 1244 436 411 1267 407 1247 437 1241 433 1246 438 1241 433 1246 1276 7279 1279 427 1247 433 404 1248 1274 433 1251 429 408 1271 413 1239 435 1245 440 1239 435 1244 430 1249 1273 7282 1276 430 1244 436 411 1267 1244 436 1248 432 405 1273 411 1242 432 1247 437 1241 433 1247 437 1241 1281 7274 1274 432 1252 428 409 1270 1252 428 1246 434 413 1266 408 1244 441 1239 435 1244 440 1239 435 1244 1278 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1275 405 1279 401 436 1243 1279 401 1273 407 440 1238 436 1244 1278 429 408 1244 440 1239 435 1244 440 8113 1272 434 1250 430 407 1246 1276 430 1254 426 411 1242 432 1247 1275 432 405 1247 437 1242 432 1247 437 8115 1280 426 1248 432 405 1248 1274 433 1251 428 409 1244 440 1239 1272 434 413 1239 435 1245 439 1239 435 8118 1277 429 1245 435 412 1240 1271 436 1248 431 406 1247 437 1242 1280 427 410 1242 432 1247 437 1242 432 8121 1274 406 1278 428 409 1244 1278 402 1272 408 439 1240 434 1245 1277 404 433 1246 438 1240 434 1245 439 8114 1281 399 1275 405 432 1247 1275 406 1278 401 436 1244 430 1249 1273 407 440 1240 434 1245 439 1239 435 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1248 412 1274 438 405 1284 1245 445 1251 435 408 1281 405 1282 404 1285 411 1251 435 1253 1276 437 406 8011 1242 445 1251 435 408 1255 1274 442 1244 443 410 1278 408 1280 406 1282 435 1254 411 1277 1242 445 408 8011 1242 443 1243 444 409 1279 1250 440 1246 441 412 1277 409 1279 407 1281 405 1282 404 1286 1243 443 410 8008 1245 442 1244 443 410 1278 1251 440 1246 441 402 1286 410 1278 408 1280 406 1256 430 1284 1276 411 411 8006 1247 439 1247 439 404 1285 1244 446 1250 437 406 1282 404 1283 403 1285 411 1278 408 1254 1275 438 405 8013 1251 436 1250 410 433 1282 1247 443 1243 443 410 1279 407 1281 405 1282 404 1258 438 1276 1243 444 409 7981 1272 414 1272 441 412 1250 1269 448 1248 438 405 1257 429 1259 437 1277 429 1232 433 1281 1248 412 431 7986 1267 446 1250 436 407 1255 1274 443 1243 443 410 1253 433 1280 406 1282 404 1258 428 1287 1273 387 435 7981 1272 414 1272 415 438 1276 1243 447 1249 411 432 1282 435 1228 489 1198 436 1278 408 1280 1249 411 432 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1281 410 1289 429 415 1276 1257 435 1264 428 416 1248 440 1250 438 1253 435 1282 1261 404 440 1277 411 8127 1288 404 1284 434 410 1281 1262 403 1285 432 412 1280 408 1256 443 1249 439 1278 1265 401 443 1275 413 8125 1289 429 1260 432 412 1253 1291 401 1287 431 413 1251 437 1253 446 1246 442 1249 1284 435 409 1256 443 8122 1282 437 1262 429 415 1277 1256 409 1290 428 416 1249 439 1251 437 1254 445 1247 1286 433 411 1254 445 8120 1283 409 1290 428 416 1275 1258 434 1254 437 418 1274 414 1249 439 1252 436 1281 1262 404 440 1251 437 8128 1286 432 1256 436 408 1257 1286 405 1283 435 409 1282 417 1247 441 1250 438 1253 1290 428 416 1249 439 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1286 406 1282 438 416 1275 1257 435 1263 430 1258 434 410 1282 416 1276 412 1254 444 1248 1284 436 1262 7278 1288 403 1285 435 408 1257 1285 433 1255 438 1260 431 413 1279 409 1283 415 1250 438 1254 1288 405 1283 7285 1281 411 1287 432 412 1253 1289 430 1258 435 1263 429 415 1250 438 1254 444 1248 440 1252 1290 429 1259 7283 1283 408 1290 428 416 1249 1283 436 1262 431 1257 435 408 1256 442 1250 438 1255 443 1248 1284 436 1262 7278 1288 430 1258 435 408 1256 1286 433 1255 437 1261 431 413 1252 436 1282 416 1249 439 1254 1288 404 1284 7282 1284 408 1290 428 416 1249 1283 436 1262 430 1258 434 410 1255 443 1249 439 1253 445 1247 1285 408 1290 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1276 403 1271 435 412 1241 1270 409 1276 431 1254 427 410 1242 1280 427 1247 433 404 1249 1273 407 430 8123 1273 433 1251 429 408 1245 1277 429 1245 435 1250 430 407 1272 1250 430 1255 426 411 1268 1254 426 411 8117 1279 401 1273 433 404 1249 1273 433 1251 429 1245 435 412 1240 1271 435 1249 431 406 1246 1276 431 406 8122 1274 406 1278 427 410 1270 1252 428 1246 434 1251 429 408 1271 1251 403 1271 436 411 1267 1244 436 411 8116 1279 427 1247 433 404 1275 1247 433 1251 429 1245 434 413 1266 1245 435 1250 430 407 1272 1250 431 406 8121 1274 431 1254 427 410 1269 1253 427 1247 433 1251 429 408 1271 1251 429 1245 434 413 1266 1245 435 412 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1280 457 1229 426 468 1236 1282 456 1230 427 468 1236 470 1235 471 1235 471 1235 471 1235 492 1240 1289 7171 1290 422 1284 399 464 1238 1291 422 1284 399 464 1239 467 1238 468 1238 468 1238 489 1217 500 1233 1285 7176 1285 426 1280 403 471 1232 1286 426 1281 403 471 1232 464 1242 496 1210 496 1237 469 1236 470 1236 1282 7206 1286 399 1287 422 441 1262 1287 398 1288 449 435 1215 491 1241 465 1240 466 1240 466 1240 466 1240 1289 7199 1283 429 1257 426 469 1235 1283 428 1258 426 469 1234 473 1234 472 1234 472 1233 474 1233 463 1243 1306 7182 1289 448 1258 398 465 1238 1291 448 1258 425 438 1239 467 1238 468 1238 468 1237 490 1216 490 1242 1287 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1289 448 1227 454 441 1238 1280 457 1228 454 441 1238 468 1239 1289 448 436 1215 491 1242 475 1233 473 8012 1281 457 1249 406 468 1237 1281 457 1260 396 467 1238 468 1239 1290 448 436 1242 475 1233 473 1234 472 8013 1291 421 1285 397 466 1238 1291 421 1285 398 465 1239 467 1240 1288 423 472 1233 473 1233 473 1234 472 8014 1289 449 1257 398 465 1239 1289 422 1284 398 465 1239 467 1240 1309 403 471 1233 473 1234 472 1235 471 8015 1309 402 1283 426 437 1241 1308 403 1283 427 436 1241 496 1237 1281 404 470 1235 471 1235 471 1236 491 8021 1282 402 1284 452 443 1235 1283 402 1284 452 443 1209 497 1236 1282 402 472 1233 463 1243 495 1212 494 8019 1284 427 1258 450 445 1233 1285 426 1259 450 444 1233 473 1233 1285 426 437 1240 497 1209 497 1236 470 8016 1288 450 1225 431 464 1241 1288 451 1255 402 472 1232 474 1232 1286 452 443 1235 523 1183 471 1235 471 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1286 424 1251 430 465 1241 1287 423 1252 457 437 1241 465 1242 464 1243 474 1234 472 1235 1314 423 440 8017 1286 451 1255 401 462 1242 1286 452 1254 402 472 1232 474 1233 473 1234 472 1234 493 1241 1287 424 439 8014 1289 422 1284 426 437 1241 1287 451 1255 403 471 1234 472 1234 472 1235 492 1215 491 1242 1286 399 464 8018 1285 426 1280 403 471 1234 1284 454 1252 404 470 1235 471 1236 491 1215 491 1243 526 1181 1285 427 436 8019 1315 423 1262 395 468 1236 1313 400 1286 424 439 1238 489 1218 499 1235 471 1236 470 1236 1282 430 464 8019 1284 401 1284 426 468 1236 1282 404 1281 456 438 1240 466 1240 466 1241 465 1241 465 1242 1286 426 468 8015 1288 424 1261 448 436 1242 1286 426 1259 451 444 1234 472 1234 472 1235 471 1235 471 1236 1282 456 438 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9090 4295 561 1708 570 561 563 569 565 567 567 565 569 563 561 570 564 568 566 565 569 1701 567 1702 566 1703 565 1705 563 1706 562 1707 561 1708 570 1699 569 1700 568 564 570 562 562 570 564 567 567 565 569 1700 568 563 591 563 561 1708 571 1699 569 1700 568 1702 566 1703 565 565 569 40667 9086 2139 562 97339 9092 2132 569 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9092 4292 563 1732 536 569 565 567 567 591 543 589 534 597 537 595 539 566 568 564 570 1699 569 1700 568 1702 566 1703 565 1705 563 1707 561 1708 570 1700 568 1701 567 591 543 1700 568 564 570 588 535 570 564 1732 556 571 563 568 566 1703 565 567 567 1702 566 1703 565 1705 563 567 567 40657 9086 2135 566 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9091 4292 564 1706 562 569 565 567 567 565 569 562 561 570 564 568 566 565 569 562 561 1708 570 1698 570 1700 568 1728 540 1703 565 1705 563 1706 562 1708 570 1699 569 1700 568 563 571 561 562 569 565 566 568 1701 587 566 568 563 571 561 562 1706 562 1707 561 1708 570 1699 569 562 561 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9000 4466 564 1681 564 566 572 559 569 561 567 564 564 567 571 558 570 560 568 563 565 1678 567 1679 566 1681 564 1682 564 1679 566 1678 567 1686 570 1676 569 1674 571 559 569 563 565 1679 566 564 564 566 572 550 567 563 565 565 563 1679 566 1677 568 563 565 1678 567 1676 569 1681 564 1680 565 1680 565 565 563 569 569 1674 571 560 567 563 565 555 573 558 570 559 569 1674 571 1670 565 564 564 1678 567 1676 569 1675 570 12978 9003 2207 566 96147 9000 2203 570 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9005 4459 570 1701 544 560 568 563 565 566 572 559 569 563 565 566 572 559 569 561 567 1678 567 1676 569 1675 570 1673 572 1671 564 1680 565 1685 571 1671 564 1677 568 561 567 562 566 564 564 565 563 565 563 557 571 557 571 557 571 1670 565 1678 567 1674 571 1668 567 1671 564 1686 570 1670 565 1675 570 559 569 560 568 561 567 562 566 563 565 553 564 563 565 564 564 1677 569 1673 573 1671 564 1678 567 1674 571 1671 564 13005 8998 2203 570 96194 8995 2204 569 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9004 4451 568 1683 573 558 570 561 567 565 563 568 570 560 568 562 566 564 564 566 572 1680 565 1688 568 1685 571 1685 571 1684 572 1683 573 1674 571 1681 564 567 571 1679 566 1686 570 561 566 565 563 567 571 551 566 563 565 1690 566 565 563 569 569 1684 572 1682 563 1690 566 1681 564 1689 567 564 564 1689 567 1687 569 563 565 566 572 559 569 551 566 563 565 1684 572 558 570 560 567 1682 563 1687 569 1683 562 1688 568 12989 8992 2208 565 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9005 4457 572 1673 572 559 569 561 567 563 565 566 562 567 571 558 570 560 568 562 566 1676 569 1673 572 1698 537 1708 537 1704 541 1703 542 1708 537 567 571 1669 566 1701 544 558 570 560 568 561 567 564 564 557 571 1671 564 565 563 566 572 1670 565 1703 542 1673 562 1678 567 1683 562 566 562 1677 568 1701 544 559 569 561 567 563 565 565 563 557 571 1668 567 563 565 563 565 1676 569 1699 546 1669 566 1678 567 1676 569 -# -name: Power -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 8C 00 00 00 -# -name: Speed_dn -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 83 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 85 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8958 4439 570 1663 563 561 562 563 560 564 569 556 567 557 566 558 565 559 564 560 563 1669 567 1663 563 1670 566 1665 561 1670 566 1666 560 1680 567 1665 561 1670 566 558 565 560 563 1669 567 557 566 559 564 552 561 563 560 565 568 1663 563 1668 568 556 567 1665 561 1670 566 1674 562 1668 568 1663 563 561 562 562 561 1670 566 558 565 559 564 552 561 563 560 564 569 1661 565 1667 569 555 568 1664 562 1668 568 1663 563 12896 8966 2197 565 95701 8962 2200 562 95719 8975 2194 568 95705 8970 2195 567 95693 8970 2196 566 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8965 4443 566 1668 568 557 566 559 564 562 561 564 569 583 540 559 564 561 562 563 560 1673 563 1671 565 1669 567 1666 560 1673 563 1670 566 1676 560 1673 563 1670 566 558 565 561 562 564 569 556 567 559 564 553 560 565 568 558 565 1668 568 1666 570 1663 563 1670 566 1667 569 1671 565 1668 568 1665 561 564 569 557 566 559 564 562 561 564 569 547 566 560 563 562 561 1672 564 1669 567 1666 570 1664 562 1671 565 1668 568 12942 8972 2198 564 95779 8964 2201 561 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8970 4439 570 1665 561 564 569 556 567 558 565 561 562 563 560 566 567 557 566 559 564 1669 567 1667 569 1665 561 1672 564 1670 566 1668 569 1674 562 563 560 1673 563 1671 565 561 562 564 569 556 567 559 564 553 560 1673 563 563 560 566 567 1667 569 1664 562 1672 564 1669 567 1676 560 566 567 1667 569 1664 562 563 570 555 568 558 565 560 563 553 560 1674 562 563 560 565 569 1667 570 1664 562 1671 565 1667 569 1663 563 12935 8969 2204 568 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8963 4430 569 1673 563 561 562 562 561 564 570 555 568 557 566 559 564 561 562 563 560 1680 567 1674 562 1680 567 1675 561 1681 566 1676 561 1671 565 1675 562 563 560 1682 565 1678 569 557 566 559 564 562 561 555 568 557 566 1675 562 564 569 555 568 1674 563 1678 569 1673 564 1670 566 1675 561 563 570 1672 564 1677 570 555 568 558 565 560 563 552 561 564 559 1682 565 560 563 563 560 1681 566 1675 561 1681 566 1676 561 12950 8975 2195 567 95870 8966 2201 561 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8973 4429 570 1673 563 562 561 565 568 557 566 559 564 561 562 564 569 557 566 559 564 1679 568 1674 562 1680 567 1676 560 1682 565 1676 571 1663 563 1679 568 558 565 1677 570 1673 563 562 561 565 568 557 566 1667 559 565 568 1673 563 563 560 565 568 1674 562 1679 568 1675 561 556 567 1675 561 564 569 1672 564 1677 570 556 567 559 564 561 562 1670 566 559 564 1676 571 555 568 557 566 1675 562 1680 567 1675 561 564 569 12939 8975 2197 565 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8972 4427 561 1681 566 560 563 562 561 565 568 557 566 559 564 561 562 563 560 565 568 1673 563 1676 571 1671 565 1676 571 1672 564 1677 570 1664 562 1679 568 1674 562 562 561 1680 567 558 565 560 563 562 561 555 568 556 567 558 565 1676 560 564 569 1670 566 1675 561 1680 567 1665 561 1680 567 1675 561 564 569 1673 563 562 561 564 569 556 567 548 565 561 562 562 561 1680 567 558 565 1677 570 1672 564 1678 569 1673 563 12942 8971 2195 567 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9024 4477 566 1725 541 537 575 530 572 534 568 537 565 540 572 533 569 537 565 540 572 1690 566 1697 569 1720 546 1690 566 1697 569 1694 572 1691 565 1698 568 1694 572 533 569 537 565 540 572 533 569 536 566 540 572 532 570 536 566 1696 570 1692 574 1715 541 1696 570 1693 573 1689 567 1696 570 1693 573 532 570 536 566 539 573 532 570 535 567 538 574 531 571 534 568 1695 571 1691 565 1725 541 1695 571 1692 574 1688 568 11738 9017 2223 574 95995 9023 2216 570 95998 9020 2219 567 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9025 4477 576 1687 569 564 548 531 571 534 568 537 565 540 572 533 569 536 566 539 573 1689 567 1696 570 1693 573 1689 567 1696 570 1693 573 1690 566 1697 569 1694 572 560 542 1694 572 560 542 537 565 540 572 533 569 536 566 539 573 1689 567 565 547 1689 567 1696 570 1692 574 1689 567 1696 570 1693 573 559 543 1693 573 559 543 536 566 539 573 532 570 535 567 538 574 1688 568 564 548 1688 568 1695 572 1691 575 1688 568 11738 9016 2223 574 95995 9022 2216 570 96000 9019 2219 630 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9022 4480 573 1691 565 540 572 533 569 537 565 540 572 533 569 536 566 540 572 532 570 1693 573 1690 566 1697 569 1694 572 1717 539 1724 542 1721 545 1718 548 1715 541 1722 544 535 567 538 574 531 571 535 567 538 574 531 571 534 568 538 564 1698 568 1695 571 1692 574 1689 567 1722 544 1719 547 1716 540 1723 543 536 566 540 572 533 569 536 566 539 573 532 570 536 566 539 573 1689 567 1696 570 1693 573 1690 566 1724 542 11737 9015 2224 572 95995 9025 2215 571 95997 9020 2219 567 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9021 4481 572 1692 574 531 571 535 567 538 574 531 571 534 568 538 564 541 571 534 568 1694 572 1691 575 1688 568 1722 544 1719 547 1690 566 1697 569 1694 572 1691 565 540 572 533 569 1694 572 533 569 537 565 540 572 533 569 537 565 1698 568 1694 572 533 569 1695 571 1692 574 1715 541 1722 544 1693 573 531 571 535 567 1696 570 535 567 539 573 532 570 535 567 538 574 1689 567 1696 570 535 567 1696 570 1693 573 1716 540 11739 9024 2216 570 95999 9020 2219 567 96001 9020 2220 566 -# -name: Power -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 92 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 89 00 00 00 -# -name: Speed_dn -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 9F 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 87 00 00 00 -# -name: Mode -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 81 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1286 402 1289 426 414 1241 1280 408 1283 431 409 1245 435 1246 434 1247 433 1249 442 1239 441 1240 1281 7121 1281 404 1276 436 414 1239 1282 429 1251 433 407 1246 434 1246 434 1247 433 1247 433 1247 433 1247 1284 8216 1275 408 1283 428 412 1267 1253 430 1250 432 408 1245 435 1244 436 1244 436 1244 436 1244 436 1243 1277 7120 1282 401 1279 403 437 1243 1277 404 1276 407 433 1247 433 1246 434 1246 434 1246 434 1246 434 1245 1275 8224 1277 405 1275 407 433 1247 1273 409 1282 400 440 1240 440 1239 441 1239 441 1239 441 1238 442 1238 1282 7115 1276 405 1275 407 433 1247 1273 409 1281 400 440 1240 440 1239 441 1239 441 1239 441 1238 432 1248 1283 8216 1275 407 1283 399 441 1238 1282 400 1280 402 438 1241 439 1241 439 1240 440 1239 441 1239 441 1238 1282 7114 1277 405 1275 406 434 1246 1274 407 1273 408 432 1248 432 1247 433 1247 1246 434 1245 435 1245 1275 8223 1279 403 1277 404 436 1244 1276 406 1274 407 433 1247 433 1246 434 1245 435 1245 435 1244 436 1244 1276 7120 1282 400 1280 401 439 1241 1279 402 1278 404 436 1244 436 1243 437 1243 437 1242 438 1241 439 1241 1279 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1286 401 1279 434 406 1249 1282 404 1276 436 414 1240 440 1241 1280 432 408 1247 433 1248 432 1248 443 7955 1277 408 1283 430 410 1244 1276 408 1283 429 411 1243 437 1243 1278 408 432 1248 432 1249 441 1265 415 9057 1286 400 1280 431 409 1271 1250 435 1256 429 411 1243 437 1243 1278 433 407 1248 432 1248 432 1248 432 7966 1277 407 1284 427 413 1241 1280 431 1249 435 405 1249 442 1239 1282 402 438 1243 437 1243 437 1243 437 9062 1280 404 1276 435 405 1248 1283 401 1279 432 408 1245 435 1246 1275 436 414 1239 441 1239 441 1239 441 7956 1276 407 1284 427 413 1241 1280 404 1276 434 406 1247 433 1247 1284 400 440 1240 440 1240 440 1240 440 9058 1284 399 1281 429 411 1243 1278 432 1248 435 405 1249 431 1248 1283 401 439 1241 439 1241 439 1241 439 7957 1275 408 1283 401 439 1241 1280 430 1250 406 434 1246 434 1246 1275 409 441 1238 432 1249 431 1248 432 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1292 403 1288 431 409 1246 1285 406 1285 432 408 1247 433 1249 442 1240 1280 407 443 1239 441 1267 413 7957 1286 401 1279 434 406 1248 1283 404 1287 427 413 1241 439 1241 439 1241 1280 407 433 1248 432 1248 443 9055 1277 409 1282 431 409 1244 1276 409 1282 431 409 1244 436 1245 435 1245 1286 426 414 1267 413 1241 439 7956 1276 409 1282 430 410 1244 1277 408 1283 429 411 1269 411 1243 437 1243 1278 407 433 1248 432 1248 432 9065 1277 407 1284 428 412 1242 1278 406 1285 427 413 1241 439 1241 439 1241 1280 406 434 1247 433 1247 433 7962 1281 405 1275 436 414 1266 1254 431 1249 436 414 1265 415 1239 441 1240 1280 432 408 1246 434 1247 433 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1274 408 1283 426 414 1239 1282 427 1253 428 412 1241 439 1240 440 1240 440 1239 1281 427 413 1267 413 7957 1275 434 1246 436 404 1275 1256 427 1253 429 411 1267 413 1267 413 1240 440 1240 1280 428 412 1267 413 9060 1282 400 1280 428 412 1267 1253 429 1251 430 410 1243 437 1243 437 1242 438 1242 1278 429 411 1241 439 7958 1274 434 1246 436 414 1264 1256 426 1254 428 412 1266 414 1240 492 1188 440 1240 1280 428 412 1240 440 9059 1272 436 1254 426 414 1239 1281 426 1254 427 413 1240 440 1239 441 1239 441 1238 1282 426 414 1239 441 7955 1277 405 1275 407 433 1246 1274 408 1282 399 441 1239 441 1239 441 1239 441 1239 1281 400 440 1240 440 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1309 376 1276 409 433 1252 1305 379 1273 410 463 1222 441 1244 460 1221 463 1221 442 1242 462 1222 1283 7692 1310 375 1309 377 464 1220 1305 379 1273 410 463 1223 460 1222 441 1242 462 1221 431 1252 463 1220 1305 7668 1281 401 1303 378 432 1252 1284 399 1305 378 464 1220 464 1217 466 1216 467 1215 468 1214 459 1223 1303 7665 1305 379 1304 381 440 1244 1282 405 1278 409 433 1254 440 1246 437 1248 467 1219 433 1252 463 1224 1312 7687 1304 385 1309 380 461 1227 1278 410 1305 382 439 1250 433 1253 462 1228 466 1222 461 1226 468 1219 1306 7689 1313 375 1308 380 441 1248 1309 379 1284 402 460 1228 466 1222 441 1248 467 1222 462 1227 467 1222 1304 7688 1283 403 1312 374 468 1219 1306 382 1312 377 464 1224 439 1249 434 1252 442 1245 459 1228 435 1253 1304 7683 1277 411 1304 383 438 1249 1308 380 1303 381 461 1225 438 1248 435 1252 442 1247 436 1252 442 1246 1311 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1274 436 1247 436 406 1277 1249 437 1257 427 415 1269 415 1270 413 1272 1254 432 410 1274 410 1271 413 8525 1277 431 1253 430 412 1272 1254 430 1254 428 413 1270 414 1271 413 1272 1254 430 412 1271 413 1272 412 8537 1275 435 1249 437 415 1270 1256 427 1257 427 415 1268 405 1279 415 1268 1247 437 404 1279 415 1269 415 8528 1274 433 1251 432 410 1272 1254 430 1254 431 411 1273 411 1271 413 1244 1282 429 413 1271 413 1271 413 8532 1281 429 1255 430 412 1272 1254 429 1255 428 414 1271 413 1245 439 1273 1253 432 410 1274 410 1273 411 8539 1273 435 1249 433 409 1273 1253 429 1255 430 412 1271 413 1270 414 1267 1248 434 408 1274 410 1273 410 8532 1280 428 1256 427 414 1268 1247 436 1248 436 406 1250 434 1275 409 1272 1254 429 413 1270 414 1270 414 8534 1278 431 1253 433 409 1273 1253 429 1255 429 413 1269 415 1267 406 1277 1249 436 405 1277 407 1249 435 8531 1281 427 1246 436 406 1275 1251 434 1250 433 409 1272 412 1270 414 1269 1246 436 405 1277 407 1274 410 8537 1275 435 1249 435 407 1278 1248 437 1257 428 414 1268 405 1277 407 1276 1250 434 408 1274 410 1272 412 8531 1281 429 1255 429 413 1243 1283 427 1257 428 414 1271 413 1272 412 1272 1254 432 410 1273 411 1247 437 8536 1276 432 1252 432 410 1273 1253 432 1252 433 409 1275 409 1275 409 1275 1251 435 407 1278 405 1277 407 8535 1278 430 1254 428 414 1268 1247 436 1248 435 406 1276 408 1277 407 1277 1249 435 407 1277 407 1277 407 8540 1273 435 1249 434 408 1273 1253 429 1255 429 413 1271 413 1270 414 1270 1256 430 412 1271 413 1271 413 8538 1275 435 1249 435 407 1275 1251 432 1252 433 409 1275 409 1274 410 1274 1252 432 410 1275 409 1276 408 8542 1281 429 1255 431 411 1273 1253 431 1253 432 410 1273 411 1271 413 1271 1255 431 411 1273 411 1272 412 8529 1273 436 1258 427 415 1268 1258 427 1257 429 413 1272 412 1273 411 1249 1277 436 406 1279 415 1268 415 8536 1276 432 1252 430 412 1271 1255 431 1253 432 410 1275 409 1274 410 1273 1253 432 410 1274 410 1272 412 8527 1275 435 1249 435 407 1276 1250 433 1251 434 408 1277 407 1279 415 1269 1257 427 415 1271 413 1270 413 8556 1277 436 1258 431 411 1277 1259 430 1254 436 406 1280 414 1272 412 1275 1251 438 414 1272 412 1272 412 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1280 431 1253 432 410 1275 1250 433 1251 434 408 1277 406 1280 414 1272 412 1274 410 1276 1250 436 405 8543 1331 352 1280 432 410 1275 1251 433 1251 435 407 1275 409 1272 412 1271 413 1272 412 1273 1253 432 410 8527 1275 435 1249 436 405 1276 1250 431 1253 429 413 1270 413 1270 413 1269 414 1269 415 1268 1247 436 405 8534 1278 430 1253 428 414 1268 1247 435 1248 435 407 1277 406 1276 408 1274 410 1274 410 1248 1277 436 405 8558 1275 438 1256 431 411 1277 1259 428 1256 431 411 1275 409 1278 416 1271 413 1273 411 1277 1259 429 413 8559 1285 428 1256 430 412 1273 1253 433 1251 434 408 1277 406 1280 414 1274 410 1279 415 1273 1253 437 415 8550 1283 430 1254 433 409 1279 1257 431 1253 434 408 1279 415 1274 410 1278 416 1274 410 1278 1258 431 411 8553 1280 432 1252 435 407 1279 1257 427 1256 427 404 1277 406 1276 408 1248 435 1274 410 1273 1253 430 412 8535 1277 433 1251 435 406 1279 1257 429 1255 431 411 1275 409 1277 406 1278 416 1271 413 1272 1254 431 411 8545 1278 433 1251 434 408 1278 1258 428 1255 432 410 1274 410 1279 415 1273 411 1278 416 1273 1253 437 415 8557 1286 429 1254 437 415 1274 1252 436 1258 430 412 1277 406 1283 411 1278 405 1282 412 1275 1250 438 414 8561 1282 433 1251 438 414 1274 1252 438 1256 433 409 1277 407 1280 414 1272 412 1275 409 1278 1258 429 413 8549 1284 430 1254 434 408 1279 1257 429 1255 433 409 1279 415 1273 411 1277 407 1280 414 1274 1252 436 405 8557 1276 436 1258 431 411 1274 1252 434 1250 437 415 1272 412 1275 408 1280 414 1275 409 1280 1256 433 409 8558 1285 428 1256 431 411 1276 1250 438 1256 432 410 1278 416 1272 412 1273 411 1273 411 1273 1253 432 410 8539 1273 437 1247 436 405 1277 1249 434 1250 435 406 1276 408 1275 409 1275 409 1275 409 1275 1251 435 406 8543 1280 430 1254 431 411 1274 1252 431 1253 431 411 1273 410 1272 412 1273 411 1274 410 1276 1250 434 408 8542 1280 432 1252 434 408 1279 1257 430 1254 433 408 1277 407 1278 416 1270 413 1272 412 1275 1251 435 406 8540 1283 427 1257 429 413 1272 1254 431 1253 432 410 1275 409 1276 408 1278 406 1277 406 1276 1250 435 407 8538 1274 434 1250 434 408 1277 1249 436 1258 428 414 1273 411 1272 412 1270 414 1271 412 1273 1253 433 409 8535 1277 433 1251 436 406 1282 1254 433 1250 438 414 1274 410 1278 405 1280 414 1271 413 1276 1260 429 412 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1280 433 1251 436 405 1281 1255 431 1253 434 408 1278 405 1279 1257 431 411 1277 407 1280 414 1274 410 8547 1275 437 1257 428 413 1270 1255 430 1254 430 412 1273 411 1275 1251 436 405 1280 414 1271 412 1273 410 8550 1283 431 1253 434 408 1278 1258 427 1256 428 414 1271 412 1272 1254 434 408 1279 415 1271 412 1247 436 8544 1279 433 1251 435 407 1279 1257 430 1254 434 407 1278 405 1277 1249 437 415 1269 414 1269 415 1269 415 8537 1275 435 1248 435 406 1276 1250 436 1248 436 406 1278 406 1277 1248 437 404 1280 414 1269 414 1271 412 8532 1280 428 1255 428 413 1269 1246 437 1246 435 406 1276 408 1277 1248 436 406 1278 406 1279 415 1268 416 8538 1274 436 1258 427 415 1269 1257 427 1257 430 412 1272 412 1273 1252 434 408 1277 407 1280 414 1272 412 8553 1280 405 1279 434 408 1276 1250 435 1259 429 413 1273 411 1274 1252 434 408 1277 407 1279 415 1270 414 8548 1275 436 1258 428 414 1272 1254 433 1250 435 406 1277 406 1278 1258 429 413 1272 412 1275 409 1277 406 8549 1284 427 1257 430 412 1274 1251 433 1251 434 407 1277 406 1278 1248 437 415 1269 415 1271 412 1273 410 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8982 4433 598 1657 570 519 594 547 566 522 591 549 575 540 573 541 572 542 571 543 570 1659 568 1635 592 1637 600 1655 572 1657 570 1660 566 1636 601 1654 572 1630 597 517 596 545 568 520 593 547 566 548 576 539 574 540 573 540 573 1629 598 1631 596 1633 593 1635 592 1637 600 1629 597 1631 596 1633 593 521 592 522 591 549 575 540 573 514 599 542 571 543 570 544 569 1659 568 1635 592 1637 600 1628 598 1630 597 1632 595 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8990 4430 601 1628 598 517 596 519 594 547 566 549 574 540 573 542 571 544 569 546 567 1663 574 1628 598 1658 568 1662 575 1654 572 1658 568 1660 566 1663 574 1656 570 544 569 1660 566 548 575 539 574 541 572 542 571 544 569 545 568 1662 575 539 574 1656 570 1658 568 1661 565 1664 573 1656 570 1659 567 548 565 1664 573 542 571 543 570 545 568 546 567 548 565 549 574 1655 571 543 570 1659 567 1661 576 1628 598 1658 568 -# -name: Speed_up -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 11 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 0E 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 05 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 18 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 40 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1313 407 1306 414 432 1273 1306 415 1308 447 409 1275 438 1275 437 1275 438 1273 1306 449 407 1278 434 7939 1307 438 1285 408 427 1278 1312 409 1304 424 484 1229 432 1280 433 1280 433 1277 1313 415 431 1282 430 7925 1310 408 1304 414 432 1274 1305 415 1308 420 426 1286 427 1285 428 1284 429 1282 1308 420 426 1286 427 7944 1312 407 1306 413 433 1272 1307 413 1310 418 428 1284 429 1283 481 1230 431 1279 1311 417 429 1283 430 7922 1313 406 1307 412 434 1271 1308 412 1311 416 430 1283 429 1282 431 1281 432 1277 1313 415 431 1281 431 7935 1310 409 1365 353 482 1222 1306 415 1308 420 426 1285 428 1284 429 1283 429 1280 1310 418 428 1284 429 7922 1303 415 1308 411 435 1270 1310 411 1312 415 431 1280 432 1279 433 1278 435 1274 1305 423 433 1278 435 7930 1305 413 1310 409 426 1277 1313 408 1305 422 434 1277 436 1275 427 1284 429 1280 1310 418 428 1283 430 7918 1307 411 1312 407 428 1275 1304 416 1307 420 436 1275 427 1283 430 1281 431 1277 1313 414 432 1279 433 7928 1307 411 1312 406 429 1274 1305 414 1309 419 427 1283 430 1281 432 1279 434 1275 1304 449 407 1277 435 7909 1305 412 1311 408 427 1276 1303 416 1307 421 435 1275 427 1283 430 1281 431 1277 1313 414 432 1279 433 7925 1310 408 1305 414 432 1271 1308 412 1311 417 429 1281 431 1279 433 1276 436 1272 1308 420 426 1284 429 7914 1311 407 1368 351 433 1271 1308 412 1311 416 430 1281 431 1278 434 1276 426 1281 1309 419 427 1283 430 7928 1307 410 1364 354 481 1221 1307 414 1309 418 428 1282 431 1280 433 1277 436 1272 1307 420 436 1274 428 7913 1312 406 1307 412 434 1269 1310 410 1313 414 432 1278 434 1275 427 1282 431 1277 1313 415 431 1279 433 7922 1313 405 1307 411 435 1268 1311 409 1304 423 433 1277 435 1274 428 1281 431 1276 1314 414 432 1278 434 7905 1310 409 1314 404 431 1271 1308 412 1311 417 429 1280 432 1277 436 1274 428 1279 1311 417 429 1280 432 7922 1313 432 1281 438 408 1267 1312 435 1288 440 406 1277 436 1273 429 1280 432 1275 1315 413 433 1278 434 7903 1311 409 1314 404 431 1269 1310 413 1310 418 428 1306 407 1276 437 1273 429 1278 1312 419 427 1306 407 7921 1315 406 1307 412 434 1292 1287 410 1313 415 431 1302 411 1298 404 1279 434 1274 1316 415 431 1302 411 7901 1314 407 1306 413 433 1294 1285 411 1312 417 429 1304 409 1300 402 1281 432 1275 1315 416 430 1303 410 7917 1308 412 1311 409 426 1274 1305 417 1306 422 434 1274 428 1280 432 1277 436 1297 1283 421 435 1273 429 7906 1309 411 1312 407 428 1274 1306 416 1307 422 434 1274 428 1280 433 1276 426 1281 1309 421 435 1273 429 7901 1313 407 1306 414 432 1270 1310 412 1311 418 428 1280 433 1276 426 1282 431 1276 1314 416 430 1278 435 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1310 411 1312 407 428 1300 1279 417 1306 419 1304 423 433 1302 410 1302 400 1282 1307 421 1302 425 431 7882 1312 409 1304 414 432 1296 1283 413 1310 415 1308 445 401 1308 404 1280 432 1276 1314 414 1309 418 428 7849 1314 406 1306 412 434 1293 1286 410 1302 422 1311 416 430 1304 409 1276 437 1272 1307 420 1303 423 433 7876 1307 413 1309 408 427 1300 1279 416 1307 418 1305 421 435 1299 403 1281 431 1277 1313 415 1308 418 428 7844 1308 411 1302 416 430 1297 1282 413 1310 415 1308 418 428 1305 407 1303 409 1271 1308 419 1304 422 434 7869 1304 416 1307 411 424 1302 1277 418 1305 419 1304 422 434 1299 403 1306 407 1275 1304 422 1301 425 431 7837 1305 414 1309 408 427 1299 1280 414 1309 415 1307 418 428 1305 408 1301 401 1280 1309 416 1307 419 427 7874 1309 410 1303 414 432 1294 1285 409 1303 446 1277 423 433 1299 403 1305 407 1273 1306 420 1303 423 433 7830 1312 407 1305 412 434 1292 1277 417 1306 418 1305 421 425 1307 406 1303 409 1271 1308 417 1305 420 426 7871 1312 407 1306 411 424 1275 1304 416 1307 417 1306 419 427 1280 432 1275 427 1279 1311 415 1308 417 429 7833 1309 410 1303 414 432 1268 1311 408 1305 419 1304 421 425 1282 430 1277 436 1296 1283 416 1307 419 427 7868 1305 414 1309 408 427 1272 1307 412 1310 413 1310 416 430 1277 425 1282 430 1301 1278 421 1302 424 432 7829 1303 415 1308 409 426 1274 1305 414 1308 415 1308 418 428 1279 433 1274 428 1278 1311 413 1310 416 430 7866 1307 411 1301 415 431 1270 1309 410 1303 421 1302 423 433 1275 427 1279 433 1273 1306 419 1304 422 434 7826 1306 413 1310 407 428 1272 1307 412 1311 413 1310 416 430 1277 425 1282 430 1275 1304 421 1302 424 432 7844 1308 410 1302 415 430 1269 1310 410 1302 421 1302 424 432 1274 428 1279 433 1299 1280 419 1304 422 434 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1306 440 1282 436 410 1269 1310 437 1286 442 404 1283 429 1282 430 1282 430 1283 429 1281 1308 419 427 7925 1309 436 1286 406 429 1275 1304 417 1306 447 409 1277 435 1276 436 1276 426 1286 426 1284 1305 448 408 7898 1305 440 1283 435 411 1268 1311 435 1277 450 406 1279 433 1278 434 1277 435 1276 436 1273 1306 448 408 7914 1309 435 1277 440 406 1272 1307 440 1283 444 402 1283 429 1282 430 1281 431 1280 432 1277 1312 442 404 7898 1305 440 1283 436 410 1267 1312 435 1288 440 406 1278 434 1277 435 1276 436 1274 428 1282 1307 446 410 7910 1313 431 1281 437 409 1268 1311 436 1287 440 406 1278 434 1277 435 1275 427 1284 428 1281 1308 445 401 7899 1304 441 1282 436 410 1267 1312 435 1288 439 407 1278 434 1276 436 1275 427 1283 429 1280 1309 444 401 7914 1309 436 1287 431 404 1272 1307 439 1283 443 403 1281 431 1279 433 1277 435 1275 427 1281 1308 445 401 7898 1305 439 1284 434 401 1275 1304 442 1280 446 410 1273 429 1281 431 1279 433 1278 434 1273 1306 448 408 7905 1308 437 1286 432 403 1273 1306 440 1283 444 402 1281 431 1279 433 1276 436 1274 428 1280 1309 444 401 7894 1309 435 1288 430 405 1271 1308 438 1285 442 404 1278 434 1275 437 1273 429 1280 432 1276 1313 440 406 7905 1308 437 1286 431 404 1272 1307 439 1284 442 403 1279 433 1276 436 1273 429 1280 432 1275 1314 439 407 7885 1307 437 1286 431 404 1271 1308 438 1285 442 404 1278 434 1274 428 1282 430 1279 433 1274 1305 448 408 7901 1312 432 1280 436 410 1266 1313 432 1280 446 410 1272 430 1278 434 1275 427 1282 430 1276 1314 440 406 7906 1307 436 1286 431 404 1270 1309 437 1286 440 406 1276 436 1273 429 1279 433 1276 426 1280 1309 444 402 7882 1311 434 1278 438 408 1267 1312 434 1278 447 409 1273 429 1279 433 1275 427 1281 431 1276 1313 439 407 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1316 405 1307 412 434 1270 1309 413 1310 416 1307 421 435 1276 436 1276 436 1275 427 1283 1307 421 1312 7037 1310 411 1312 407 428 1274 1305 418 1305 420 1303 425 431 1278 434 1277 435 1276 436 1274 1305 423 1310 7006 1310 411 1312 406 429 1274 1305 416 1306 419 1304 423 433 1303 409 1275 427 1284 428 1281 1308 420 1303 7043 1314 407 1305 412 434 1295 1284 412 1310 414 1309 419 427 1308 404 1280 432 1279 433 1275 1314 413 1310 7003 1313 407 1305 413 433 1294 1285 411 1301 424 1309 418 428 1306 406 1304 408 1276 436 1273 1306 421 1302 7041 1306 414 1309 410 425 1301 1278 418 1305 420 1303 424 432 1302 400 1310 402 1282 430 1277 1312 415 1307 7003 1313 407 1305 413 433 1294 1285 410 1302 423 1310 416 430 1304 408 1302 400 1284 428 1279 1310 417 1306 7033 1314 406 1307 412 434 1292 1287 408 1304 420 1303 424 432 1301 401 1308 404 1279 433 1274 1305 422 1311 6995 1311 410 1302 415 431 1295 1284 411 1301 423 1310 416 430 1303 409 1299 403 1280 432 1275 1304 422 1301 7034 1313 406 1307 411 424 1301 1278 417 1305 419 1303 423 433 1299 403 1305 407 1275 427 1281 1308 418 1304 6997 1309 411 1301 416 430 1295 1284 411 1301 422 1301 425 431 1301 401 1307 405 1277 435 1271 1308 418 1305 7028 1308 411 1301 415 431 1294 1285 410 1302 422 1301 425 431 1300 402 1306 406 1276 426 1280 1309 416 1307 6993 1312 407 1305 412 434 1291 1278 416 1307 417 1306 420 426 1306 406 1301 401 1281 431 1275 1304 422 1301 7029 1308 411 1301 415 431 1268 1311 409 1303 420 1303 423 433 1273 429 1278 434 1274 428 1277 1312 414 1309 6989 1306 412 1310 406 429 1270 1309 411 1301 422 1301 424 432 1274 428 1279 433 1275 427 1278 1311 414 1309 7019 1307 411 1301 415 431 1268 1311 408 1304 419 1304 421 435 1271 431 1276 426 1282 430 1275 1304 421 1302 6994 1312 406 1306 410 425 1274 1305 414 1309 414 1339 386 429 1277 425 1281 431 1276 426 1305 1284 414 1309 7018 1308 409 1303 413 433 1267 1302 417 1337 386 1337 388 458 1248 433 1273 429 1278 434 1271 1308 416 1338 6956 1308 410 1333 382 464 1236 1333 386 1337 386 1337 388 458 1248 454 1252 429 1277 435 1270 1340 384 1338 6949 1336 382 1330 386 460 1239 1330 388 1335 388 1335 391 455 1250 462 1244 458 1249 463 1241 1338 386 1336 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1289 402 1282 405 442 1268 1263 400 1284 403 433 1276 418 1242 442 1245 439 1248 446 1240 444 1243 1288 7090 1288 402 1282 405 442 1268 1263 400 1284 402 434 1276 418 1241 443 1244 440 1247 447 1240 444 1243 1288 7090 1288 402 1282 405 442 1269 1262 400 1284 402 445 1267 417 1243 441 1246 438 1249 445 1242 442 1245 1286 7092 1286 403 1291 395 441 1244 1287 402 1282 404 443 1242 442 1245 439 1249 445 1242 442 1245 439 1248 1293 7086 1292 398 1286 401 435 1248 1283 407 1287 399 437 1247 437 1250 444 1243 441 1246 438 1249 445 1242 1289 7090 1288 402 1282 405 442 1269 1262 401 1283 403 444 1268 416 1244 440 1247 437 1250 444 1243 441 1247 1284 7096 1293 397 1287 401 435 1275 1256 407 1287 400 436 1274 410 1250 444 1243 441 1247 437 1250 444 1243 1288 7092 1286 404 1280 408 439 1272 1259 403 1281 406 441 1270 414 1246 438 1250 444 1243 441 1246 438 1250 1291 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1291 399 1285 404 443 1243 1288 401 1283 404 443 1243 441 1245 439 1249 1282 407 440 1246 438 1249 445 7935 1291 401 1283 404 443 1268 1263 400 1284 403 444 1268 416 1245 439 1248 1283 408 439 1272 412 1249 445 7935 1291 400 1284 403 444 1268 1263 400 1284 403 433 1277 417 1244 440 1274 1257 407 440 1272 412 1248 446 7935 1290 401 1283 404 443 1242 1289 400 1284 404 443 1242 442 1245 439 1275 1256 407 440 1246 438 1249 445 7936 1290 400 1284 404 443 1243 1288 400 1284 404 443 1243 441 1245 439 1248 1283 407 440 1246 438 1249 445 7935 1290 401 1283 404 443 1268 1263 401 1283 404 443 1268 416 1245 439 1248 1283 408 439 1273 411 1250 444 7937 1288 403 1281 406 441 1271 1260 403 1281 406 441 1270 414 1247 447 1241 1290 400 436 1275 419 1241 443 7940 1286 405 1289 398 438 1273 1258 404 1280 408 439 1272 412 1249 445 1242 1289 402 435 1276 418 1242 442 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1293 395 1289 398 438 1248 1293 395 1289 398 438 1248 446 1241 443 1245 439 1249 1292 396 440 1245 439 7942 1294 395 1289 398 438 1247 1284 404 1290 397 439 1246 438 1250 444 1243 440 1247 1284 405 442 1244 440 7940 1285 404 1290 396 440 1246 1285 403 1291 396 440 1245 438 1249 445 1242 442 1245 1286 403 444 1242 442 7939 1286 402 1292 395 441 1245 1286 402 1292 394 442 1244 440 1248 446 1241 443 1245 1286 402 445 1242 442 7941 1284 404 1290 397 439 1248 1293 395 1289 398 438 1248 446 1242 442 1246 438 1250 1291 396 440 1247 447 7934 1291 398 1286 401 446 1241 1290 399 1285 402 445 1242 442 1246 438 1250 444 1244 1287 401 446 1241 443 7940 1285 403 1291 396 440 1247 1284 404 1290 397 439 1247 437 1251 443 1245 439 1249 1292 396 440 1246 438 7945 1291 398 1286 401 446 1240 1291 398 1286 401 446 1240 444 1244 439 1248 446 1242 1289 399 437 1249 445 -# -name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 46 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 44 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 43 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 16 00 00 00 -# -name: Mode -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 0D 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1308 408 1311 402 442 1250 1314 412 1307 405 449 1242 446 1250 449 1246 453 1243 445 1250 449 1248 1316 7148 1306 411 1308 405 449 1242 1311 415 1304 408 446 1246 453 1244 444 1251 448 1247 451 1244 444 1252 1312 7154 1309 406 1303 410 444 1247 1306 420 1309 404 450 1241 447 1249 450 1247 451 1244 444 1252 447 1249 1315 7150 1314 403 1306 407 447 1245 1308 417 1302 412 442 1250 449 1248 450 1246 442 1254 444 1251 448 1249 1304 7160 1314 429 1280 433 421 1248 1305 445 1284 429 415 1252 447 1250 449 1247 441 1254 445 1252 447 1250 1303 7164 1310 404 1305 410 444 1251 1302 420 1309 404 440 1257 441 1255 443 1252 447 1250 449 1248 440 1256 1307 7159 1304 409 1310 403 441 1254 1309 413 1306 407 447 1248 440 1255 443 1252 446 1249 450 1246 442 1253 1362 7102 1310 404 1305 409 445 1251 1302 420 1310 403 441 1254 444 1251 447 1248 440 1255 443 1253 446 1250 1303 7163 1311 403 1306 408 446 1249 1304 418 1301 412 442 1253 446 1250 449 1248 440 1256 442 1254 445 1251 1302 7162 1302 413 1306 407 447 1248 1305 417 1302 412 442 1254 445 1251 448 1248 440 1256 442 1253 446 1249 1304 7155 1308 405 1304 409 445 1251 1302 420 1309 404 440 1255 443 1253 445 1250 448 1248 440 1255 495 1202 1310 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1317 406 1313 409 445 1250 1324 410 1319 403 451 1243 445 1252 447 1250 500 1196 1316 408 446 1248 451 7997 1311 408 1311 407 447 1245 1319 411 1308 408 446 1248 450 1247 452 1246 453 1244 1309 411 443 1251 448 8000 1308 411 1308 408 446 1247 1317 412 1307 410 444 1248 451 1246 442 1253 446 1251 1313 407 447 1246 452 7995 1313 406 1313 404 450 1244 1320 410 1309 409 445 1248 451 1245 454 1243 445 1251 1313 434 420 1245 453 7992 1317 430 1279 437 417 1251 1313 442 1277 439 415 1252 447 1250 449 1248 451 1247 1317 411 454 1241 447 8003 1315 402 1307 407 447 1244 1309 417 1302 411 443 1249 450 1246 442 1253 446 1251 1313 406 448 1272 416 8004 1315 404 1305 410 444 1277 1286 413 1306 408 446 1274 425 1272 416 1253 446 1278 1286 408 446 1274 425 7994 1314 406 1313 406 448 1273 1291 415 1314 406 448 1274 425 1273 415 1253 446 1252 1312 411 443 1278 421 7997 1312 410 1309 409 445 1275 1289 413 1306 412 442 1279 420 1277 422 1275 424 1273 1280 414 440 1253 446 7999 1309 411 1308 409 445 1250 1303 422 1308 409 445 1250 449 1247 441 1255 444 1252 1312 407 447 1248 440 8004 1315 404 1305 412 442 1253 1311 414 1305 410 444 1250 449 1246 442 1253 446 1250 1314 404 440 1257 442 8001 1307 411 1308 408 446 1248 1305 421 1308 407 447 1247 441 1254 445 1252 447 1250 1314 404 440 1257 442 8005 1314 404 1305 413 441 1254 1310 416 1303 413 441 1254 445 1251 448 1248 440 1257 1307 410 444 1252 447 7998 1310 407 1312 405 439 1255 1309 416 1303 411 443 1251 448 1248 440 1254 445 1250 1303 411 443 1251 448 7994 1304 414 1305 411 443 1251 1302 422 1308 408 446 1248 440 1254 445 1251 448 1248 1305 411 443 1253 446 7997 1312 406 1303 413 441 1253 1311 413 1306 408 446 1248 440 1255 444 1253 446 1251 1313 404 440 1256 443 8002 1307 411 1308 408 446 1248 1305 419 1300 414 440 1254 445 1250 449 1247 441 1255 1309 409 445 1249 450 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1313 405 1314 405 449 1250 1313 420 1320 406 448 1252 447 1253 446 1254 445 1255 443 1255 1319 404 450 8002 1316 406 1313 409 445 1253 1311 422 1318 407 499 1202 445 1253 446 1253 445 1254 445 1254 1320 404 450 7999 1309 408 1311 406 448 1248 1305 421 1308 411 443 1253 446 1251 447 1249 449 1248 440 1257 1306 409 445 8000 1308 408 1311 405 449 1247 1306 418 1311 405 449 1248 440 1256 443 1253 446 1251 448 1250 1313 404 450 7996 1312 436 1283 407 447 1250 1313 414 1315 405 449 1250 448 1249 449 1248 450 1246 442 1255 1319 402 452 7991 1307 439 1280 412 442 1255 1308 420 1309 411 443 1255 444 1254 445 1253 446 1253 446 1253 1321 406 448 7998 1320 406 1323 403 451 1249 1314 421 1319 408 446 1254 444 1255 443 1255 443 1256 442 1256 1318 408 446 8004 1314 412 1317 409 445 1255 1318 417 1323 404 450 1250 448 1251 447 1252 447 1253 445 1255 1319 407 447 8003 1315 410 1319 407 447 1252 1322 414 1315 410 444 1256 453 1247 451 1248 450 1247 441 1256 1307 438 416 8002 1316 431 1288 429 415 1252 1311 444 1285 431 423 1241 447 1249 449 1247 451 1248 450 1246 1317 402 442 8002 1316 403 1306 411 443 1251 1312 416 1313 404 450 1244 444 1252 447 1251 447 1249 449 1247 1316 402 442 8002 1316 403 1306 410 444 1250 1313 414 1305 410 444 1249 450 1246 452 1244 444 1253 446 1252 1311 407 447 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1292 433 1265 432 406 1261 1286 438 1260 436 413 1255 443 1253 444 1252 435 1260 438 1259 439 1257 1290 7192 1288 436 1262 434 415 1279 1257 440 1258 438 411 1284 414 1256 442 1254 444 1253 445 1252 435 1261 1286 7196 1284 440 1258 438 411 1257 1290 434 1264 432 417 1252 435 1260 438 1259 439 1257 441 1255 443 1253 1283 7198 1292 431 1256 440 409 1259 1288 436 1262 434 415 1253 434 1261 437 1259 439 1257 441 1255 443 1253 1283 7198 1292 431 1256 439 410 1258 1289 435 1263 433 416 1252 435 1261 437 1258 440 1256 442 1254 444 1252 1284 7198 1292 405 1282 440 409 1258 1289 435 1263 433 416 1252 435 1260 438 1258 440 1256 442 1254 444 1252 1284 7196 1284 440 1258 438 411 1257 1290 433 1265 431 407 1260 438 1284 414 1256 442 1254 444 1252 435 1260 1287 7195 1285 438 1260 436 413 1255 1292 405 1282 440 409 1259 439 1257 441 1255 443 1254 444 1252 435 1260 1287 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1287 410 1288 435 414 1281 1255 414 1284 439 410 1259 439 1283 415 1254 444 1252 435 1261 1286 437 412 8042 1287 410 1288 434 415 1255 1281 440 1258 438 411 1258 440 1255 443 1253 434 1261 437 1259 1288 408 441 8039 1290 406 1292 431 407 1262 1285 411 1287 435 414 1256 442 1253 445 1251 436 1260 438 1258 1289 434 415 8039 1290 406 1281 441 408 1287 1260 409 1289 434 415 1254 433 1261 437 1259 439 1283 415 1255 1292 405 433 8046 1283 413 1285 438 411 1285 1262 434 1264 431 407 1288 410 1259 439 1257 441 441 1253 1283 413 436 8044 1285 412 1286 436 413 1257 1290 405 1282 440 409 1287 411 1284 414 1281 417 1253 434 1261 1286 437 412 8042 1287 436 1262 434 415 1281 1256 440 1258 411 438 1258 440 1255 443 1253 434 1261 437 1258 1289 434 415 8039 1290 406 1281 440 409 1287 1260 409 1289 433 416 1253 434 1261 437 1259 439 1256 442 1254 1293 404 434 8046 1283 413 1285 411 438 1257 1290 406 1281 414 435 1287 411 1257 441 1255 443 1253 434 1261 1286 438 411 8042 1287 409 1289 433 416 1280 1257 439 1259 436 413 1256 442 1253 445 1251 436 1286 412 1283 1264 433 416 8037 1292 404 1283 412 437 1285 1262 408 1290 432 417 1278 409 1259 439 1257 441 1255 443 1253 1283 439 410 8043 1285 437 1261 435 414 1281 1255 440 1258 438 411 1283 415 1254 444 1252 435 1260 438 1258 1289 434 414 8038 1291 432 1255 440 409 1286 1261 435 1263 433 416 1251 436 1259 439 1257 441 1255 443 1253 1283 440 409 8044 1284 411 1287 436 413 1254 1293 431 1256 439 410 1257 441 1281 417 1253 434 1261 437 1259 1288 435 413 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1288 437 1260 435 414 1254 1292 431 1256 440 409 1259 438 1258 1288 435 413 1255 442 1253 444 1252 435 8046 1292 431 1256 440 409 1259 1288 436 1261 434 414 1254 443 1253 1283 440 409 1259 438 1258 439 1256 441 8040 1287 436 1261 434 414 1254 1292 431 1256 439 409 1258 491 1204 1291 433 415 1253 434 1261 436 1260 438 8044 1284 440 1257 438 410 1257 1290 434 1263 432 417 1251 436 1259 1287 436 412 1255 442 1254 443 1252 435 8045 1293 431 1256 439 410 1258 1289 435 1262 433 416 1253 434 1261 1285 439 410 1258 440 1256 442 1255 442 8038 1290 433 1264 431 407 1260 1287 437 1261 435 413 1254 443 1253 1283 440 408 1259 439 1257 440 1255 442 8038 1289 434 1263 432 417 1251 1285 438 1260 436 413 1255 442 1253 1283 441 408 1260 438 1258 440 1256 442 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1289 408 1289 434 414 1245 1291 433 1264 432 416 1270 417 1243 444 1243 444 1243 444 1244 443 1244 1292 7134 1291 405 1292 431 417 1242 1294 429 1268 427 411 1248 439 1248 439 1274 413 1247 440 1247 440 1247 1289 8241 1295 400 1287 436 412 1247 1289 434 1263 432 416 1242 445 1242 445 1242 445 1241 446 1241 435 1251 1295 7130 1295 400 1287 436 412 1246 1290 406 1291 431 417 1268 419 1241 446 1241 435 1251 436 1277 410 1250 1286 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1369 328 1296 427 411 1275 1261 436 1261 434 414 1245 442 1245 442 1244 443 1244 443 1244 1291 431 417 7972 1291 404 1293 429 409 1251 1295 400 1287 436 412 1273 414 1246 441 1245 442 1245 442 1245 1290 405 443 9076 1287 408 1289 433 415 1271 1264 430 1267 428 410 1276 411 1249 438 1248 439 1248 439 1274 1261 434 414 7974 1289 406 1291 431 417 1242 1294 428 1259 436 412 1273 414 1273 414 1245 442 1245 442 1244 1292 430 418 -# -name: Power -type: parsed -protocol: NECext -address: 00 FC 00 00 -command: 80 7F 00 00 -# -name: Speed_up -type: parsed -protocol: NECext -address: 00 FC 00 00 -command: 85 7A 00 00 -# -name: Mode -type: parsed -protocol: NECext -address: 00 FC 00 00 -command: 81 7E 00 00 -# -name: Timer -type: parsed -protocol: NECext -address: 00 FC 00 00 -command: 86 79 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1299 409 1271 412 433 1211 1293 416 1274 409 436 1207 462 1207 462 1208 461 1208 461 1209 460 1209 1296 7131 1300 410 1270 386 459 1210 1294 389 1301 382 463 1206 464 1206 463 1207 463 1208 462 1208 461 1209 1295 8208 1302 382 1298 385 460 1208 1297 387 1293 390 455 1213 456 1214 466 1205 465 1205 464 1206 463 1206 1298 7129 1302 382 1298 385 460 1208 1297 387 1293 390 455 1213 457 1213 467 1204 465 1205 464 1205 464 1206 1298 8207 1293 391 1299 383 452 1218 1297 386 1294 388 457 1213 456 1214 455 1215 454 1216 454 1216 453 1217 1298 7130 1290 415 1275 406 429 1219 1296 409 1271 411 434 1212 457 1213 456 1213 456 1214 455 1188 481 1215 1299 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1296 386 1294 414 431 1212 1292 416 1274 409 436 1206 463 1207 462 1207 463 1207 463 1208 1297 411 434 7953 1292 417 1273 408 437 1206 1298 411 1269 413 432 1210 459 1210 459 1211 458 1211 458 1211 1293 415 430 9123 1293 389 1301 407 438 1205 1299 410 1269 412 433 1209 460 1210 459 1210 459 1210 459 1210 1294 388 457 7957 1298 410 1270 413 432 1211 1293 389 1301 408 437 1205 464 1206 463 1206 463 1207 463 1207 1297 385 460 9120 1296 387 1293 416 429 1214 1301 381 1299 410 435 1208 461 1209 460 1209 460 1210 459 1210 1294 414 431 7958 1297 385 1295 414 431 1212 1303 380 1300 409 436 1207 462 1207 463 1207 462 1209 460 1208 1296 386 459 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1295 410 1270 411 434 1210 1294 412 1268 414 431 1238 431 1212 1302 405 429 1214 455 1266 1249 406 428 7959 1296 410 1270 412 433 1210 1294 413 1267 414 431 1213 456 1213 1301 405 429 1214 455 1266 1249 406 428 9065 1299 406 1274 408 437 1207 1297 409 1271 411 434 1236 433 1210 1294 412 433 1211 458 1264 1240 414 431 7958 1297 409 1271 411 434 1209 1295 412 1268 414 431 1238 431 1212 1302 405 429 1214 455 1267 1248 407 427 9067 1298 383 1297 410 435 1209 1295 412 1268 414 431 1238 431 1212 1292 415 430 1214 455 1266 1249 407 427 7962 1293 414 1276 406 428 1215 1300 408 1272 410 435 1235 434 1209 1295 411 434 1210 459 1262 1242 413 432 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1300 381 1299 410 435 1208 1296 413 1267 415 430 1213 456 1213 456 1214 455 1214 1301 408 437 1207 462 7950 1295 413 1267 416 429 1214 1301 381 1299 410 435 1208 461 1208 461 1208 461 1208 1296 386 459 1211 458 9008 1296 387 1293 415 430 1213 1301 407 1273 410 435 1208 461 1208 461 1208 461 1208 1296 386 459 1210 459 7952 1293 416 1274 408 437 1206 1298 384 1296 413 432 1211 458 1211 458 1211 458 1211 1304 405 429 1214 455 9013 1301 407 1273 410 435 1259 1245 412 1268 415 430 1264 405 1213 456 1213 456 1213 1301 407 438 1256 403 7958 1297 410 1270 413 432 1262 1253 405 1275 407 427 1267 402 1216 453 1216 453 1216 1298 409 436 1259 410 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1294 412 1268 415 430 1265 1250 408 1272 411 434 1260 409 1210 1294 413 432 1264 405 1213 456 1215 454 7959 1296 411 1269 415 430 1265 1250 408 1271 410 435 1260 409 1234 1270 413 432 1264 405 1238 431 1214 455 9016 1298 410 1270 414 431 1264 1251 407 1273 409 436 1260 409 1260 1244 413 432 1263 406 1237 433 1213 456 7959 1296 412 1268 415 430 1265 1250 407 1273 410 435 1260 409 1234 1270 414 431 1264 405 1214 455 1215 454 9017 1297 411 1269 414 431 1264 1251 407 1273 410 435 1260 409 1209 1295 387 458 1237 432 1213 456 1214 456 7959 1296 413 1267 416 429 1239 1276 409 1271 412 433 1235 434 1211 1304 379 455 1239 430 1215 454 1216 453 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1294 388 1291 390 455 1215 1289 392 1298 384 451 1220 460 1210 459 1210 1294 388 457 1212 457 1212 457 7954 1301 382 1298 384 461 1207 1297 387 1293 389 456 1212 457 1212 457 1213 1291 392 463 1205 465 1205 464 9263 1295 414 1266 417 428 1214 1301 409 1271 412 433 1209 460 1209 460 1210 1294 415 430 1213 456 1213 456 7959 1296 413 1267 415 430 1213 1302 408 1271 411 434 1208 461 1209 460 1209 1295 414 431 1212 457 1213 456 9272 1297 412 1268 415 430 1212 1292 417 1273 410 435 1207 462 1208 461 1208 1296 413 432 1211 458 1211 458 7956 1299 410 1270 413 432 1210 1294 390 1300 409 436 1206 463 1207 463 1207 1298 387 458 1210 459 1211 458 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1251 410 1245 415 407 1252 1246 415 1250 410 412 1247 408 1252 413 1247 408 1252 413 1247 408 1252 1246 7350 1248 438 1227 433 389 1245 1253 434 1221 439 383 1250 415 1245 410 1250 405 1255 410 1249 406 1255 1253 7340 1247 439 1226 407 415 1245 1253 433 1222 412 410 1250 405 1255 410 1249 406 1254 411 1249 406 1254 1254 7340 1247 412 1253 407 415 1245 1253 407 1248 412 410 1250 405 1255 410 1249 406 1254 411 1248 407 1253 1245 7349 1249 411 1254 406 405 1254 1254 406 1249 411 411 1248 407 1252 413 1246 409 1251 414 1245 410 1250 1248 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1250 409 1246 441 381 1252 1246 414 1252 435 387 1246 409 1250 405 1255 1243 417 405 1281 384 1275 380 8188 1244 416 1249 437 385 1249 1249 437 1218 443 379 1253 412 1248 407 1253 1245 415 407 1252 413 1246 409 8186 1246 414 1251 435 387 1272 1226 408 1247 439 383 1276 379 1281 384 1249 1249 437 385 1248 407 1253 412 8181 1251 409 1246 440 382 1277 1221 439 1226 434 378 1255 410 1249 406 1254 1244 416 406 1253 412 1247 408 8187 1245 441 1225 435 387 1273 1225 435 1220 440 382 1251 414 1245 410 1250 1248 412 410 1249 406 1254 411 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1243 444 1221 438 384 1276 1222 438 1217 443 379 1254 411 1274 381 1252 413 1246 409 1251 1247 413 409 8185 1247 440 1225 435 387 1246 1252 408 1247 440 382 1277 388 1245 410 1276 379 1280 385 1249 1249 411 411 8184 1248 412 1243 444 378 1282 1216 444 1221 439 383 1250 405 1254 411 1248 407 1253 412 1248 1250 410 412 8182 1250 410 1245 442 380 1253 1245 415 1250 437 385 1247 408 1252 413 1272 383 1251 414 1272 1226 408 414 8180 1252 409 1246 440 382 1252 1246 440 1225 435 387 1272 383 1250 405 1254 411 1249 406 1254 1244 416 406 8188 1244 416 1249 437 385 1249 1249 411 1244 443 379 1253 412 1273 382 1278 387 1246 409 1251 1247 413 409 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1305 379 1283 402 434 1251 431 1253 1276 408 1275 411 435 1249 1280 404 432 1253 1276 408 438 1247 1282 7144 1281 404 1279 407 439 1247 435 1250 1310 375 1307 379 467 1218 1311 374 431 1254 1306 379 436 1249 1311 7119 1305 380 1302 382 464 1222 460 1224 1305 380 1302 382 464 1221 1308 377 469 1217 1302 383 463 1222 1307 7121 1303 380 1303 382 464 1221 462 1223 1306 378 1305 381 465 1220 1309 375 430 1254 1306 380 435 1250 1279 7148 1276 408 1274 411 435 1250 432 1252 1277 408 1274 410 436 1249 1280 404 432 1253 1276 408 438 1247 1282 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1282 402 1281 404 432 1252 430 1254 1275 408 1275 410 436 1248 435 1249 434 1251 1278 405 431 1254 439 7984 1277 407 1276 408 438 1246 436 1248 1281 402 1281 403 433 1251 432 1253 430 1254 1275 408 438 1246 437 7985 1276 407 1276 408 438 1246 436 1248 1281 402 1281 403 433 1252 431 1253 440 1245 1274 409 437 1248 435 7990 1333 351 1332 352 433 1252 431 1254 1326 358 1335 349 436 1249 434 1250 433 1252 1328 355 440 1244 438 7984 1328 355 1328 356 439 1245 438 1247 1333 350 1333 351 434 1250 433 1252 431 1253 1327 357 438 1246 437 7988 1273 410 1273 411 435 1249 434 1250 1279 405 1278 406 440 1244 439 1246 436 1247 1282 402 434 1251 432 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1282 403 1279 405 431 1254 439 1246 1273 412 434 1251 432 1254 1275 410 436 1249 433 1251 431 1254 1275 7152 1283 402 1281 405 431 1254 439 1246 1273 412 434 1251 432 1254 1305 379 436 1249 433 1252 430 1254 1306 7122 1302 382 1311 374 462 1223 459 1226 1303 381 465 1221 461 1223 1306 379 467 1218 464 1221 461 1224 1305 7123 1312 373 1310 375 461 1224 469 1216 1303 381 465 1221 461 1223 1306 379 467 1218 464 1221 461 1224 1305 7123 1312 374 1308 376 460 1225 468 1218 1311 374 462 1223 459 1226 1303 382 464 1222 460 1224 469 1217 1302 7128 1306 379 1303 382 464 1221 461 1224 1305 380 466 1219 463 1221 1277 408 469 1217 465 1219 463 1222 1307 7122 1282 403 1279 405 462 1223 459 1226 1283 402 465 1221 461 1224 1274 410 467 1218 464 1221 461 1224 1274 7155 1280 405 1278 408 438 1247 435 1250 1279 406 440 1245 437 1249 1280 405 431 1255 438 1249 433 1252 1277 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1279 406 1307 377 438 1246 436 1249 1311 373 463 1221 462 1224 469 1216 466 1218 465 1220 463 1222 460 7967 1304 380 1303 382 464 1221 462 1223 1276 409 468 1217 466 1220 463 1223 459 1225 468 1217 465 1220 462 7966 1275 410 1303 382 464 1220 463 1222 1276 409 468 1218 465 1220 463 1223 459 1225 468 1217 435 1250 463 7965 1306 378 1304 381 465 1220 463 1222 1307 378 468 1217 465 1219 433 1253 440 1245 437 1248 434 1251 462 7966 1275 411 1282 404 432 1254 439 1248 1281 404 432 1254 439 1247 436 1250 433 1253 440 1246 437 1248 435 7995 1276 409 1284 402 434 1252 431 1255 1274 411 435 1251 431 1254 439 1247 436 1250 433 1253 440 1246 436 7992 1279 407 1276 410 436 1249 433 1252 1277 409 447 1238 434 1252 431 1254 439 1247 435 1250 433 1253 440 7990 1281 404 1278 406 440 1245 438 1249 1280 405 441 1245 437 1248 435 1251 432 1254 439 1247 435 1250 433 -# -name: Power -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 01 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 05 00 00 00 -# -name: Speed_dn -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 1B 00 00 00 -#Timer DOWN -name: Timer -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 09 00 00 00 -#Rotate -name: Rotate -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 03 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1397 357 1370 357 500 1188 1427 331 1343 383 473 1240 476 1240 476 1240 475 1240 475 1240 475 1240 1370 7358 1367 361 1366 361 496 1220 1366 361 1366 361 496 1220 495 1220 495 1220 495 1221 494 1220 495 1220 1366 7361 1365 361 1366 361 495 1221 1365 362 1365 362 494 1221 494 1221 494 1220 495 1220 495 1221 494 1220 1365 7361 1364 361 1366 362 494 1221 1365 362 1365 362 494 1221 494 1221 494 1221 494 1221 494 1221 494 1221 1365 7361 1364 362 1364 362 494 1221 1364 362 1365 362 495 1221 494 1221 494 1221 494 1221 494 1221 494 1221 1364 7361 1364 363 1364 363 493 1222 1364 363 1363 363 493 1223 492 1223 492 1223 492 1247 468 1223 492 1247 1339 7386 1338 388 1338 388 468 1247 1339 388 1338 388 468 1248 467 1247 468 1247 468 1247 468 1248 467 1247 1338 7387 1337 389 1338 389 467 1248 1338 389 1337 389 467 1248 467 1248 467 1248 467 1248 467 1248 467 1248 1337 7388 1336 389 1337 389 467 1249 1336 390 1337 390 466 1249 466 1249 466 1249 466 1249 466 1249 466 1248 1337 7388 1312 414 1312 414 465 1251 1335 391 1337 390 441 1274 441 1274 465 1249 464 1250 442 1274 441 1273 1337 7388 1311 414 1312 415 441 1274 1311 415 1311 415 441 1274 441 1274 441 1274 441 1274 441 1274 441 1274 1311 7413 1311 415 1312 415 441 1274 1311 415 1311 416 440 1275 440 1275 440 1275 440 1275 440 1275 439 1275 1310 7414 1309 416 1310 417 439 1276 1310 417 1309 417 438 1277 438 1277 438 1277 438 1301 413 1301 414 1301 1285 7439 1284 442 1284 442 414 1301 1284 443 1283 443 413 1302 413 1302 413 1302 413 1302 412 1302 413 1302 1283 7441 1283 443 1284 443 412 1303 1283 444 1282 444 412 1303 411 1303 412 1303 412 1303 412 1303 412 1303 1283 7441 1282 445 1281 445 411 1304 1282 470 1256 471 385 1330 385 1330 385 1330 385 1330 385 1330 385 1330 1256 7468 1255 471 1256 471 384 1331 1255 471 1255 472 383 1331 384 1331 383 1332 383 1332 383 1331 383 1332 1254 7470 1253 498 1228 499 356 1358 1228 499 1227 499 356 1359 356 1359 356 1359 356 1359 355 1360 355 1359 1227 7497 1226 526 1200 527 327 1387 1200 527 1199 554 300 1414 301 1415 299 1415 299 1415 300 1416 299 1415 1172 7553 1170 609 1117 583 270 1471 1117 637 1089 692 118 10334 871 -#Osc -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1395 358 1369 358 499 1215 1399 331 1341 384 472 1241 474 1240 475 1240 1345 383 473 1240 474 1240 474 8239 1366 387 1339 387 469 1246 1339 388 1339 387 469 1246 469 1246 469 1246 1339 388 469 1246 469 1246 469 8242 1339 387 1339 387 469 1246 1339 387 1339 387 469 1246 469 1246 469 1246 1339 387 469 1246 469 1246 468 8243 1338 388 1338 388 469 1246 1339 388 1338 388 468 1246 468 1246 469 1247 1338 388 468 1246 468 1247 468 8243 1338 388 1338 388 468 1246 1338 388 1338 388 468 1246 469 1246 468 1246 1339 387 469 1246 468 1246 468 8218 1363 363 1363 363 493 1222 1363 363 1363 363 493 1222 493 1222 492 1222 1363 363 493 1221 493 1222 493 8217 1363 363 1363 363 493 1222 1363 363 1363 363 493 1246 468 1223 492 1223 1361 375 481 1247 467 1247 467 8243 1337 388 1338 388 468 1247 1337 389 1337 388 468 1247 468 1247 468 1247 1337 389 467 1248 466 1248 466 8243 1337 389 1337 389 467 1248 1336 389 1337 390 466 1248 466 1248 466 1248 1336 390 466 1248 466 1248 466 8244 1336 390 1311 415 465 1249 1336 390 1336 391 465 1250 465 1249 465 1250 1310 415 465 1250 464 1250 439 8270 1310 416 1310 416 440 1275 1310 417 1309 417 438 1300 414 1301 413 1301 1284 442 414 1301 413 1301 413 8297 1284 442 1284 442 413 1301 1284 443 1283 443 413 1302 412 1302 412 1302 1282 443 413 1302 412 1302 412 8298 1282 443 1283 443 413 1302 1283 443 1283 444 412 1303 411 1303 411 1303 1282 444 412 1303 411 1303 411 8299 1280 445 1281 470 385 1329 1255 470 1256 471 384 1330 384 1329 385 1329 1255 471 385 1330 384 1330 384 8326 1253 472 1254 472 384 1331 1253 473 1253 499 356 1357 357 1358 356 1358 1226 499 356 1358 356 1359 355 8355 1224 502 1224 501 355 1385 1199 527 1199 527 328 1386 328 1386 328 1386 1199 527 328 1387 327 1387 327 8409 1171 554 1172 555 300 1414 1172 555 1171 555 300 1416 299 1416 298 1415 1171 556 298 1442 272 1442 272 8438 1143 583 1143 583 271 1471 1115 611 1115 664 179 1536 178 1563 122 1619 1006 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1395 359 1367 358 499 1213 1368 359 1366 359 473 1239 474 1239 474 1238 475 1238 1368 360 497 1215 497 8209 1362 364 1361 366 492 1221 1360 366 1359 366 491 1222 492 1222 491 1221 493 1245 1336 366 492 1222 492 8213 1360 366 1359 389 468 1222 1359 367 1358 390 467 1246 468 1246 468 1245 468 1246 1335 390 467 1246 467 8238 1335 390 1335 390 468 1246 1335 390 1335 390 467 1246 467 1246 468 1246 467 1246 1335 390 467 1246 467 8238 1334 390 1335 390 467 1247 1334 390 1335 391 466 1247 466 1247 466 1247 466 1247 1334 390 467 1247 466 8239 1334 391 1334 391 466 1247 1334 391 1334 391 467 1247 466 1247 466 1247 466 1248 1333 391 466 1248 465 8239 1333 392 1333 392 466 1248 1333 392 1333 392 465 1248 465 1248 465 1248 465 1248 1333 392 465 1248 465 8240 1332 392 1333 392 465 1248 1333 393 1332 393 464 1249 464 1249 464 1249 464 1249 1331 393 465 1249 464 8241 1331 393 1332 393 464 1250 1331 394 1331 394 463 1250 463 1251 462 1250 463 1251 1329 396 462 1251 462 8243 1305 420 1305 444 436 1277 1280 444 1281 445 435 1277 412 1301 436 1277 435 1277 1280 445 436 1277 436 8268 1280 445 1279 445 412 1301 1280 445 1280 445 411 1302 411 1302 411 1302 411 1302 1279 446 411 1302 411 8293 1278 446 1279 446 411 1303 1278 447 1277 447 410 1303 410 1304 409 1329 384 1329 1252 472 385 1329 384 8320 1252 473 1251 473 383 1330 1251 473 1252 473 384 1330 383 1330 383 1330 383 1330 1251 474 382 1330 383 8321 1250 474 1251 475 381 1331 1250 500 1224 500 356 1358 355 1358 355 1358 355 1358 1223 501 355 1358 355 8349 1223 502 1222 528 327 1386 1196 528 1196 530 326 1386 327 1387 326 1386 327 1413 1169 556 299 1414 299 8404 1169 556 1168 584 271 1441 1141 611 1113 637 216 1498 215 1524 178 1562 122 1564 1058 11171 970 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1366 359 1366 360 496 1214 1367 360 1395 331 524 1185 474 1239 474 1238 475 1238 475 1238 1343 383 498 8205 1366 386 1338 386 470 1244 1338 386 1339 386 470 1243 470 1244 469 1244 469 1243 470 1244 1338 386 470 8234 1338 386 1339 386 469 1244 1338 386 1339 386 470 1243 470 1220 493 1219 494 1243 470 1244 1338 362 494 8233 1339 362 1363 386 469 1244 1338 386 1339 386 469 1244 469 1244 469 1244 469 1244 469 1244 1338 387 468 8234 1338 386 1338 387 468 1244 1338 387 1337 387 468 1244 469 1244 468 1245 469 1244 469 1244 1338 387 468 8234 1337 387 1338 387 465 1247 1338 387 1338 387 468 1245 468 1244 444 1269 468 1246 467 1245 1337 387 468 8235 1337 387 1337 388 468 1245 1336 388 1336 388 466 1246 467 1245 468 1245 467 1247 467 1245 1312 412 467 8235 1312 412 1312 412 443 1270 1335 390 1311 412 468 1246 466 1246 467 1246 467 1246 467 1246 1311 412 467 8236 1311 413 1311 413 442 1271 1311 413 1311 413 442 1271 466 1247 466 1246 442 1271 442 1271 1311 413 442 8260 1311 413 1311 413 442 1271 1311 413 1311 414 441 1271 442 1271 465 1248 464 1249 465 1247 1310 414 441 8261 1334 390 1310 414 466 1247 1335 390 1333 391 465 1248 465 1248 465 1248 465 1248 465 1248 1334 390 465 8237 1334 391 1333 390 465 1248 1309 415 1309 416 464 1249 464 1249 463 1250 462 1275 438 1274 1307 394 438 8287 1283 441 1283 441 438 1274 1283 441 1283 442 436 1276 414 1299 438 1275 413 1300 412 1300 1282 442 413 8289 1282 443 1281 443 412 1301 1281 443 1281 443 412 1301 411 1302 411 1302 410 1327 385 1327 1255 469 386 8316 1255 470 1254 470 385 1327 1255 470 1254 470 385 1327 385 1328 384 1328 385 1328 385 1328 1254 470 385 8317 1253 471 1253 470 385 1329 1253 471 1253 471 384 1329 383 1330 382 1330 382 1330 383 1330 1252 473 382 8344 1226 498 1226 498 357 1356 1226 498 1226 498 356 1356 356 1356 356 1356 356 1356 356 1356 1226 499 355 8346 1224 499 1225 525 329 1384 1198 526 1198 525 329 1384 328 1384 328 1384 328 1384 329 1384 1198 526 328 8373 1198 526 1198 527 327 1385 1197 553 1171 553 301 1412 300 1412 300 1412 300 1386 327 1412 1170 554 299 8401 1170 554 1170 555 298 1414 1169 581 1143 582 271 1440 272 1440 272 1440 272 1441 271 1441 1142 609 244 8458 1113 636 1088 663 178 1507 1088 691 1033 -#Timer OFF -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3583 1639 534 386 484 1208 534 386 484 386 485 386 484 387 483 386 484 386 485 386 457 394 476 394 451 419 452 419 476 1237 506 393 477 394 475 395 474 396 473 397 473 398 472 1270 472 399 471 1271 471 1270 471 399 470 401 469 1273 469 402 468 427 443 427 443 427 443 1299 443 427 443 427 444 427 444 427 444 1298 443 1299 442 427 444 427 445 426 445 425 446 425 447 424 447 399 472 398 473 1294 445 426 446 400 471 399 472 1294 446 425 447 398 473 1295 446 400 471 398 472 74544 3552 1672 472 399 471 1271 471 399 471 400 470 400 470 400 470 400 471 400 470 400 470 401 470 401 469 401 469 401 470 1273 470 400 470 401 469 401 469 401 470 401 469 401 470 1273 470 401 469 1273 470 1272 469 401 469 401 469 1274 468 401 469 401 469 401 470 402 469 1273 469 401 469 401 469 402 469 401 469 1273 469 1273 469 401 469 402 468 402 469 426 444 427 444 427 444 426 445 426 444 1274 468 402 469 426 444 427 443 1275 467 403 467 427 443 1275 467 402 468 427 443 -#OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1366 388 1295 387 446 1164 1368 387 1296 387 448 1164 518 1164 493 1189 517 1165 492 1189 493 1189 493 7851 1365 387 1270 388 446 1190 1365 387 1295 387 447 1192 490 1193 489 1193 489 1194 488 1194 488 1193 489 7855 1337 386 1296 386 448 1193 1338 386 1296 386 448 1193 489 1193 489 1193 489 1193 489 1193 489 1193 489 7855 1337 386 1297 386 448 1194 1337 386 1296 386 448 1194 488 1194 488 1194 488 1194 488 1194 488 1194 488 8160 1336 387 1296 387 447 1194 1337 387 1296 386 448 1194 488 1194 488 1194 488 1194 488 1194 488 1194 488 7855 1336 386 1297 387 447 1195 1336 386 1297 386 448 1195 486 1195 487 1195 487 1195 487 1196 486 1196 486 7881 1310 387 1272 397 436 1245 1286 397 1285 397 436 1246 436 1246 436 1247 435 1247 435 1248 434 1248 434 7934 1259 424 1259 424 408 1274 1258 424 1259 424 408 1274 408 1274 408 1274 408 1274 408 1274 408 1274 408 8239 1258 425 1258 424 408 1274 1258 424 1258 425 407 1273 408 1273 409 1273 408 1273 408 1274 408 1273 408 7907 1283 424 1258 424 409 1248 1283 399 1283 400 433 1247 434 1247 434 1247 434 1247 434 1247 434 1247 434 7905 1282 424 1258 425 407 1273 1257 425 1257 425 407 1273 407 1274 407 1274 407 1274 407 1275 406 1275 406 7958 1230 478 1204 478 353 1328 1204 478 1204 453 378 1327 353 1328 353 1302 379 1301 380 1301 380 1300 381 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1341 337 1339 338 496 1178 1395 305 1316 337 1339 338 496 1178 526 1151 526 1151 1366 335 1341 335 498 7888 1338 338 1337 339 495 1182 1336 340 1336 340 1336 340 494 1182 494 1182 494 1183 1336 340 1336 340 494 7892 1336 340 1336 340 494 1183 1336 340 1336 340 1336 340 494 1183 494 1183 494 1183 1335 341 1335 340 494 7893 1335 341 1335 341 493 1183 1336 341 1335 341 1335 341 493 1183 493 1183 494 1183 1335 341 1335 341 493 7893 1335 341 1335 341 493 1184 1334 341 1335 341 1335 341 493 1184 493 1184 493 1184 1335 341 1335 342 492 7894 1334 342 1334 342 492 1184 1334 342 1334 341 1335 342 492 1184 493 1184 492 1185 1334 342 1334 342 492 7894 1334 342 1334 342 492 1185 1333 343 1333 342 1334 342 492 1185 492 1185 492 1185 1333 343 1333 343 491 7895 1333 343 1333 343 491 1185 1333 343 1333 344 1332 344 490 1186 490 1186 491 1186 1333 344 1332 344 490 7897 1331 368 1308 368 466 1211 1308 368 1308 368 1308 368 466 1211 465 1211 466 1211 1307 368 1308 369 465 7921 1307 368 1308 368 466 1211 1308 368 1308 369 1307 368 466 1211 466 1211 465 1211 1308 369 1307 369 465 7921 1307 369 1307 369 465 1212 1307 369 1307 369 1307 369 465 1212 465 1212 465 1212 1307 369 1307 369 465 7922 1306 370 1306 370 464 1212 1307 370 1306 370 1306 370 464 1212 464 1213 464 1212 1306 370 1306 370 464 7922 1306 370 1306 371 463 1213 1306 370 1306 371 1305 371 463 1213 439 1238 439 1238 1305 371 1305 371 462 7924 1280 396 1280 396 438 1238 1281 396 1280 396 1280 396 438 1239 437 1239 438 1239 1280 396 1280 397 437 7949 1279 397 1279 397 437 1240 1279 398 1278 422 1254 422 412 1265 411 1265 412 1265 1254 422 1254 422 412 7974 1254 422 1254 422 412 1265 1254 423 1253 423 1253 449 384 1293 383 1293 383 1294 1225 451 1225 452 382 8056 1171 505 1171 532 188 1463 1169 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1349 364 1321 363 490 1219 1324 364 1294 389 463 1245 1324 363 462 1219 491 1221 462 1223 461 1226 484 8019 1292 393 1292 420 433 1252 1292 393 1292 420 433 1252 1292 393 460 1252 433 1252 460 1226 459 1252 433 8021 1317 393 1292 394 459 1226 1318 393 1292 394 458 1226 1318 394 432 1253 459 1226 459 1253 432 1253 459 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1376 337 1348 363 462 1219 1325 390 1295 388 1322 363 490 1218 466 1219 465 1220 490 1222 1319 369 1316 7187 1292 393 1292 393 460 1252 1292 370 1315 393 1292 420 433 1252 433 1252 460 1252 433 1252 1292 393 1292 7188 1291 393 1318 393 433 1252 1292 393 1319 393 1291 393 460 1226 459 1252 432 1253 459 1225 1319 393 1292 -# -name: Power -type: parsed -protocol: NECext -address: 00 F3 00 00 -command: 91 6E 00 00 -# -name: Timer -type: parsed -protocol: NECext -address: 00 F3 00 00 -command: 96 69 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9253 4427 684 486 656 486 656 486 682 461 681 1573 680 1575 678 464 677 491 651 1604 650 1604 650 1604 650 1604 650 491 651 491 651 1604 650 1604 651 491 651 491 651 1604 650 1604 650 491 651 491 651 491 652 1604 650 1604 651 1604 650 491 651 491 652 1604 650 1604 650 1604 651 491 651 39948 9250 2183 651 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9226 4450 657 484 657 484 658 484 657 485 657 1596 658 1597 681 487 653 488 653 1602 652 1602 652 1602 652 1602 652 490 652 490 652 1602 652 1602 652 490 652 490 652 490 652 1603 652 490 652 490 652 490 652 1602 652 1602 652 1602 653 1602 652 490 652 1602 652 1602 652 1602 653 489 653 39949 9250 2179 653 -#OSC -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9231 4449 657 484 658 483 659 483 659 483 659 1595 659 1595 659 485 681 461 680 1601 652 1602 653 1601 653 1601 653 488 654 488 654 1602 653 1601 653 488 654 488 654 1602 653 1602 652 1602 652 488 654 488 654 1602 653 1601 653 1602 652 488 654 488 654 488 654 1602 652 1602 653 488 654 39978 9229 2174 654 96468 9259 2146 679 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9384 4452 658 485 657 484 658 484 659 485 657 1597 658 1597 682 487 655 488 654 1603 652 1603 653 1603 653 1604 653 491 653 491 653 1603 653 1603 653 1603 653 491 653 1604 652 490 654 1603 652 490 653 490 653 1603 652 490 653 1603 652 490 653 1603 652 490 653 1603 652 1603 652 490 653 39953 9263 2181 652 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 83 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 87 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 8B 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2256 695 788 1354 789 1349 789 1340 792 702 762 697 763 692 789 661 788 720 786 693 786 689 785 685 784 681 784 676 784 1334 784 1330 783 102265 2255 695 787 1356 786 1352 785 1348 785 681 783 676 783 671 784 666 784 724 784 696 783 691 784 686 784 681 783 676 784 1335 783 1330 783 -#OSC -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2227 749 733 1382 761 1379 866 1292 841 566 898 591 868 588 866 582 760 1412 867 612 866 576 898 603 867 598 866 1256 759 695 867 1246 759 101611 2335 615 868 1245 899 1268 869 1266 867 566 898 591 760 694 761 689 760 1411 760 720 760 715 759 710 759 705 759 1363 760 696 758 1352 761 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2224 708 755 1419 757 1443 756 685 756 1470 757 709 758 710 784 657 836 708 756 763 785 1468 782 1444 755 737 756 712 754 1471 781 1419 780 101298 2250 656 778 1420 809 1391 753 686 755 1472 779 687 779 687 752 688 831 714 778 741 750 1502 776 1422 752 740 752 741 751 1448 751 1475 749 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2203 675 786 1388 867 1361 786 678 786 1444 758 708 759 707 760 707 786 735 757 760 786 735 757 736 781 711 757 734 759 733 785 734 758 101185 2198 708 757 1416 757 1442 756 685 755 1445 780 685 780 662 776 689 803 742 778 740 753 766 779 715 777 714 779 714 778 690 776 742 752 -#StrengthUp -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2224 685 782 1419 808 1418 779 687 757 1470 757 710 757 684 809 657 809 736 758 1469 758 736 756 1470 757 762 755 1446 782 1418 781 712 755 101352 2223 707 758 1417 834 1392 781 685 781 1446 780 687 754 687 754 713 804 741 779 1447 779 715 778 1447 779 740 752 1448 778 1422 779 690 775 -#StrengthDown -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2222 685 781 1419 808 1419 780 686 755 1447 781 684 783 686 780 686 807 1470 757 1470 756 1471 756 1497 756 1445 780 1447 780 1421 779 1421 754 101509 2250 684 777 1421 781 1444 754 687 754 1472 779 687 779 688 753 688 830 1448 777 1449 778 1448 752 1501 752 1450 773 1451 778 1422 778 1423 777 -# -name: Power -type: parsed -protocol: NECext -address: 41 59 00 00 -command: 05 FA 00 00 -# -name: Speed_up -type: parsed -protocol: NECext -address: 41 59 00 00 -command: 44 BB 00 00 -#OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1332 391 1303 359 485 1241 1304 391 1303 391 452 1242 452 1242 452 1243 451 1243 452 1243 452 1242 1303 7275 1303 391 1303 390 453 1240 1330 364 1328 366 477 1218 476 1218 476 1220 474 1220 474 1220 474 1220 1325 7252 1326 369 1325 369 475 1219 1325 369 1325 369 475 1219 475 1219 475 1219 475 1219 475 1219 475 1219 1326 7254 1326 369 1326 369 475 1219 1326 369 1326 369 475 1219 475 1220 475 1219 475 1219 475 1220 475 1220 1325 7256 1325 370 1325 370 474 1220 1325 370 1325 370 474 1220 475 1220 474 1220 474 1220 474 1221 474 1221 1324 7281 1301 370 1325 370 474 1220 1325 370 1324 370 474 1221 474 1221 473 1221 473 1221 473 1221 474 1220 1325 7256 1325 370 1324 371 473 1221 1324 370 1324 370 474 1221 473 1221 473 1220 474 1220 474 1220 474 1220 1324 7256 1324 371 1323 371 473 1221 1323 371 1323 371 473 1221 473 1221 473 1221 473 1221 473 1221 473 1221 1323 7256 1323 371 1323 371 473 1222 1322 372 1322 372 472 1222 472 1222 472 1222 472 1222 472 1222 472 1222 1322 7281 1297 372 1322 373 471 1246 1298 396 1298 397 447 1247 447 1247 447 1247 447 1247 447 1247 447 1247 1297 7281 1297 397 1297 397 447 1247 1297 397 1297 397 447 1247 447 1247 447 1247 447 1247 447 1248 446 1248 1296 7282 1296 398 1296 398 446 1248 1296 398 1296 398 447 1248 446 1248 446 1248 446 1247 447 1247 447 1248 1295 7281 1296 398 1296 398 446 1248 1296 399 1295 399 446 1248 446 1248 446 1248 446 1248 446 1248 446 1248 1296 7282 1296 399 1295 399 445 1248 1295 399 1295 399 445 1248 446 1249 444 1249 445 1249 445 1249 445 1249 1294 7282 1294 399 1295 400 444 1249 1295 399 1294 400 444 1250 444 1249 445 1250 444 1249 445 1250 444 1250 1294 7282 1294 400 1294 400 444 1250 1294 400 1294 401 443 1251 443 1250 444 1250 444 1250 444 1250 443 1251 1292 7284 1292 401 1293 402 442 1251 1292 402 1291 403 442 1276 418 1252 442 1276 418 1277 417 1277 417 1277 1242 7334 1268 427 1242 452 392 1302 1242 452 1242 452 392 1302 392 1302 392 1302 392 1302 391 1302 392 1302 1242 7335 1241 453 1240 453 391 1302 1241 453 1240 453 391 1303 390 1303 390 1303 390 1304 390 1303 391 1304 1240 7361 1216 479 1216 479 364 1330 1215 479 1215 480 363 1330 364 1330 364 1330 364 1330 364 1330 364 1330 1214 7362 1213 480 1214 480 363 1331 1213 481 1213 482 361 1332 362 1332 362 1331 363 1331 362 1332 362 1332 1212 7391 1185 508 1185 509 334 1359 1185 509 1184 509 334 1359 335 1359 334 1386 307 1386 307 1386 307 1386 1159 7445 1131 562 1131 616 196 1471 1104 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2265 691 792 690 792 684 793 1342 821 1310 821 640 822 634 822 660 792 718 791 690 791 685 791 680 791 675 791 670 790 1330 789 662 788 99517 2230 723 760 721 760 715 761 1374 761 1368 762 699 762 694 762 689 762 747 761 719 761 715 761 710 761 705 761 701 760 1360 760 691 760 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2312 617 867 641 840 577 899 1269 866 1265 865 622 839 593 864 612 838 670 839 1306 839 611 865 1271 864 627 839 1259 865 590 866 611 840 102129 2316 670 813 667 813 664 811 1323 810 1319 759 702 759 697 759 692 759 750 759 1386 758 717 759 1376 758 706 759 1365 759 696 759 691 760 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2232 725 759 722 759 717 759 1375 760 1370 760 702 759 697 759 692 788 1384 788 1356 788 1351 788 1347 787 1342 787 1338 786 670 785 1330 784 99591 2229 724 760 721 785 690 785 1349 784 1345 784 677 783 673 782 669 781 1391 781 1363 781 1359 780 1353 781 1349 780 1343 781 675 780 1334 780 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2230 724 760 720 761 715 761 1374 761 1370 760 701 760 696 760 691 786 1385 760 720 787 689 760 710 760 705 786 1339 785 670 785 665 784 98757 2224 729 754 726 755 721 754 1380 754 1375 754 706 754 701 754 696 754 1418 754 726 754 720 755 716 755 710 755 1369 755 700 755 696 755 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2256 698 786 695 786 689 787 1348 787 1342 787 673 788 668 787 663 787 722 786 696 784 1353 786 1375 759 706 759 702 783 672 783 1332 782 102265 2310 645 838 668 812 664 811 1323 810 1319 810 651 809 647 808 642 808 701 807 673 807 1332 807 1327 807 658 808 653 807 648 807 1307 807 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1356 337 1423 337 516 1157 1298 409 1296 416 448 1274 451 1276 452 1274 450 1274 449 1277 449 1275 1301 7091 1301 407 1300 405 450 1267 1302 407 1301 414 450 1274 451 1275 451 1275 451 1276 450 1275 451 1274 1302 7074 1303 406 1302 406 450 1268 1301 409 1299 416 449 1277 472 1254 473 1253 473 1254 472 1254 473 1252 1323 7069 1325 384 1300 408 470 1247 1324 387 1299 417 471 1253 472 1254 472 1254 472 1254 472 1254 472 1252 1324 7052 1324 384 1300 408 472 1246 1324 387 1299 417 472 1253 472 1254 472 1254 472 1254 473 1254 472 1252 1324 7064 1325 385 1323 385 471 1246 1324 387 1323 394 471 1254 471 1254 472 1254 472 1254 472 1254 472 1252 1324 7053 1324 385 1323 385 471 1247 1323 387 1322 394 471 1254 472 1254 472 1255 471 1255 471 1255 472 1253 1323 7070 1323 385 1323 385 471 1247 1323 388 1322 395 470 1254 472 1255 471 1255 471 1255 471 1255 471 1253 1323 7054 1323 386 1322 386 470 1248 1322 413 1297 396 470 1255 470 1256 470 1256 470 1256 470 1256 471 1254 1322 7071 1322 411 1297 387 469 1249 1321 414 1296 396 470 1280 445 1281 446 1281 445 1282 445 1282 445 1280 1296 7076 1296 412 1296 412 444 1274 1297 413 1297 421 445 1281 445 1282 444 1282 444 1282 445 1282 444 1280 1296 7098 1295 412 1296 412 445 1275 1296 414 1296 421 445 1282 444 1282 445 1282 444 1283 444 1282 445 1280 1296 7082 1296 412 1296 412 445 1275 1295 414 1296 421 444 1282 444 1282 444 1283 444 1283 444 1282 445 1280 1296 7098 1296 413 1295 413 444 1275 1295 415 1295 422 444 1283 443 1282 444 1283 443 1282 445 1282 444 1281 1295 7082 1295 413 1295 413 444 1275 1295 415 1294 423 443 1283 443 1283 443 1283 443 1283 443 1283 443 1281 1294 7093 1295 413 1295 414 443 1276 1294 415 1295 423 443 1283 443 1283 443 1283 443 1283 444 1283 444 1280 1295 7082 1295 414 1294 414 443 1276 1294 416 1294 423 443 1283 443 1283 443 1283 443 1283 444 1283 444 1281 1294 7098 1295 414 1294 414 443 1276 1294 416 1294 424 442 1284 443 1283 443 1283 443 1284 443 1283 443 1281 1294 7083 1294 414 1294 415 442 1277 1293 417 1293 425 441 1284 442 1284 442 1284 442 1284 442 1284 443 1282 1293 7099 1294 415 1293 416 441 1278 1292 418 1292 425 441 1285 441 1285 441 1285 442 1285 441 1285 441 1283 1292 7083 1293 417 1291 441 415 1279 1292 443 1267 451 415 1286 440 1286 441 1286 440 1285 441 1286 440 1284 1291 7096 1292 441 1267 442 415 1304 1266 444 1266 451 415 1311 415 1287 439 1287 440 1311 415 1286 440 1285 1290 7085 1291 442 1241 467 414 1304 1266 445 1241 476 414 1313 414 1312 415 1311 415 1312 415 1312 414 1310 1241 7151 1242 467 1241 467 415 1305 1241 469 1241 477 412 1314 414 1312 414 1312 414 1312 414 1312 414 1310 1241 7135 1241 467 1241 468 389 1330 1241 470 1240 477 389 1337 389 1337 414 1312 414 1313 413 1313 389 1335 1241 7151 1241 469 1239 470 386 1331 1240 496 1214 503 362 1338 388 1338 388 1338 388 1338 388 1338 388 1336 1240 7130 1241 494 1214 495 361 1357 1214 497 1213 504 362 1364 362 1364 362 1364 363 1364 362 1364 362 1362 1214 7153 1239 495 1213 496 360 1358 1213 498 1212 531 334 1365 362 1365 361 1365 361 1365 362 1364 362 1363 1213 7162 1213 496 1212 522 334 1359 1212 524 1186 532 333 1367 359 1366 360 1366 361 1365 361 1365 361 1364 1211 7180 1212 523 1185 550 306 1387 1185 552 1157 586 278 1395 332 1394 332 1394 332 1393 333 1393 334 1391 1184 7190 1185 576 1131 604 250 1415 1158 660 1049 2341 251 1448 278 1422 305 1448 278 1448 278 1447 1130 7229 1157 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1357 353 1327 382 448 1239 1333 409 1301 416 477 1246 452 1274 452 1274 453 1273 479 1246 1330 416 448 7967 1300 409 1299 410 446 1273 1299 412 1298 419 446 1280 446 1281 446 1281 446 1281 446 1279 1298 419 446 7958 1298 411 1297 410 446 1273 1298 412 1298 419 446 1280 446 1281 445 1281 446 1281 445 1279 1298 419 446 7967 1298 410 1298 411 445 1274 1297 413 1297 420 445 1281 445 1281 445 1281 445 1282 445 1279 1298 420 445 7957 1297 411 1297 411 445 1274 1297 413 1297 420 445 1281 445 1281 445 1282 444 1282 444 1280 1296 420 445 7962 1297 411 1297 411 445 1274 1297 413 1297 420 445 1282 444 1282 444 1282 444 1282 444 1280 1297 421 444 7957 1296 412 1297 411 444 1275 1296 414 1296 421 444 1282 444 1283 443 1283 443 1282 444 1281 1296 421 444 7968 1296 412 1296 412 444 1275 1296 414 1296 421 444 1283 443 1283 443 1283 443 1283 443 1281 1296 422 443 7958 1295 413 1295 413 443 1276 1295 415 1295 422 443 1283 443 1284 442 1284 442 1284 442 1282 1294 422 443 7970 1293 414 1294 414 442 1277 1294 416 1294 423 442 1284 442 1309 417 1309 417 1309 417 1307 1269 424 441 7978 1270 416 1292 414 442 1302 1269 440 1270 424 441 1309 417 1309 417 1310 417 1309 417 1307 1270 448 417 7994 1269 439 1269 439 416 1302 1269 441 1269 448 416 1310 416 1310 416 1310 416 1310 416 1308 1269 448 416 7984 1269 439 1268 440 416 1303 1268 442 1267 449 415 1310 416 1310 416 1310 416 1310 416 1309 1268 449 416 7994 1268 440 1268 440 415 1303 1268 442 1268 449 415 1311 415 1311 415 1311 415 1311 415 1309 1267 450 415 7985 1267 441 1267 441 414 1304 1266 444 1267 450 414 1311 415 1312 413 1312 414 1312 414 1310 1265 452 414 7991 1266 442 1242 466 413 1306 1241 468 1242 475 414 1337 388 1338 388 1338 388 1338 388 1336 1217 501 386 8013 1215 492 1216 492 387 1331 1216 494 1216 501 363 1363 362 1363 363 1363 363 1363 387 1338 1215 502 362 8047 1215 493 1215 492 363 1356 1215 495 1215 502 362 1364 361 1364 362 1364 362 1364 362 1363 1214 503 361 8063 1189 494 1214 494 361 1382 1189 496 1214 503 361 1390 335 1391 335 1391 335 1391 335 1389 1188 529 335 8074 1188 520 1188 520 334 1385 1187 522 1187 529 334 1392 333 1417 308 1418 308 1419 307 1417 1161 556 307 8092 1160 547 1161 547 307 1412 1160 550 1160 557 306 1419 306 1446 279 1421 305 1446 279 1445 1133 584 279 8125 1133 575 1133 576 277 1466 1106 603 1107 611 251 1474 251 1501 224 1502 224 1528 186 1511 1079 638 224 8228 1025 735 972 2640 786 -# -name: Speed_up -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 1A 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 1D 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 18 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 0D 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 80 DE 00 00 -command: 00 FF 00 00 -# -name: Speed_up -type: parsed -protocol: NECext -address: 80 DE 00 00 -command: 08 F7 00 00 -# -name: Speed_dn -type: parsed -protocol: NECext -address: 80 DE 00 00 -command: 10 EF 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4640 4393 562 1442 562 1443 562 1443 537 1470 535 1468 562 1444 562 2398 562 1444 562 1446 560 2424 535 1470 535 2425 534 1472 533 1473 533 2427 533 1474 531 1473 4577 4456 531 1473 531 1474 532 1474 531 1474 532 1474 531 1474 531 2429 531 1474 531 1474 531 2429 531 1474 531 2429 531 1474 531 1474 531 2429 531 1474 531 14007 9125 2259 530 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4609 4425 563 1441 563 1442 563 1442 538 1469 537 1469 561 1444 562 2400 560 1470 535 1470 536 1470 536 2424 536 1470 535 1471 534 1472 533 2427 533 2427 533 1472 4580 4454 531 1472 533 1474 532 1474 531 1474 532 1474 532 1474 532 2428 532 1474 532 1474 532 1474 532 2428 532 1474 532 1474 532 1474 532 2428 532 2429 532 14008 9127 2258 528 50213 9131 2253 532 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4607 4424 563 1441 563 1443 563 1444 536 1469 537 1469 537 1496 534 2399 561 1470 535 2424 536 1470 536 1470 535 2424 535 1471 534 1472 534 1472 533 2427 533 1472 4579 4455 531 1472 532 1474 532 1474 532 1474 532 1474 532 1474 532 2429 531 1474 532 2428 532 1474 532 1474 532 2429 532 1474 532 1474 532 1474 532 2428 532 14007 9125 2258 530 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4604 4427 561 1442 562 1443 561 1444 537 1469 536 1469 562 1445 561 2399 560 1446 559 2424 534 1472 533 2426 533 1473 532 2428 531 1475 531 1474 531 1474 531 1473 4575 4457 530 1473 531 1475 531 1475 530 1475 531 1475 531 1475 530 2429 531 1475 530 2429 530 1475 530 2430 530 1475 530 2429 531 1475 531 1475 531 1475 530 14008 9122 2260 530 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4582 4424 562 1442 562 1471 534 1471 509 1496 510 1496 534 1472 534 2424 536 1470 536 1470 536 1470 536 1470 535 2424 535 2425 534 2426 534 1472 533 1473 533 1472 4581 4453 532 1472 532 1473 533 1473 533 1473 533 1473 533 1473 533 2428 532 1473 532 1473 532 1474 532 1474 532 2428 533 2428 532 2428 532 1474 532 1474 532 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1369 311 1327 312 498 1162 1286 355 1285 362 457 1221 458 1221 458 1221 458 1222 457 1221 458 1219 1312 6796 1310 330 1282 356 454 1219 1281 360 1280 367 452 1227 452 1227 452 1227 452 1227 452 1227 452 1225 1280 6815 1280 359 1279 359 451 1220 1280 361 1279 368 451 1228 451 1228 451 1228 451 1228 451 1228 451 1226 1279 6827 1279 382 1256 382 428 1245 1255 384 1256 391 427 1251 428 1252 426 1252 427 1252 427 1252 427 1250 1255 6838 1255 383 1255 383 426 1245 1255 385 1255 392 426 1252 427 1252 426 1252 426 1252 427 1252 426 1250 1255 6849 1255 383 1255 383 426 1245 1255 385 1254 392 426 1252 426 1252 426 1252 426 1252 426 1252 426 1250 1254 6835 1254 383 1254 383 426 1245 1254 385 1254 392 426 1252 426 1252 426 1252 426 1252 426 1252 426 1250 1254 6852 1254 383 1254 384 425 1245 1254 386 1253 392 426 1253 425 1252 426 1252 426 1253 425 1253 425 1251 1253 6835 1253 384 1253 384 425 1245 1253 386 1253 393 425 1252 425 1253 425 1253 425 1253 425 1253 425 1251 1253 6852 1252 384 1253 384 425 1246 1252 386 1253 393 425 1253 424 1253 425 1253 425 1254 424 1253 425 1252 1252 6835 1253 385 1252 385 424 1247 1252 387 1252 394 424 1254 424 1254 424 1254 424 1254 424 1254 424 1252 1251 6850 1251 386 1251 386 423 1248 1251 388 1250 395 423 1255 422 1256 422 1279 399 1280 398 1279 399 1277 1227 6862 1226 411 1226 411 398 1273 1226 413 1226 420 397 1280 398 1279 398 1280 397 1280 398 1280 398 1278 1226 6892 1226 411 1225 411 397 1273 1225 413 1225 420 397 1280 397 1280 398 1280 397 1280 397 1280 397 1279 1224 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1308 332 1305 332 479 1190 1309 333 1307 364 455 1199 479 1198 481 1198 480 1198 480 1197 1331 339 480 7657 1306 332 1305 332 479 1194 1304 335 1305 342 478 1201 477 1201 478 1201 477 1201 478 1199 1305 342 478 7647 1305 333 1304 333 478 1194 1304 335 1304 342 477 1201 477 1201 478 1201 477 1201 477 1199 1305 342 478 7660 1303 334 1304 333 478 1194 1304 336 1303 343 477 1202 477 1202 476 1202 477 1202 476 1200 1304 343 477 7647 1303 334 1304 334 476 1195 1303 336 1303 343 476 1202 476 1202 476 1202 476 1202 476 1200 1303 343 476 7659 1302 335 1302 335 476 1196 1302 337 1302 344 475 1203 475 1203 475 1203 475 1203 475 1201 1302 344 476 7646 1302 335 1300 338 474 1197 1300 338 1301 345 474 1204 474 1204 474 1204 449 1229 449 1227 1275 370 449 7690 1275 362 1275 361 449 1222 1275 364 1275 371 448 1230 448 1230 448 1231 447 1232 446 1229 1274 372 447 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1338 312 1380 310 446 1133 1313 356 1284 362 457 1221 457 1219 1285 363 456 1221 457 1222 482 1196 482 7655 1282 356 1281 357 453 1219 1279 360 1279 367 452 1226 452 1224 1280 367 452 1226 452 1226 452 1226 452 7671 1279 358 1279 358 452 1219 1279 360 1279 367 452 1227 451 1225 1279 367 451 1227 451 1227 451 1227 451 7682 1279 359 1278 359 451 1220 1278 361 1278 368 451 1227 450 1225 1278 368 451 1227 451 1228 450 1228 450 7691 1277 360 1277 360 450 1221 1277 362 1277 368 450 1228 449 1226 1277 369 449 1229 449 1252 426 1252 426 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1338 311 1326 312 497 1134 1364 311 1276 361 457 1221 457 1220 457 1218 1283 361 456 1221 456 1221 457 7676 1285 351 1285 351 457 1213 1286 353 1285 360 457 1220 457 1220 457 1219 1285 361 456 1221 456 1222 481 7639 1283 353 1284 353 455 1216 1283 356 1282 363 454 1223 455 1223 454 1221 1283 363 454 1223 454 1223 454 7676 1282 354 1282 354 454 1217 1282 356 1282 364 453 1223 454 1223 454 1221 1282 363 454 1224 453 1224 454 7681 1281 355 1281 355 453 1217 1281 357 1281 364 453 1225 452 1225 452 1222 1281 365 452 1225 452 1225 452 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3565 3379 984 2506 984 2505 985 797 900 2590 900 2590 926 2572 926 826 925 829 922 858 894 859 894 858 895 2604 894 859 894 859 894 2596 894 859 894 859 894 867 894 2596 894 2596 894 2596 894 2596 894 2596 894 867 894 40343 3530 3468 895 2596 894 2596 894 859 894 2596 894 2596 894 2604 894 859 894 859 894 859 894 859 894 859 894 2604 894 859 894 859 894 2596 894 859 894 859 894 867 894 2596 894 2596 894 2596 894 2596 895 2596 894 866 895 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3595 3377 930 2561 929 2561 929 853 900 2591 899 2590 900 2597 901 2590 925 2565 924 2567 922 2570 920 2596 894 866 895 858 895 858 895 2596 894 858 895 859 894 866 895 858 895 858 895 858 895 859 894 858 895 2604 894 40343 3533 3467 895 2595 895 2596 894 858 895 2596 894 2596 894 2604 895 2596 894 2596 894 2596 894 2596 894 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 859 894 859 894 859 894 859 894 2605 893 40319 3533 3442 920 2595 895 2596 894 858 895 2596 894 2596 894 2604 894 2596 894 2596 894 2596 894 2596 894 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 859 894 859 894 859 894 859 894 2604 894 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3594 3380 981 2505 930 2560 930 853 900 2590 900 2590 900 2598 900 2589 900 853 924 2568 921 2571 919 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 2596 894 859 894 859 894 859 894 2604 894 40335 3532 3467 895 2596 894 2596 895 859 894 2596 894 2596 894 2605 894 2596 894 859 894 2596 894 2597 894 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 2597 894 859 894 859 894 859 894 2605 893 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3534 3439 954 2505 985 2535 955 798 954 2535 899 2591 899 2599 899 853 925 2566 924 2567 922 2596 894 2596 894 867 894 859 894 860 893 2597 894 859 894 859 894 867 894 2597 893 859 894 859 894 860 893 859 894 2605 894 40336 3531 3469 894 2597 893 2597 893 859 894 2597 893 2597 893 2605 893 860 893 2597 893 2597 893 2597 894 2597 893 868 893 860 893 860 893 2597 893 860 893 860 893 868 893 2597 893 860 893 860 893 860 893 860 893 2605 893 -# -name: Power -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 1A 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1370 314 1375 320 519 1167 1370 322 1339 350 465 1221 467 1222 467 1222 466 1221 467 1221 467 1221 1317 7067 1317 372 1341 349 491 1198 1340 352 1337 353 486 1202 486 1226 462 1226 462 1227 461 1227 462 1227 1311 7073 1312 378 1311 378 462 1227 1311 378 1312 378 462 1227 462 1227 461 1227 462 1227 461 1227 461 1227 1311 7074 1311 378 1311 378 462 1227 1311 379 1310 379 461 1228 461 1228 460 1228 460 1228 460 1229 459 1228 1310 7076 1309 381 1309 380 460 1229 1310 381 1309 381 458 1230 458 1230 459 1230 459 1230 459 1230 458 1230 1309 7077 1309 380 1310 380 460 1229 1310 380 1310 380 459 1229 460 1229 459 1229 460 1229 459 1229 459 1229 1310 7075 1310 379 1310 380 459 1229 1310 380 1310 379 460 1229 460 1229 459 1229 460 1228 460 1229 459 1229 1310 7074 1310 380 1310 379 460 1229 1310 379 1310 379 460 1229 459 1229 459 1229 460 1229 460 1229 460 1228 1311 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1394 321 1369 321 520 1141 1397 321 1368 321 519 1142 495 1194 495 1194 494 1195 494 1195 1366 351 491 7891 1341 350 1337 353 487 1201 1337 354 1336 354 487 1202 487 1202 487 1202 487 1202 487 1202 1336 354 487 7900 1336 354 1337 354 486 1202 1337 354 1336 354 487 1202 487 1202 487 1203 486 1202 487 1202 1336 354 487 7901 1335 354 1336 354 487 1203 1335 354 1336 355 486 1203 486 1203 486 1203 486 1203 486 1203 1335 355 486 7900 1335 355 1335 355 486 1203 1335 355 1335 355 486 1203 486 1204 485 1204 485 1204 485 1203 1335 356 485 7900 1334 356 1334 356 485 1204 1334 356 1334 356 485 1204 485 1204 484 1204 485 1204 485 1204 1333 357 484 7901 1333 357 1333 380 460 1228 1310 380 1310 380 460 1228 460 1228 460 1228 460 1228 460 1228 1310 380 460 7924 1309 380 1310 380 460 1229 1309 380 1310 380 460 1229 459 1229 460 1229 459 1230 459 1230 1309 381 458 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1343 372 1318 372 467 1194 1346 372 1318 372 467 1193 525 1164 1400 319 495 1166 523 1166 523 1167 521 7890 1344 347 1340 350 489 1200 1339 351 1339 351 488 1201 488 1201 1338 351 488 1201 488 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1339 352 1338 352 488 1201 488 1201 1339 352 487 1202 487 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1338 352 1338 352 487 1202 487 1202 1337 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1202 1338 353 1337 353 486 1202 487 1202 1338 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1203 1336 354 1336 354 485 1203 486 1204 1335 354 485 1203 485 1203 486 1203 486 7901 1336 378 1312 355 484 1205 1335 378 1312 378 461 1227 462 1227 1313 378 461 1228 461 1228 461 1228 461 7925 1312 378 1312 378 461 1228 1311 378 1312 378 461 1228 461 1228 1312 378 461 1228 461 1228 461 1228 461 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1370 319 1371 321 519 1167 1371 322 1366 324 488 1198 466 1222 466 1222 1317 375 464 1221 467 1220 468 7915 1317 372 1341 350 490 1197 1340 376 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1312 377 1312 377 462 1226 462 1226 462 1226 1312 378 462 1226 462 1226 462 7922 1312 377 1313 377 462 1226 1313 377 1312 377 462 1226 462 1226 462 1226 1313 377 462 1226 462 1226 462 7921 1312 377 1312 377 462 1226 1312 377 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1313 377 1312 354 485 1202 486 1202 486 1202 1337 352 487 1202 487 1201 487 7897 1337 352 1337 351 488 1200 1339 351 1339 352 487 1201 487 1201 488 1201 1338 352 487 1201 487 1201 488 7896 1338 352 1337 352 487 1201 1337 352 1337 352 487 1201 487 1201 487 1201 1338 352 487 1201 487 1201 487 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1369 349 1370 319 521 1167 1371 321 1340 349 466 1221 467 1222 467 1221 468 1221 1318 373 466 1220 468 7917 1344 347 1341 349 489 1198 1340 351 1338 352 487 1202 486 1202 486 1202 486 1202 1337 353 486 1202 486 7922 1312 377 1313 377 462 1227 1312 377 1313 378 461 1227 461 1227 462 1227 462 1227 1312 377 462 1227 461 7923 1312 378 1311 378 461 1227 1312 378 1311 377 462 1227 461 1227 461 1227 461 1227 1312 377 462 1227 462 7922 1312 378 1312 377 462 1227 1312 354 1336 354 485 1227 461 1227 461 1227 461 1203 1336 353 486 1203 486 7922 1312 353 1336 354 485 1203 1336 354 1336 354 485 1203 485 1227 461 1227 461 1227 1312 378 461 1227 461 7923 1312 378 1312 378 461 1227 1312 378 1312 378 461 1228 461 1228 461 1228 460 1228 1311 378 461 1227 461 7923 1311 378 1311 378 461 1227 1312 378 1312 378 461 1227 462 1227 461 1227 461 1227 1312 378 461 1228 461 6641 1312 378 1312 355 484 1228 1311 355 1335 378 461 1228 461 1228 460 1228 460 1228 1311 378 461 1228 460 7924 1311 379 1310 379 460 1228 1311 379 1311 379 460 1229 459 1229 459 1229 460 1229 1310 380 459 1229 459 7925 1310 380 1309 381 458 1230 1309 381 1309 381 458 1230 458 1231 457 1231 458 1231 1308 381 458 1231 457 7952 1283 407 1283 407 432 1256 1283 407 1283 407 432 1257 431 1257 432 1257 431 1257 1283 408 431 1257 431 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1372 307 1369 310 524 1149 1370 310 1366 334 500 1151 526 1152 1367 335 499 1155 522 1156 521 1179 498 7895 1339 338 1339 338 496 1181 1339 339 1338 339 496 1182 496 1182 1339 339 496 1182 496 1182 496 1182 496 7897 1338 339 1338 339 496 1182 1339 339 1338 339 496 1182 496 1182 1338 339 496 1182 496 1183 495 1183 495 7897 1338 339 1338 339 496 1182 1339 339 1338 340 495 1183 495 1183 1337 340 495 1183 495 1183 495 1183 495 7898 1337 340 1337 340 495 1183 1337 340 1337 340 495 1183 495 1183 1337 340 495 1183 495 1183 495 1183 495 7898 1337 340 1338 340 495 1183 1337 340 1337 340 494 1183 495 1184 1336 340 495 1184 494 1184 494 1183 495 7898 1337 340 1337 341 494 1183 1338 340 1337 340 495 1183 495 1183 1337 340 494 1184 494 1184 494 1183 495 7898 1337 340 1337 340 494 1184 1337 341 1336 341 494 1184 494 1184 1337 340 494 1184 494 1184 494 1184 494 7898 1337 341 1336 341 494 1184 1336 341 1336 341 494 1184 494 1184 1336 341 494 1184 494 1184 494 1184 494 7899 1336 341 1336 341 494 1184 1336 341 1336 341 494 1184 494 1184 1336 341 494 1184 494 1184 494 1184 494 7899 1335 341 1337 341 494 1184 1336 341 1336 342 493 1185 493 1185 1335 342 493 1185 493 1185 493 1185 493 7899 1336 342 1335 342 493 1185 1335 342 1335 342 493 1185 493 1185 1335 342 493 1185 493 1185 493 1185 493 7899 1336 342 1335 342 493 1185 1335 342 1335 342 493 1185 493 1185 1335 342 493 1185 493 1185 493 1185 493 7900 1335 342 1335 342 493 1185 1335 342 1335 342 493 1185 493 1185 1335 342 493 1186 492 1186 492 1185 492 7900 1335 342 1335 343 492 1186 1334 343 1334 343 492 1186 492 1186 1334 343 492 1186 492 1186 492 1186 492 7901 1334 343 1334 344 491 1186 1334 344 1333 344 491 1187 491 1187 1333 344 491 1186 491 1187 491 1187 491 7901 1334 344 1333 368 467 1211 1309 368 1309 368 466 1211 467 1211 1309 368 467 1211 467 1211 467 1211 467 7926 1309 368 1309 369 466 1212 1308 369 1308 369 465 1212 466 1212 1308 369 465 1212 466 1212 466 1212 466 7927 1308 369 1308 369 466 1212 1308 370 1307 370 464 1213 465 1213 1307 370 464 1213 465 1213 465 1213 465 7927 1307 370 1307 370 464 1213 1308 370 1307 371 464 1214 464 1214 1306 371 464 1214 464 1214 464 1214 463 7928 1306 371 1306 371 463 1215 1305 372 1305 372 463 1215 463 1214 1306 372 463 1214 463 1215 463 1215 463 7929 1305 396 1281 397 437 1240 1280 397 1280 397 437 1241 437 1217 1303 397 437 1240 438 1240 438 1240 438 7954 1280 397 1255 422 412 1266 1255 423 1254 422 412 1266 412 1266 1255 423 411 1266 412 1266 412 1266 412 7980 1255 422 1255 423 411 1266 1255 423 1254 423 411 1267 411 1266 1255 423 411 1266 412 1267 410 1267 411 7980 1254 424 1253 424 410 1267 1254 424 1253 425 409 1267 410 1268 1253 450 384 1268 410 1268 410 1268 410 7982 1252 450 1227 450 384 1294 1227 450 1227 450 384 1294 384 1294 1227 451 383 1294 384 1295 383 1294 383 8008 1227 451 1226 451 383 1295 1226 452 1225 452 382 1296 382 1296 1225 478 355 1321 356 1296 382 1296 381 8010 1225 478 1199 478 355 1322 1200 479 1198 505 192 1459 355 1323 1199 505 145 1506 354 1324 353 1324 275 8116 1191 3869 795 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9230 4449 644 522 617 523 617 524 615 526 613 529 611 530 610 530 610 531 609 1640 610 1640 610 1640 609 1640 609 1640 610 1640 609 1640 609 1640 609 531 609 531 609 1640 609 531 609 531 609 531 609 1640 609 531 609 1640 609 1641 608 532 608 1641 608 1641 608 1641 608 532 608 1641 608 40020 9177 2212 611 -# -name: Timer -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 15 00 00 00 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9215 4421 664 475 664 500 638 500 638 501 662 476 662 477 661 478 661 1588 659 1589 658 1614 633 1615 632 1615 606 1640 606 1641 629 1618 630 508 631 1616 631 1616 632 1616 632 507 632 507 633 482 657 481 658 481 658 480 659 480 659 480 659 1589 658 1589 658 1589 658 1589 658 1589 658 39821 9206 2163 659 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1290 398 1290 397 446 1243 1290 397 1291 397 446 1242 446 1242 446 1243 445 1244 444 1243 445 1243 1262 7180 1262 425 1263 426 445 1243 1262 426 1262 425 446 1243 445 1244 444 1243 445 1242 446 1242 446 1242 1264 7181 1262 425 1263 425 445 1244 1262 424 1264 424 446 1243 445 1243 445 1244 444 1243 445 1243 445 1244 1263 7179 1263 424 1264 424 445 1243 1263 425 1263 425 445 1244 444 1244 444 1243 444 1245 417 1270 418 1269 1264 7180 1262 425 1263 425 418 1270 1263 425 1263 424 419 1270 418 1270 418 1270 418 1270 418 1270 418 1270 1263 7179 1264 424 1264 424 419 1269 1264 424 1264 424 419 1270 418 1269 419 1270 418 1270 418 1270 418 1270 1263 7179 1264 424 1264 424 419 1269 1264 424 1264 424 419 1270 418 1270 418 1270 418 1269 419 1270 418 1269 1264 7180 1262 424 1264 424 419 1269 1264 425 1263 424 419 1270 418 1269 419 1270 418 1270 418 1270 418 1269 1264 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1262 424 1263 424 419 1268 1264 424 1264 424 419 1270 418 1270 418 1270 417 1270 418 1270 1263 424 418 8023 1263 424 1263 424 418 1270 1263 424 1264 424 418 1270 418 1269 419 1270 418 1269 419 1269 1264 424 418 8022 1264 424 1264 424 419 1269 1264 424 1264 424 418 1269 419 1269 419 1269 419 1269 418 1269 1264 424 418 8023 1263 423 1265 423 419 1271 1262 424 1264 423 419 1269 419 1268 420 1270 418 1269 419 1268 1265 424 418 8024 1263 423 1265 423 419 1269 1264 423 1265 424 418 1269 418 1270 418 1269 419 1269 419 1268 1265 424 418 8023 1263 424 1263 423 419 1269 1264 423 1265 423 419 1270 418 1269 419 1269 419 1269 419 1269 1264 423 419 8022 1264 424 1263 424 418 1269 1264 423 1265 424 418 1268 420 1269 419 1269 419 1269 419 1269 1264 424 419 8023 1263 423 1264 424 418 1269 1264 424 1264 424 418 1270 418 1269 419 1269 418 1269 419 1269 1264 424 418 8023 1264 424 1264 424 418 1269 1264 424 1264 423 420 1269 419 1270 418 1268 420 1269 419 1269 1264 423 419 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1264 424 1263 423 419 1268 1265 423 1264 423 419 1269 418 1270 1262 423 419 1270 417 1269 419 1268 420 8022 1262 423 1264 423 419 1268 1264 424 1263 423 419 1268 419 1269 1263 423 419 1269 419 1268 420 1269 418 8022 1263 423 1264 424 418 1268 1264 423 1264 424 419 1269 418 1269 1264 423 419 1269 418 1269 419 1269 419 8021 1264 423 1264 424 418 1268 1264 423 1264 423 419 1268 419 1269 1263 423 419 1268 419 1268 419 1269 419 8020 1264 423 1264 423 419 1268 1265 423 1264 423 419 1269 418 1269 1264 423 419 1270 417 1268 420 1269 418 8022 1263 423 1265 423 419 1267 1266 423 1264 423 419 1268 419 1269 1263 423 419 1269 418 1268 419 1268 420 8022 1263 423 1264 423 419 1268 1265 423 1264 423 419 1268 420 1269 1264 423 419 1268 420 1268 419 1268 420 8021 1264 422 1265 423 419 1269 1263 423 1264 423 419 1269 418 1268 1264 423 419 1269 419 1269 418 1268 419 8021 1264 424 1263 423 419 1269 1263 423 1264 423 419 1269 418 1268 1264 424 418 1270 417 1268 419 1268 419 8022 1262 423 1264 423 420 1269 1263 423 1264 424 418 1269 418 1268 1264 424 418 1269 419 1269 418 1269 419 8021 1263 424 1263 424 418 1269 1263 423 1264 423 419 1269 419 1269 1263 423 419 1269 419 1269 419 1269 419 8021 1264 423 1264 423 419 1269 1263 423 1264 423 419 1268 420 1269 1263 423 419 1268 419 1269 418 1269 419 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1262 423 1264 423 419 1268 1264 423 1264 423 419 1269 419 1268 419 1268 419 1268 1264 423 419 1268 419 8020 1264 423 1264 423 419 1268 1264 423 1264 423 419 1268 419 1269 419 1268 419 1268 1264 423 419 1269 418 8021 1264 423 1264 423 419 1268 1264 424 1263 423 419 1269 419 1268 419 1268 419 1268 1264 423 419 1268 420 8021 1264 423 1264 423 419 1268 1264 423 1264 424 418 1268 420 1268 420 1269 418 1269 1263 423 419 1268 419 8021 1264 423 1264 423 419 1269 1263 423 1264 424 418 1269 418 1269 419 1268 419 1268 1264 423 419 1268 419 8021 1264 423 1264 423 419 1268 1264 423 1264 424 418 1268 419 1268 419 1268 419 1268 1264 424 418 1269 419 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2199 716 759 763 701 742 732 737 727 742 732 737 727 1448 759 738 726 743 732 1444 752 771 724 745 729 1447 760 1417 758 739 746 750 725 1426 760 737 727 743 732 739 725 1452 755 743 732 50977 2206 711 753 1449 758 51061 2226 721 754 1451 724 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2214 833 744 1598 752 822 745 824 743 1584 746 813 743 811 745 829 717 1627 744 835 752 1584 746 824 743 1584 745 813 743 811 745 803 743 100100 2213 835 752 1589 751 824 742 826 751 1576 743 816 750 803 743 805 751 1619 752 827 750 1587 753 816 750 1576 743 816 751 803 743 806 750 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2218 859 723 1594 753 822 749 820 751 1577 749 811 750 804 746 803 747 1625 753 826 756 1583 753 816 755 1573 753 806 755 800 750 799 751 100206 2213 863 729 1588 748 826 756 813 748 1581 755 804 746 808 753 797 753 1618 750 830 752 1587 749 820 751 1577 749 810 751 830 720 829 721 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2221 740 729 736 723 743 747 719 750 716 753 1445 744 722 747 719 719 747 722 1450 749 717 752 741 728 1444 755 1445 754 712 747 746 744 1454 724 741 728 1444 755 1444 755 711 779 1446 722 51387 2222 741 728 1445 754 50964 2197 740 750 1448 751 50939 2221 741 728 1443 746 50962 2198 738 752 1447 721 50965 2217 718 751 1445 754 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2221 754 723 755 722 1415 728 1403 720 717 750 709 747 706 750 698 748 758 750 728 749 1414 719 1413 720 717 750 708 748 1395 727 1384 728 101577 2217 732 745 732 745 1418 725 1406 727 710 747 712 755 698 748 701 745 761 747 731 746 1417 726 1405 727 710 746 711 745 1398 725 1387 725 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2194 749 719 1455 753 1452 725 718 750 1480 749 720 728 742 726 717 783 792 718 777 723 1533 727 1477 752 743 757 767 722 746 723 1480 749 101228 2200 768 700 1448 750 1454 754 715 722 1482 747 722 746 723 745 723 777 772 728 767 754 1502 748 1482 726 769 731 766 723 745 755 1448 750 49842 2221 746 702 1447 782 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1224 443 1252 441 438 1266 1254 443 1254 450 438 1273 439 1273 438 1277 434 1271 1254 451 437 1274 438 7899 1250 441 1254 443 436 1267 1253 444 1253 450 438 1273 439 1272 439 1273 438 1271 1254 452 436 1275 436 7888 1253 440 1255 441 438 1267 1253 443 1254 450 438 1272 439 1274 437 1273 438 1271 1254 451 437 1274 438 7894 1255 442 1253 442 437 1266 1254 444 1253 451 437 1273 439 1274 437 1274 437 1273 1252 451 437 1275 437 7888 1253 442 1253 441 438 1268 1253 442 1255 449 439 1271 440 1276 435 1275 436 1272 1253 450 438 1271 441 7895 1254 440 1255 442 437 1267 1254 444 1253 450 438 1273 439 1275 437 1273 438 1272 1253 450 438 1275 437 7893 1254 442 1253 442 437 1267 1253 443 1254 451 437 1275 437 1273 438 1274 437 1273 1252 450 438 1272 440 7891 1253 442 1253 442 437 1266 1255 443 1254 451 437 1274 438 1274 437 1274 438 1272 1253 450 438 1274 438 7894 1254 441 1254 441 438 1267 1254 444 1253 450 438 1274 438 1274 437 1274 437 1272 1254 452 436 1273 439 7890 1254 441 1254 442 437 1266 1255 444 1253 450 438 1273 439 1274 437 1274 438 1273 1253 451 437 1274 438 7895 1253 441 1254 441 438 1267 1254 443 1254 451 437 1273 439 1276 435 1273 439 1271 1254 450 438 1274 438 7896 1254 441 1254 441 438 1267 1253 443 1254 451 437 1273 439 1274 437 1273 438 1272 1254 450 438 1274 438 7889 1253 441 1254 442 437 1267 1253 443 1254 450 438 1274 438 1274 437 1274 438 1272 1253 449 439 1273 439 7896 1254 442 1253 441 438 1268 1253 445 1252 451 437 1274 438 1274 437 1275 436 1271 1254 450 438 1274 437 7888 1254 441 1254 442 437 1268 1252 444 1253 450 438 1274 437 1275 437 1276 435 1273 1252 450 438 1274 438 7895 1254 442 1253 441 438 1267 1253 443 1254 451 437 1273 491 1221 491 1221 490 1220 1252 450 438 1274 491 7841 1253 441 1254 444 435 1267 1253 443 1254 450 491 1221 491 1219 492 1221 491 1218 1254 450 438 1273 492 7838 1253 441 1254 442 437 1267 1254 443 1254 451 437 1274 491 1221 490 1221 490 1220 1252 452 436 1274 491 7841 1254 441 1254 441 437 1268 1253 444 1253 450 438 1273 439 1272 439 1274 437 1271 1254 452 436 1275 437 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1280 415 1280 417 436 1267 1280 418 1279 424 1278 425 437 1275 490 1224 488 1223 489 1220 1279 422 1281 7000 1279 419 1276 416 489 1216 1279 419 1278 424 1279 423 492 1224 488 1222 490 1223 489 1220 1280 423 1279 6997 1278 417 1278 419 486 1215 1280 420 1277 423 1279 424 489 1226 485 1225 486 1227 435 1275 1252 450 1252 7029 1249 447 1225 469 406 1299 1226 471 1226 477 1225 479 405 1307 405 1307 405 1307 405 1304 1227 476 1226 7047 1228 469 1226 469 405 1300 1226 472 1226 475 1227 477 406 1307 405 1305 407 1306 406 1305 1226 475 1228 7052 1227 469 1226 468 405 1299 1228 471 1226 476 1226 476 407 1307 405 1306 406 1306 406 1303 1228 474 1228 7042 1227 468 1227 468 406 1299 1227 470 1227 476 1226 478 405 1309 403 1306 406 1307 405 1304 1226 476 1226 7057 1250 445 1250 444 433 1271 1251 446 1251 450 1252 452 435 1277 435 1277 435 1278 434 1275 1277 424 1278 6991 1278 417 1278 416 487 1218 1279 418 1279 423 1279 425 488 1223 489 1223 489 1223 489 1222 1279 424 1278 7004 1280 416 1279 415 489 1220 1276 417 1280 423 1279 425 488 1223 489 1223 489 1223 489 1221 1280 423 1279 6989 1280 415 1280 415 489 1217 1279 417 1280 423 1279 425 488 1223 489 1223 488 1223 489 1222 1279 423 1279 6999 1279 417 1278 416 488 1216 1280 418 1279 424 1278 424 489 1225 487 1223 489 1222 490 1222 1279 423 1279 6995 1278 416 1279 415 489 1216 1280 418 1279 423 1279 426 487 1224 488 1224 487 1223 488 1220 1281 423 1279 6999 1280 415 1280 416 488 1217 1279 417 1280 423 1279 425 488 1224 488 1226 486 1225 486 1220 1281 423 1279 6994 1279 417 1278 416 487 1218 1279 417 1280 422 1280 426 485 1226 486 1226 486 1226 485 1225 1278 423 1279 7001 1251 444 1251 442 435 1270 1252 445 1252 450 1252 452 434 1278 434 1277 435 1277 434 1276 1251 451 1251 7018 1250 445 1250 445 432 1272 1251 446 1251 451 1251 452 435 1278 434 1277 435 1277 435 1274 1253 450 1252 7031 1278 418 1277 416 436 1270 1277 419 1278 424 1278 426 435 1276 436 1275 437 1276 484 1224 1280 423 1279 6989 1279 417 1278 415 487 1219 1279 418 1279 424 1278 425 486 1225 487 1225 486 1225 487 1222 1280 423 1279 7013 1278 416 1279 415 488 1218 1279 418 1279 426 1276 425 487 1224 488 1224 487 1224 487 1222 1279 422 1280 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1254 441 1254 441 438 1272 1249 443 1254 448 1254 450 438 1274 438 1274 437 1276 1249 449 1253 451 437 7865 1253 443 1252 441 438 1268 1252 445 1252 451 1251 451 437 1274 437 1273 438 1275 1250 448 1254 450 438 7862 1253 441 1254 443 436 1268 1252 444 1253 449 1253 450 438 1274 437 1275 436 1272 1253 449 1253 451 437 7864 1252 444 1251 442 437 1266 1254 443 1253 448 1254 451 437 1273 438 1276 435 1272 1253 449 1253 452 436 7861 1254 441 1254 441 438 1268 1252 446 1250 448 1253 450 438 1272 439 1273 438 1273 1251 450 1252 452 436 7863 1253 441 1253 441 438 1267 1253 444 1252 449 1253 450 438 1273 438 1276 435 1270 1254 448 1254 451 437 7856 1253 441 1253 440 439 1266 1254 444 1252 450 1251 451 437 1275 436 1274 437 1272 1252 449 1252 451 437 7868 1252 441 1253 441 438 1266 1253 443 1253 450 1251 451 437 1273 438 1274 437 1270 1254 448 1253 451 437 7854 1254 441 1253 440 439 1267 1252 443 1254 448 1253 450 438 1274 437 1273 438 1272 1252 447 1254 450 438 7868 1253 442 1252 441 438 1266 1253 443 1253 449 1252 450 438 1277 434 1272 439 1273 1251 448 1253 451 437 7856 1252 441 1253 443 436 1267 1252 442 1254 448 1253 451 437 1274 490 1221 490 1219 1252 449 1252 451 437 7864 1252 442 1253 441 490 1215 1252 444 1252 449 1252 450 491 1221 490 1220 491 1218 1253 449 1252 451 490 7807 1254 442 1253 441 491 1215 1251 444 1252 448 1253 451 490 1220 491 1221 490 1218 1253 448 1253 450 491 7810 1253 441 1253 441 491 1213 1254 443 1253 448 1253 451 490 1219 492 1220 491 1219 1252 448 1253 450 491 7808 1254 441 1253 442 489 1214 1253 443 1253 448 1254 450 490 1221 490 1222 489 1219 1253 449 1253 452 488 7813 1279 416 1279 416 487 1218 1278 418 1279 422 1280 425 486 1225 487 1225 486 1223 1279 423 1279 426 435 7896 1279 416 1279 416 437 1268 1279 418 1279 423 1279 425 437 1275 436 1275 436 1273 1279 423 1279 425 436 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1275 405 1279 401 436 1243 1279 401 1273 407 440 1238 436 1244 1278 429 408 1244 440 1239 435 1244 440 8113 1272 434 1250 430 407 1246 1276 430 1254 426 411 1242 432 1247 1275 432 405 1247 437 1242 432 1247 437 8115 1280 426 1248 432 405 1248 1274 433 1251 428 409 1244 440 1239 1272 434 413 1239 435 1245 439 1239 435 8118 1277 429 1245 435 412 1240 1271 436 1248 431 406 1247 437 1242 1280 427 410 1242 432 1247 437 1242 432 8121 1274 406 1278 428 409 1244 1278 402 1272 408 439 1240 434 1245 1277 404 433 1246 438 1240 434 1245 439 8114 1281 399 1275 405 432 1247 1275 406 1278 401 436 1244 430 1249 1273 407 440 1240 434 1245 439 1239 435 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1277 429 1245 435 412 1267 1245 435 1249 431 406 1273 1249 431 406 1247 438 1241 433 1246 439 1267 407 8120 1275 430 1255 426 411 1242 1280 426 1248 432 405 1274 1248 432 405 1248 436 1243 431 1248 436 1242 432 8123 1272 407 1278 429 408 1245 1277 430 1244 436 411 1268 1254 426 411 1243 431 1274 410 1268 406 1274 410 8117 1278 401 1273 433 404 1275 1247 433 1252 429 408 1271 1251 429 408 1271 413 1239 435 1271 413 1265 409 8119 1277 402 1272 408 439 1240 1272 434 1251 430 407 1272 1250 430 407 1246 439 1241 433 1245 439 1240 434 8120 1275 431 1254 426 411 1243 1279 427 1247 433 404 1249 1273 407 440 1265 409 1244 430 1275 409 1269 405 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1317 325 1360 371 472 1214 1291 397 1320 325 527 1224 474 1223 476 1225 473 1222 476 1223 474 1221 1317 7168 1289 356 1329 358 485 1240 1318 372 1316 325 530 1222 475 1224 472 1224 474 1223 475 1225 473 1222 1316 7209 1315 369 1316 368 449 1241 1289 399 1289 409 470 1252 446 1225 474 1250 448 1223 474 1222 475 1222 1316 7181 1315 368 1316 325 491 1242 1316 372 1315 379 475 1223 474 1250 449 1224 474 1222 476 1222 473 1222 1316 7176 1315 367 1316 370 474 1214 1315 324 1338 380 498 1224 474 1222 474 1222 475 1248 449 1222 502 1194 1289 7192 1315 368 1316 324 492 1240 1317 325 1363 377 475 1223 474 1249 449 1224 472 1223 474 1223 473 1223 1316 7204 1314 369 1315 370 473 1239 1289 371 1315 381 473 1224 473 1223 475 1224 473 1226 472 1223 473 1220 1316 7178 1313 366 1319 369 473 1215 1288 397 1316 380 472 1223 474 1225 472 1224 471 1250 448 1224 447 1248 1288 7203 1314 325 1356 324 521 1215 1286 424 1288 379 475 1223 472 1223 473 1224 475 1221 448 1248 476 1245 1289 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1314 348 1360 324 466 1269 1262 368 1345 324 530 1224 475 1222 474 1226 474 1223 474 1220 1316 378 450 8036 1315 394 1290 323 493 1240 1317 372 1316 377 475 1224 476 1222 475 1221 476 1222 473 1221 1290 324 557 8047 1314 369 1316 394 446 1242 1288 373 1314 379 448 1252 471 1226 471 1224 445 1252 472 1222 1287 431 447 8022 1315 369 1314 323 464 1274 1283 399 1315 388 385 1305 473 1223 500 1198 473 1223 474 1221 1315 380 474 8016 1313 324 1362 368 472 1215 1315 324 1364 378 474 1223 473 1223 474 1222 473 1224 473 1220 1315 378 477 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1350 359 1323 359 482 1175 1351 337 1346 345 506 1185 510 1183 1350 371 481 1187 508 1187 508 1189 506 7967 1343 362 1318 364 477 1210 1316 368 1316 376 476 1220 476 1218 1315 377 475 1220 475 1220 475 1220 475 8037 1315 366 1315 366 475 1211 1315 369 1315 377 475 1220 475 1219 1314 377 475 1220 475 1220 475 1221 474 8008 1314 367 1314 367 474 1212 1314 370 1314 378 474 1221 475 1219 1314 378 474 1221 475 1221 474 1221 474 8009 1314 366 1315 367 474 1212 1314 370 1314 378 474 1221 474 1219 1314 378 474 1221 474 1221 475 1221 474 7998 1314 366 1315 367 474 1212 1314 370 1314 378 474 1221 475 1219 1314 378 474 1221 475 1221 474 1221 475 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1347 336 1347 359 482 1175 1351 361 1322 370 481 1184 511 1185 510 1183 1349 370 481 1188 507 1189 506 7963 1319 362 1317 364 477 1210 1316 368 1316 376 476 1220 475 1220 475 1218 1315 376 476 1219 476 1220 475 8005 1315 366 1315 365 476 1211 1315 368 1316 377 475 1220 475 1219 476 1218 1315 377 475 1220 475 1220 475 7979 1315 366 1315 366 475 1211 1315 369 1315 377 475 1220 475 1220 475 1218 1315 377 475 1220 475 1220 475 7984 1314 366 1315 366 475 1211 1315 369 1315 377 475 1221 474 1221 475 1218 1315 377 475 1221 474 1221 475 7970 1314 367 1314 366 475 1212 1314 369 1315 378 474 1221 474 1221 474 1219 1314 378 474 1221 474 1221 475 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1395 358 1369 358 499 1215 1399 331 1341 384 472 1241 474 1240 475 1240 1345 383 473 1240 474 1240 474 8239 1366 387 1339 387 469 1246 1339 388 1339 387 469 1246 469 1246 469 1246 1339 388 469 1246 469 1246 469 8242 1339 387 1339 387 469 1246 1339 387 1339 387 469 1246 469 1246 469 1246 1339 387 469 1246 469 1246 468 8243 1338 388 1338 388 469 1246 1339 388 1338 388 468 1246 468 1246 469 1247 1338 388 468 1246 468 1247 468 8243 1338 388 1338 388 468 1246 1338 388 1338 388 468 1246 469 1246 468 1246 1339 387 469 1246 468 1246 468 8218 1363 363 1363 363 493 1222 1363 363 1363 363 493 1222 493 1222 492 1222 1363 363 493 1221 493 1222 493 8217 1363 363 1363 363 493 1222 1363 363 1363 363 493 1246 468 1223 492 1223 1361 375 481 1247 467 1247 467 8243 1337 388 1338 388 468 1247 1337 389 1337 388 468 1247 468 1247 468 1247 1337 389 467 1248 466 1248 466 8243 1337 389 1337 389 467 1248 1336 389 1337 390 466 1248 466 1248 466 1248 1336 390 466 1248 466 1248 466 8244 1336 390 1311 415 465 1249 1336 390 1336 391 465 1250 465 1249 465 1250 1310 415 465 1250 464 1250 439 8270 1310 416 1310 416 440 1275 1310 417 1309 417 438 1300 414 1301 413 1301 1284 442 414 1301 413 1301 413 8297 1284 442 1284 442 413 1301 1284 443 1283 443 413 1302 412 1302 412 1302 1282 443 413 1302 412 1302 412 8298 1282 443 1283 443 413 1302 1283 443 1283 444 412 1303 411 1303 411 1303 1282 444 412 1303 411 1303 411 8299 1280 445 1281 470 385 1329 1255 470 1256 471 384 1330 384 1329 385 1329 1255 471 385 1330 384 1330 384 8326 1253 472 1254 472 384 1331 1253 473 1253 499 356 1357 357 1358 356 1358 1226 499 356 1358 356 1359 355 8355 1224 502 1224 501 355 1385 1199 527 1199 527 328 1386 328 1386 328 1386 1199 527 328 1387 327 1387 327 8409 1171 554 1172 555 300 1414 1172 555 1171 555 300 1416 299 1416 298 1415 1171 556 298 1442 272 1442 272 8438 1143 583 1143 583 271 1471 1115 611 1115 664 179 1536 178 1563 122 1619 1006 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1333 408 1299 408 449 1268 1302 410 1300 417 449 1274 451 1272 1299 417 449 1276 449 1276 449 1276 449 7978 1324 384 1324 385 472 1247 1322 389 1321 395 471 1256 470 1254 1320 396 470 1280 446 1280 446 1280 446 7977 1296 411 1297 388 469 1273 1296 389 1321 421 445 1280 446 1278 1296 420 446 1280 445 1280 446 1280 445 7982 1296 412 1296 412 445 1273 1296 414 1296 421 445 1280 445 1278 1296 421 445 1281 445 1280 445 1280 445 7976 1295 412 1296 412 445 1273 1296 414 1296 421 445 1280 445 1278 1296 421 445 1281 444 1280 445 1280 445 7987 1295 412 1296 412 445 1273 1296 414 1295 421 445 1280 445 1278 1295 421 445 1280 445 1280 445 1280 445 7952 1319 412 1296 412 445 1273 1296 414 1296 422 444 1280 445 1278 1296 422 444 1281 444 1280 445 1280 445 7980 1296 412 1296 412 445 1273 1296 414 1296 421 445 1281 444 1279 1295 422 444 1281 444 1280 445 1280 445 7952 1318 413 1295 412 445 1273 1296 414 1296 421 445 1280 445 1278 1296 421 445 1280 445 1281 444 1280 445 7956 1319 412 1296 412 445 1273 1295 415 1295 422 444 1280 445 1278 1295 422 444 1280 445 1280 445 1280 445 7957 1319 412 1295 412 445 1273 1295 415 1294 422 444 1280 445 1279 1294 422 444 1280 445 1281 444 1281 444 7956 1318 413 1294 413 444 1273 1295 415 1294 422 444 1281 444 1279 1295 422 444 1281 444 1281 444 1281 444 7974 1295 413 1295 413 444 1274 1294 415 1295 422 444 1281 444 1279 1294 422 444 1281 444 1281 444 1281 444 7979 1294 413 1295 413 444 1274 1294 416 1294 423 443 1281 444 1279 1294 422 444 1281 443 1281 444 1281 444 7973 1294 414 1294 413 444 1274 1294 416 1293 423 443 1281 443 1279 1294 423 443 1282 443 1282 443 1282 443 7986 1293 414 1293 414 443 1274 1294 416 1293 423 443 1282 443 1280 1293 423 443 1282 443 1282 443 1282 443 7975 1293 415 1293 415 442 1275 1293 417 1292 424 442 1282 442 1280 1293 424 442 1283 441 1283 442 1283 442 7980 1292 415 1292 415 442 1276 1292 417 1292 425 441 1283 441 1281 1292 425 441 1283 441 1284 441 1284 441 7975 1292 416 1291 416 441 1277 1291 419 1290 426 440 1284 440 1282 1290 427 439 1285 439 1285 440 1284 440 7981 1291 442 1265 442 415 1302 1265 444 1265 451 415 1310 414 1308 1265 452 414 1310 414 1310 414 1310 415 7977 1289 442 1241 467 414 1303 1240 469 1240 476 414 1310 414 1308 1240 476 414 1311 413 1311 412 1312 414 8013 1240 467 1240 467 413 1305 1239 470 1239 477 412 1312 389 1333 1240 477 413 1311 412 1312 389 1336 388 8027 1239 468 1239 468 388 1329 1239 470 1239 478 387 1337 387 1334 1239 478 388 1336 388 1337 387 1337 388 8032 1239 469 1238 494 362 1331 1237 496 1213 504 362 1362 386 1337 1212 504 362 1362 362 1363 362 1363 361 8054 1212 495 1212 495 361 1356 1212 497 1212 505 360 1364 360 1362 1211 505 360 1364 360 1364 360 1364 360 8060 1212 522 1185 522 334 1384 1185 524 1185 532 333 1391 333 1389 1184 532 333 1391 333 1391 333 1392 333 8089 1184 524 1183 550 306 1411 1158 552 1157 559 305 1420 304 1418 1156 586 278 1446 278 1446 278 1446 278 8143 1130 603 1104 605 249 1493 1077 712 997 4114 1050 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1351 383 1324 383 474 1217 1352 386 1324 392 448 1249 477 1249 477 1246 1353 390 475 1249 475 1250 474 7942 1323 386 1322 386 471 1247 1321 388 1322 396 470 1254 471 1255 470 1253 1320 396 471 1254 471 1255 470 7933 1320 387 1321 387 470 1248 1320 389 1321 396 471 1255 470 1255 470 1253 1321 397 469 1255 470 1255 470 7943 1319 388 1320 388 469 1248 1320 390 1319 397 469 1255 469 1256 468 1254 1319 397 469 1255 469 1256 468 7931 1318 388 1319 388 469 1249 1318 391 1318 398 468 1257 468 1256 468 1255 1318 398 468 1257 468 1256 468 7949 1318 389 1318 390 467 1250 1317 392 1317 422 444 1281 443 1281 443 1279 1294 422 444 1281 443 1281 443 7956 1294 413 1294 413 444 1274 1293 415 1294 423 443 1281 443 1281 443 1279 1294 423 443 1281 443 1281 443 7966 1293 414 1293 414 443 1274 1293 416 1293 423 443 1282 442 1282 443 1280 1293 423 443 1282 442 1282 442 7956 1292 414 1293 414 443 1275 1292 416 1293 424 442 1282 442 1282 442 1280 1293 424 442 1282 442 1282 442 7967 1292 415 1292 415 442 1276 1291 417 1292 424 442 1283 441 1283 441 1281 1292 425 441 1283 441 1283 441 7963 1291 416 1291 416 440 1277 1290 418 1291 425 441 1284 440 1284 440 1282 1290 427 439 1284 440 1285 439 7969 1289 417 1290 418 438 1302 1265 444 1265 451 415 1309 415 1310 414 1308 1264 451 415 1310 414 1310 414 7983 1264 443 1263 443 414 1303 1264 445 1264 452 414 1310 413 1311 413 1309 1239 477 413 1311 413 1311 413 7995 1262 444 1239 468 412 1306 1238 470 1239 478 412 1312 411 1312 412 1310 1237 479 387 1362 362 1362 387 8010 1212 494 1213 495 361 1355 1213 497 1212 504 361 1363 361 1363 361 1361 1211 504 361 1363 360 1364 360 8054 1210 496 1211 521 334 1383 1185 524 1184 532 333 1390 334 1391 333 1389 1183 532 333 1392 332 1391 332 8089 1157 524 1183 549 306 1411 1157 552 1157 585 279 1445 279 1445 278 1444 1130 586 278 1472 251 1447 277 8155 1103 630 1076 657 186 1530 1050 660 1048 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1275 398 1297 369 561 1107 1301 370 1298 397 533 1108 563 1107 563 1106 1274 421 534 1110 560 1111 558 7970 1268 424 1243 425 530 1140 1242 427 1242 428 528 1141 528 1142 527 1142 1241 428 528 1142 528 1142 527 8000 1240 428 1241 428 528 1142 1241 428 1241 428 528 1142 528 1142 527 1142 1241 428 528 1142 527 1142 528 8000 1240 429 1240 429 527 1142 1240 429 1240 429 528 1142 527 1143 527 1143 1240 430 526 1143 526 1144 525 8029 1211 461 1208 485 446 1225 1156 565 1103 620 229 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1283 419 1288 442 417 1258 1288 416 1281 448 411 1264 443 1258 439 1264 443 1258 439 1263 444 1259 1286 7832 1283 420 1287 443 416 1260 1286 417 1280 450 409 1265 442 1260 437 1265 443 1259 438 1265 442 1260 1286 7834 1281 421 1286 444 415 1260 1286 444 1263 440 408 1266 441 1261 436 1266 441 1261 436 1266 441 1261 1285 7833 1282 421 1287 443 416 1259 1287 443 1254 449 410 1265 442 1259 438 1264 443 1259 438 1264 443 1259 1287 7833 1282 420 1287 442 417 1259 1287 442 1255 448 411 1264 443 1258 439 1263 444 1257 440 1262 435 1268 1288 7831 1284 418 1289 441 407 1268 1288 441 1256 446 413 1262 435 1267 440 1261 436 1266 441 1261 436 1266 1290 7829 1286 416 1281 449 410 1265 1281 422 1285 444 415 1259 438 1264 444 1258 439 1263 444 1258 439 1263 1283 7836 1290 413 1284 445 414 1261 1285 444 1253 450 409 1266 442 1260 437 1264 444 1259 438 1263 445 1258 1288 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1308 413 1310 409 426 1277 1313 410 1302 425 431 1279 433 1304 408 1304 408 1277 435 1277 435 1275 1304 7045 1312 408 1304 415 431 1272 1307 415 1308 419 427 1283 429 1308 404 1307 405 1281 431 1280 432 1278 1311 7015 1311 409 1303 415 431 1272 1307 414 1309 419 427 1283 430 1281 431 1306 406 1279 433 1279 433 1276 1303 7043 1304 417 1306 412 434 1269 1310 412 1311 417 429 1281 431 1279 433 1304 408 1277 435 1276 426 1283 1306 7018 1308 412 1311 408 427 1275 1304 417 1306 422 434 1275 427 1284 428 1308 404 1281 431 1280 432 1276 1314 7030 1307 413 1310 409 426 1276 1303 418 1305 423 433 1276 426 1284 428 1309 403 1281 431 1280 432 1277 1313 7009 1307 413 1310 409 426 1275 1304 418 1305 422 434 1275 427 1283 429 1307 405 1279 433 1278 434 1274 1305 7037 1310 410 1302 416 430 1272 1307 414 1309 418 428 1281 431 1305 407 1277 435 1275 427 1283 429 1279 1310 7010 1306 414 1309 409 426 1300 1279 417 1306 421 425 1309 403 1306 407 1278 434 1275 427 1284 428 1280 1309 7031 1306 414 1309 409 426 1300 1279 417 1306 421 425 1308 404 1306 406 1277 435 1274 428 1282 430 1277 1312 7049 1308 411 1312 406 429 1297 1282 413 1310 417 429 1304 408 1275 427 1282 430 1279 433 1276 436 1272 1307 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1318 409 1321 412 453 1248 1326 412 1318 412 442 1256 443 1255 1319 404 450 1249 450 1249 450 1249 450 8000 1319 402 1317 406 448 1249 1315 420 1371 353 501 1196 451 1247 1317 406 448 1249 450 1247 441 1254 445 8002 1306 408 1311 403 441 1254 1309 415 1315 401 443 1253 446 1250 1314 402 442 1254 445 1251 448 1248 440 7999 1310 405 1304 411 443 1252 1312 414 1305 412 442 1254 445 1252 1312 407 447 1249 450 1247 441 1255 444 7998 1310 406 1313 404 450 1246 1307 418 1363 354 449 1247 441 1255 1309 407 447 1249 450 1246 442 1254 445 7998 1311 403 1306 409 445 1250 1303 419 1311 404 450 1244 444 1251 1313 402 442 1253 446 1250 449 1248 440 8002 1306 436 1283 432 422 1245 1308 444 1285 430 414 1254 445 1252 1312 432 422 1245 443 1253 446 1250 449 7998 1311 407 1312 403 451 1242 1311 415 1304 411 443 1250 449 1247 1307 411 443 1249 450 1245 443 1252 447 7998 1311 408 1301 413 441 1279 1285 415 1304 411 443 1276 423 1273 1280 410 444 1275 424 1272 416 1280 419 7998 1311 408 1301 414 440 1251 1313 414 1305 409 445 1248 451 1245 1308 411 443 1249 450 1246 442 1254 445 8003 1306 414 1305 409 445 1248 1306 421 1309 406 448 1246 442 1253 1311 407 447 1246 442 1253 446 1250 449 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1323 406 1313 415 449 1249 1314 420 1309 415 449 1249 450 1248 450 1248 1316 412 442 1258 451 1248 451 7999 1319 408 1311 415 449 1249 1314 420 1320 405 449 1249 450 1249 450 1250 1313 413 451 1248 450 1249 449 8007 1321 406 1313 414 450 1248 1315 419 1311 415 449 1249 450 1249 449 1250 1314 413 441 1257 452 1248 450 8006 1312 415 1314 412 442 1257 1317 417 1312 411 443 1255 444 1256 443 1258 1316 410 444 1255 444 1256 442 8014 1314 411 1318 408 446 1252 1312 421 1319 405 449 1249 450 1248 450 1249 1314 411 443 1256 442 1256 442 8014 1314 410 1319 405 449 1248 1315 416 1313 408 446 1251 448 1250 449 1250 1314 410 444 1253 445 1252 446 8006 1312 412 1317 407 447 1251 1313 420 1309 415 449 1248 440 1257 441 1256 1318 406 448 1251 447 1251 447 8005 1313 412 1317 407 447 1250 1313 418 1311 412 442 1255 443 1254 444 1254 1320 405 449 1249 450 1249 449 8003 1315 409 1310 413 441 1256 1318 414 1315 407 447 1251 447 1250 448 1250 1313 410 444 1255 443 1254 444 8008 1320 406 1313 409 445 1253 1310 422 1318 405 449 1249 449 1249 449 1249 1314 412 442 1256 442 1257 452 8003 1315 413 1316 407 447 1250 1313 421 1319 406 448 1249 449 1250 448 1279 1295 406 448 1246 452 1247 451 8002 1326 407 1322 415 449 1251 1323 424 1326 414 450 1250 448 1256 453 1249 1325 409 445 1253 445 1255 454 8005 1323 413 1327 406 448 1254 1330 419 1321 412 452 1248 450 1250 448 1254 1330 404 450 1250 448 1254 455 8002 1316 408 1311 412 452 1244 1319 413 1316 406 448 1248 450 1248 450 1249 1314 407 447 1249 449 1249 449 8005 1313 411 1308 411 443 1252 1311 417 1312 406 448 1249 449 1248 450 1248 1315 434 420 1251 447 1252 446 -# -name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 40 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 01 00 00 00 -# -name: Mode -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 09 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 86 00 00 00 -# -name: Mode -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 01 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 19 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 1C 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 19 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 0C 00 00 00 -# -name: Mode -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 07 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9248 4429 664 479 662 504 663 478 664 478 663 1588 687 1611 663 478 662 480 661 1615 659 1616 659 1616 658 1616 659 483 658 483 658 1616 658 1616 658 1616 658 1617 657 1616 658 483 658 1617 657 483 658 483 658 1616 658 483 658 483 658 483 658 1617 657 483 657 1617 657 1617 657 484 657 39720 9237 2194 658 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9281 4426 667 503 640 503 666 477 666 477 666 1609 666 1610 665 478 664 480 662 1614 661 1615 661 1615 661 1615 661 482 661 482 661 1615 661 1615 661 482 661 1615 661 1616 660 482 661 483 660 482 661 482 660 1616 660 1616 660 483 660 483 660 1616 659 1616 660 1616 660 1616 659 483 660 39712 9244 2192 659 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9278 4401 665 478 663 479 687 479 664 477 664 1586 688 1610 664 478 662 479 661 1614 660 1615 659 1615 660 1615 660 482 659 482 659 1615 660 1615 659 1615 659 1615 659 1615 659 482 659 482 659 482 659 482 659 1615 659 482 659 482 659 482 659 1615 659 1615 659 1615 659 1615 659 482 659 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9261 4448 641 499 642 499 642 498 643 498 643 1632 643 1632 667 475 666 475 666 1609 665 1610 664 1611 663 1612 662 479 662 479 662 1613 661 1614 660 479 662 479 662 1613 661 1612 662 1637 637 504 637 504 637 1637 637 1637 637 1613 661 504 637 504 637 504 636 1637 637 1637 637 504 636 39707 9242 2184 662 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9265 4406 688 455 686 479 662 479 662 479 662 1612 661 1613 660 481 659 482 658 1617 657 1618 656 1618 656 1618 656 485 656 485 655 1618 656 1618 656 1618 656 485 655 1618 655 485 656 1618 655 485 656 485 655 1618 655 485 655 1619 655 485 655 1619 654 486 654 1619 655 1619 655 486 654 39717 9230 2198 654 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1261 422 1260 422 514 1169 1260 422 1260 422 418 1265 418 1266 417 1266 514 1169 514 1169 514 1168 1262 7159 1261 422 1261 422 418 1265 1261 422 1260 422 418 1265 418 1266 417 1266 417 1266 418 1264 419 1265 1261 7159 1260 422 1260 422 418 1265 1261 421 1262 422 418 1266 514 1168 516 1169 515 1167 516 1168 515 1168 1261 7160 1261 422 1261 421 515 1168 1262 422 1261 421 515 1169 515 1167 516 1167 516 1167 516 1168 515 1168 1261 7160 1260 422 1260 422 514 1168 1262 421 1261 421 419 1264 515 1169 515 1169 514 1169 515 1169 515 1168 1262 7160 1260 421 1261 421 418 1266 1260 422 1261 422 417 1265 418 1265 418 1266 417 1265 418 1265 418 1266 1260 7159 1261 422 1260 422 418 1266 1260 421 1261 422 417 1265 418 1266 417 1266 417 1265 418 1266 417 1265 1261 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1341 341 1341 341 498 1186 1340 341 1341 342 497 1186 521 1160 499 1186 1340 342 522 1162 521 1162 521 7900 1340 342 1340 343 521 1163 1339 343 1339 343 521 1162 521 1162 521 1162 1340 343 521 1163 521 1163 520 7902 1338 344 1338 343 496 1187 1340 343 1339 343 521 1164 520 1164 519 1163 1339 344 519 1163 496 1187 521 7901 1339 343 1339 344 495 1187 1340 342 1341 343 496 1187 497 1186 497 1187 1340 343 496 1188 495 1187 496 7927 1337 343 1339 343 496 1187 1339 343 1339 343 496 1188 520 1163 520 1163 1340 343 496 1187 520 1163 520 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1342 341 1341 340 500 1183 1343 341 1341 341 499 1184 499 1184 499 1184 499 1183 1343 340 499 1185 499 7923 1341 340 1342 341 498 1184 1342 341 1341 341 498 1185 498 1186 497 1184 500 1186 1341 342 497 1185 498 7923 1341 342 1340 342 497 1185 1342 341 1341 342 521 1162 498 1186 497 1185 498 1186 1340 342 521 1162 521 7899 1341 341 1341 341 498 1185 1341 342 1340 341 498 1186 497 1185 499 1185 498 1185 1341 341 498 1185 499 7923 1341 342 1341 341 499 1184 1342 342 1341 342 497 1184 499 1185 498 1184 499 1185 1341 342 497 1185 499 7922 1342 341 1341 341 499 1185 1341 341 1341 341 499 1183 500 1185 498 1185 498 1184 1342 342 497 1185 499 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1286 396 1287 396 444 1240 1286 396 1286 397 443 1239 444 1240 1285 396 444 1239 444 1240 443 1241 442 7977 1286 396 1286 396 500 1183 1287 396 1286 395 501 1183 501 1184 1286 395 500 1184 499 1183 500 1183 501 7922 1340 341 1341 341 499 1184 1342 341 1341 340 500 1184 499 1183 1343 340 500 1184 499 1183 500 1184 499 7922 1342 340 1342 340 500 1184 1342 340 1342 340 500 1185 498 1184 1342 340 500 1184 499 1184 499 1186 498 7922 1341 341 1341 341 499 1184 1342 341 1341 340 500 1183 500 1184 1342 341 499 1184 500 1184 499 1184 499 -# -name: Power -type: parsed -protocol: NECext -address: 82 21 00 00 -command: 1F E0 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 82 21 00 00 -command: 1B E4 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1278 421 1279 422 508 1210 1250 422 1278 421 509 1182 509 1183 508 1183 508 1183 508 1182 509 1184 1276 7327 1278 421 1279 421 509 1184 1276 422 1277 422 508 1183 508 1183 508 1182 509 1184 507 1184 507 1182 1278 7329 1351 345 1355 346 508 1183 1353 346 1353 346 509 1182 509 1182 509 1184 507 1183 508 1182 508 1183 1353 7251 1354 345 1354 346 508 1184 1352 346 1353 346 508 1184 507 1182 509 1185 506 1182 509 1183 508 1186 1350 7253 1352 345 1354 346 508 1183 1353 346 1353 345 509 1184 507 1183 508 1211 479 1184 507 1183 508 1185 1351 7254 1351 347 1352 348 506 1183 1353 347 1352 346 508 1183 508 1184 507 1187 504 1183 508 1185 506 1184 1352 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1278 421 1279 421 509 1183 1277 421 1278 422 508 1183 508 1182 509 1182 509 1182 509 1184 1276 421 509 8086 1279 421 1278 421 509 1183 1277 422 1277 422 508 1182 509 1183 508 1182 509 1184 507 1183 1352 347 508 8088 1353 346 1353 346 508 1184 1351 346 1353 347 507 1184 507 1184 507 1183 508 1187 504 1183 1353 346 508 8088 1352 345 1354 346 508 1183 1353 347 1352 346 508 1184 507 1185 506 1185 506 1184 507 1183 1349 350 508 8092 1345 352 1348 351 506 1185 1347 351 1348 352 505 1185 505 1185 506 1187 503 1185 505 1185 1347 354 504 8094 1250 446 1253 447 497 1193 1253 447 1252 446 497 1194 496 1195 495 1193 497 1195 495 1196 1252 446 495 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1253 446 1253 447 495 1194 1254 446 1253 447 422 1268 422 1268 1253 448 421 1270 421 1269 422 1269 422 8176 1251 448 1251 447 421 1269 1253 445 1254 447 421 1267 423 1269 1253 446 422 1270 421 1269 422 1269 422 8174 1253 447 1252 446 423 1269 1252 447 1252 445 424 1268 423 1269 1252 447 422 1268 422 1269 422 1268 423 8174 1252 446 1253 448 493 1195 1254 446 1253 447 495 1196 495 1195 1252 447 496 1193 497 1194 498 1194 496 8098 1253 445 1254 446 499 1194 1252 446 1253 446 498 1192 498 1191 1254 447 503 1187 504 1187 504 1186 505 8091 1346 352 1347 353 505 1186 1346 352 1347 352 505 1185 506 1187 1345 351 507 1185 505 1185 505 1184 506 -# -name: Power -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 88 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 8C 00 00 00 -# -name: Speed_dn -type: parsed -protocol: NEC -address: 30 00 00 00 -command: 87 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1255 435 1257 435 415 1278 1257 440 1256 435 415 1278 415 1278 415 1279 414 1279 414 1279 414 1278 1257 7176 1257 436 1256 435 415 1279 1256 438 1258 437 413 1279 414 1278 415 1278 415 1279 414 1279 414 1278 1257 7174 1256 436 1256 437 413 1278 1257 439 1257 435 415 1278 415 1278 415 1277 416 1279 414 1278 415 1278 1256 7175 1257 435 1257 435 415 1279 1256 440 1256 435 415 1278 415 1278 415 1278 415 1278 415 1280 413 1278 1257 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1284 409 1283 408 440 1253 1284 411 1285 407 441 1254 439 1253 440 1253 440 1251 442 1252 1285 409 439 7991 1285 409 1283 408 440 1253 1284 411 1285 407 441 1255 438 1253 440 1252 441 1251 442 1254 1283 407 441 7994 1283 410 1282 409 439 1254 1283 413 1283 409 439 1253 440 1253 440 1253 440 1253 440 1253 1284 407 441 7996 1284 409 1283 408 440 1253 1284 412 1284 408 440 1253 440 1253 440 1251 442 1253 440 1253 1284 409 439 7996 1284 407 1285 408 440 1254 1283 411 1285 409 439 1254 439 1253 440 1252 441 1253 440 1253 1284 408 440 7996 1284 408 1284 409 439 1255 1282 411 1285 408 440 1254 439 1252 441 1252 441 1254 439 1253 1284 407 441 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1251 439 1253 437 414 1282 1252 441 1255 436 415 1279 414 1280 413 1280 413 1279 1255 437 414 1279 414 8020 1253 438 1255 438 413 1281 1253 441 1255 438 413 1280 413 1279 414 1279 414 1280 1254 437 414 1280 413 8024 1253 438 1254 437 414 1281 1253 440 1256 437 414 1279 414 1279 414 1278 415 1280 1254 437 414 1280 413 8023 1254 440 1252 436 415 1279 1255 441 1255 436 415 1279 414 1279 414 1279 414 1279 1255 437 414 1279 414 8024 1253 437 1256 437 414 1280 1254 441 1255 437 414 1278 415 1280 413 1280 413 1280 1254 437 414 1279 414 8023 1254 438 1255 436 415 1280 1255 441 1255 436 415 1279 414 1279 414 1279 414 1280 1254 436 415 1280 413 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1252 437 1255 438 519 1174 1255 441 1255 437 518 1175 519 1173 520 1175 1253 437 518 1175 519 1175 518 7917 1253 438 1254 438 413 1281 1253 440 1256 437 414 1279 465 1228 465 1228 1254 436 415 1279 465 1227 466 7969 1253 438 1254 438 413 1280 1254 440 1256 436 415 1280 413 1278 466 1229 1254 437 414 1279 414 1278 415 8024 1252 437 1255 436 415 1279 1255 440 1256 437 414 1279 414 1279 414 1279 1255 437 414 1279 414 1278 415 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 1253 434 1258 435 492 1200 1258 438 1258 434 498 1194 494 1202 1257 433 494 1200 493 1200 493 1200 493 7940 1259 434 1258 433 493 1200 1259 438 1258 433 493 1199 494 1200 1259 434 492 1200 493 1200 493 1199 494 7939 1258 435 1257 434 497 1196 1258 437 1259 433 498 1196 492 1201 1258 434 497 1197 491 1200 493 1201 492 7941 1258 434 1258 435 495 1197 1258 437 1259 433 497 1197 497 1196 1259 434 496 1197 496 1197 496 1196 497 7939 1258 435 1257 434 495 1198 1258 438 1258 434 495 1198 495 1199 1257 435 494 1199 494 1197 496 1197 496 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 121 20917 311 387 1345 372 1316 372 450 1212 1319 400 1292 396 450 1210 479 1212 476 1238 450 1238 451 1238 451 1238 1345 7078 1316 372 1315 372 474 1216 1314 377 1314 374 472 1217 472 1217 472 1217 472 1217 472 1217 472 1217 1314 7106 1314 375 1313 374 472 1218 1313 378 1314 375 471 1218 471 1218 471 1218 471 1218 471 1218 471 1218 1313 7108 1312 376 1312 375 471 1219 1312 380 1312 376 470 1218 471 1218 471 1219 470 1218 471 1219 470 1219 1312 7110 1311 376 1312 376 470 1219 1312 381 1311 377 469 1219 470 1219 469 1220 469 1220 469 1220 469 1221 1310 7133 1286 402 1286 401 445 1245 1285 406 1286 401 445 1244 444 1244 445 1244 445 1244 445 1244 445 1245 1286 7134 1285 402 1286 402 444 1245 1286 406 1286 402 444 1245 444 1245 444 1245 444 1245 443 1245 444 1245 1285 7136 1284 403 1285 403 443 1245 1285 407 1285 403 443 1246 443 1246 443 1246 443 1246 443 1246 443 1246 1285 7135 1284 404 1284 404 442 1247 1284 408 1284 404 442 1247 442 1247 442 1247 442 1247 442 1248 441 1248 1283 7138 1282 405 1283 405 441 1249 1282 410 1281 407 440 1250 439 1249 440 1273 415 1274 390 1299 415 1274 1256 7166 1256 432 1256 431 415 1275 1255 436 1232 456 390 1299 415 1275 390 1299 390 1299 390 1299 390 1300 1231 7189 1231 457 1232 457 389 1301 1230 461 1231 458 388 1301 388 1301 388 1301 388 1326 363 1326 363 1327 1204 7217 1204 484 1205 484 362 1328 1203 488 1204 485 361 1328 361 1328 361 1328 361 1329 360 1353 335 1330 1202 7246 1177 511 1177 512 334 1356 1175 542 1150 539 307 1383 306 1383 306 1382 307 1409 280 1409 280 1410 1122 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1293 396 1293 395 450 1238 1294 399 1293 395 450 1238 451 1238 1294 395 450 1237 452 1237 452 1237 452 7964 1294 395 1318 370 475 1215 1316 377 1315 397 448 1241 448 1242 1290 398 447 1242 447 1242 447 1242 447 7970 1290 398 1292 372 473 1217 1316 377 1315 397 448 1217 473 1217 1316 372 473 1216 473 1217 473 1216 473 7944 1316 373 1316 372 473 1217 1316 376 1316 372 473 1216 473 1217 1316 372 473 1216 473 1216 473 1216 473 7942 1318 372 1317 371 474 1216 1317 375 1317 371 473 1216 473 1216 1318 371 474 1216 473 1216 473 1216 473 7943 1318 371 1318 371 474 1216 1318 375 1317 371 474 1216 474 1216 1318 371 474 1216 473 1216 473 1216 474 7943 1317 372 1317 372 473 1217 1317 376 1316 372 473 1217 472 1217 1316 372 473 1217 472 1217 472 1217 472 7944 1314 375 1314 398 447 1243 1290 403 1289 398 447 1243 446 1244 1289 399 446 1243 447 1243 446 1243 446 7971 1289 400 1289 399 446 1244 1289 403 1289 399 446 1243 446 1244 1289 399 446 1244 446 1244 445 1244 445 7972 1288 400 1289 399 446 1244 1290 403 1289 399 446 1244 445 1244 1289 399 446 1244 446 1244 445 1244 445 7946 1288 401 1288 400 469 1222 1311 380 1312 399 445 1244 445 1245 1289 399 445 1244 445 1245 445 1244 446 7971 1289 400 1289 400 444 1246 1289 403 1289 400 443 1246 444 1245 1272 416 445 1244 420 1269 444 1245 420 7996 1288 401 1288 400 420 1270 1264 429 1263 425 419 1270 419 1270 1264 425 419 1270 419 1270 419 1270 419 7995 1262 426 1263 425 419 1270 1263 430 1262 425 419 1270 419 1271 1262 426 418 1271 418 1271 418 1271 418 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1321 393 1295 393 451 1211 1322 396 1295 393 451 1210 507 1182 507 1181 1351 364 479 1208 480 1208 480 7933 1322 366 1320 368 475 1213 1319 373 1318 370 474 1214 474 1215 474 1215 1317 371 473 1215 473 1215 473 7939 1315 372 1316 372 472 1216 1316 376 1315 373 471 1217 472 1218 470 1218 1313 398 446 1242 446 1242 446 7967 1262 425 1263 425 444 1245 1263 429 1287 400 445 1244 445 1243 446 1244 1288 400 445 1243 446 1243 446 7966 1288 400 1288 400 445 1244 1287 404 1288 400 445 1244 444 1244 444 1245 1286 401 444 1244 444 1245 444 7968 1261 426 1286 401 419 1270 1285 406 1285 402 444 1244 445 1244 445 1245 1287 400 444 1244 445 1244 444 7968 1288 400 1288 400 444 1244 1288 404 1287 399 445 1244 444 1244 445 1244 1263 425 444 1244 444 1244 444 7967 1261 427 1261 426 418 1272 1260 456 1235 452 392 1296 392 1296 393 1297 1235 453 392 1296 393 1296 393 7996 1261 427 1261 427 418 1270 1262 430 1285 402 444 1245 443 1245 444 1245 1286 402 444 1245 444 1245 444 7969 1286 402 1286 402 444 1245 1286 406 1285 402 444 1245 444 1245 444 1245 1286 402 444 1245 444 1245 444 7968 1284 403 1285 403 443 1246 1284 407 1284 403 443 1246 442 1246 443 1246 1284 403 443 1246 443 1246 442 7971 1284 405 1283 405 441 1247 1283 408 1283 405 441 1248 441 1248 440 1249 1281 406 440 1249 440 1249 440 7974 1281 408 1280 431 390 1299 1232 460 1256 432 389 1299 390 1299 389 1300 1231 456 390 1299 390 1299 390 8023 1231 457 1231 457 389 1300 1231 461 1230 458 388 1300 389 1301 388 1301 1230 458 388 1302 387 1301 388 8027 1228 484 1204 484 362 1327 1204 488 1203 484 362 1328 361 1328 361 1329 1202 486 360 1354 335 1329 360 8080 1176 512 1176 512 334 1356 1175 542 1149 539 306 1382 307 1384 305 1383 1149 566 279 1436 252 1410 279 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1373 341 1295 393 452 1211 1322 397 1295 393 451 1210 479 1210 479 1211 478 1237 1296 393 451 1236 453 7962 1321 367 1320 368 476 1214 1318 374 1318 370 474 1215 474 1215 474 1215 474 1216 1316 371 473 1215 474 7941 1316 372 1316 372 472 1217 1316 376 1316 372 473 1216 473 1216 473 1217 472 1217 1315 373 472 1217 472 7941 1315 374 1314 374 471 1218 1314 401 1291 398 447 1242 447 1242 447 1242 447 1243 1290 398 446 1242 447 7968 1290 399 1289 398 447 1243 1290 402 1290 398 447 1242 447 1242 447 1242 447 1243 1289 398 446 1243 446 7968 1289 399 1289 398 447 1243 1290 402 1290 398 446 1243 446 1243 446 1243 446 1243 1290 398 446 1243 446 7967 1289 399 1289 399 446 1244 1289 403 1289 398 446 1243 446 1243 446 1243 446 1244 1289 399 446 1243 446 7969 1288 400 1288 399 445 1244 1288 403 1288 400 445 1244 445 1244 445 1244 445 1244 1288 400 445 1244 420 7995 1261 427 1261 428 416 1296 1236 456 1236 452 392 1296 393 1296 393 1296 393 1296 1236 452 393 1296 393 7997 1260 428 1260 428 416 1273 1260 432 1259 428 417 1296 392 1272 417 1296 393 1296 1237 452 393 1296 393 8021 1236 452 1236 452 392 1297 1236 456 1236 452 393 1297 392 1297 392 1297 392 1298 1235 453 391 1298 391 -# -name: Power -type: parsed -protocol: NEC -address: 03 00 00 00 -command: D8 00 00 00 -# -name: Speed_up -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 4F 00 00 00 -# -name: Speed_dn -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 47 00 00 00 -# -name: Rotate -type: parsed -protocol: NEC -address: 03 00 00 00 -command: C3 00 00 00 -# -name: Timer -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 9B 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9018 4461 617 1623 617 502 618 502 617 502 617 503 616 501 619 503 616 501 618 502 617 1622 617 1623 617 1621 618 1621 618 1622 617 1623 617 1622 618 1623 617 1623 617 501 619 503 617 531 588 502 618 502 617 502 617 503 617 501 618 1623 617 1623 617 1622 618 1623 617 1622 618 1623 617 1623 617 1621 619 503 617 503 617 501 619 503 616 501 618 503 617 503 616 504 616 1621 619 1623 617 1623 617 1624 615 1623 617 1623 616 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9021 4460 676 1564 676 445 675 444 676 445 675 445 675 445 674 445 675 446 674 445 675 1566 674 1565 674 1565 674 1566 673 1565 674 1565 674 1565 674 1566 674 1565 674 1565 674 445 674 446 674 445 674 446 673 445 674 446 674 446 673 446 674 1566 673 1565 674 1567 672 1568 672 1567 672 1566 674 1567 673 1567 673 447 673 447 673 448 672 446 621 499 621 499 621 500 672 449 618 1619 620 1621 619 1619 621 1620 620 1620 620 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8995 4486 588 1653 587 532 588 530 590 531 589 534 585 532 587 531 589 532 587 532 588 1652 587 1652 588 1652 588 1652 588 1653 587 1652 588 1652 588 1652 588 1651 589 531 589 532 588 1652 588 532 588 531 589 531 589 531 588 531 589 1652 588 1651 589 532 588 1651 589 1651 589 1652 588 1651 589 1652 587 533 586 531 588 1653 587 531 588 531 589 532 588 531 589 532 587 1651 588 1653 586 530 589 1651 588 1651 588 1652 587 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8993 4485 589 1651 589 529 590 529 591 530 590 530 589 530 590 531 589 530 589 531 589 1649 590 1650 589 1649 590 1650 589 1651 588 1649 590 1650 589 1654 585 1650 589 1651 588 1650 590 533 586 531 588 532 588 530 590 530 590 532 587 531 588 530 589 1650 589 1650 589 1652 587 1651 588 1651 588 1650 589 1650 589 1652 587 530 590 530 589 530 589 531 588 531 588 531 588 530 589 531 588 1651 588 1650 590 1651 589 1651 589 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 2280 776 785 1565 783 796 782 790 783 1549 783 810 752 805 752 800 752 858 752 830 752 826 776 797 775 793 774 789 773 810 747 805 747 102605 2223 832 752 1595 753 825 752 820 752 1581 752 811 751 806 751 802 750 860 750 833 775 804 773 799 773 795 773 790 773 785 772 780 773 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 4612 4435 543 1461 544 1460 545 1461 544 1460 543 1462 518 1487 518 2441 516 1513 491 1513 492 2465 492 1513 516 2441 516 1488 516 1489 515 2443 514 1492 514 1485 4580 4467 513 1492 513 1492 513 1492 513 1491 514 1492 513 1492 513 2445 513 1492 513 1492 513 2445 513 1492 513 2445 513 1492 513 1492 513 2445 513 1493 513 14064 9205 2275 513 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 4611 4435 544 1460 545 1460 546 1459 546 1460 519 1487 517 1487 518 2440 518 1488 517 1488 516 1512 517 2440 517 1488 516 1488 516 1489 515 2444 513 2445 514 1485 4581 4467 513 1492 513 1492 513 1492 513 1492 513 1492 513 1492 513 2444 514 1492 513 1491 514 1492 513 2445 513 1492 513 1492 513 1492 513 2444 513 2446 513 14066 9203 2275 513 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 4611 4434 545 1460 545 1459 546 1457 548 1458 521 1485 520 1485 545 2413 544 1462 542 2439 518 1487 517 1489 515 2444 512 1493 512 1493 512 1493 512 2446 512 1486 4581 4470 512 1493 511 1493 512 1493 512 1493 512 1493 512 1493 512 2446 511 1493 511 2446 512 1493 512 1493 512 2446 512 1493 512 1494 511 1493 512 2447 512 14071 9203 2279 511 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 4608 4436 545 1458 547 1458 547 1458 547 1458 521 1484 521 1485 520 2439 544 1461 544 2440 518 1487 517 2441 516 1489 515 2444 513 1493 512 1492 513 1494 512 1486 4577 4471 512 1493 512 1493 512 1493 512 1492 513 1493 512 1493 512 2446 512 1493 512 2446 512 1493 512 2446 512 1493 512 2446 512 1493 512 1493 512 1494 512 14051 9198 2279 512 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 4605 4437 544 1460 545 1459 546 1458 546 1459 520 1485 520 1486 545 2414 543 1487 517 1488 515 1489 515 1491 513 2446 512 2446 511 2446 512 1493 512 1494 511 1487 4573 4472 511 1493 511 1493 511 1493 511 1493 512 1494 510 1494 511 2446 511 1493 511 1494 511 1494 510 1493 511 2447 511 2446 511 2446 511 1494 511 1495 511 14064 9191 2281 510 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 4617 4436 544 1459 545 1458 545 1458 546 1458 520 1485 519 1485 545 2413 544 1462 542 1486 517 2440 515 2442 513 1492 512 1492 513 2444 512 1492 512 1493 513 1485 4582 4469 512 1493 512 1492 512 1492 512 1493 511 1493 512 1492 512 2445 511 1493 511 1493 511 2445 512 2446 511 1493 511 1493 511 2445 511 1493 511 1494 511 14048 9216 2280 512 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9220 4459 632 565 628 539 628 539 627 540 626 542 624 545 622 548 619 571 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 571 596 571 596 572 595 1652 596 571 596 571 596 571 596 572 595 1652 596 1652 596 1653 595 571 596 1652 596 39508 9197 2229 595 96159 9231 2229 596 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9244 4432 633 564 629 538 630 538 629 539 627 540 626 542 624 570 598 547 620 1651 597 1651 597 1651 597 1651 597 1651 597 1651 597 1651 598 1651 597 1651 597 1651 597 1651 597 570 597 570 597 570 597 570 597 570 597 570 597 570 597 570 598 1651 597 1651 597 1651 597 1651 597 1652 596 39509 9207 2227 597 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9223 4461 632 565 628 540 627 540 626 541 625 542 624 545 622 547 621 571 597 1629 620 1652 597 1653 595 1653 596 1653 596 1653 596 1653 596 1653 596 572 596 572 595 1653 596 1653 595 572 595 572 596 572 595 572 595 1653 596 1653 596 572 595 572 595 1653 596 1653 595 1653 596 1653 595 39519 9204 2229 596 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9227 4459 634 564 629 539 629 538 629 539 627 541 625 543 624 570 598 571 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 571 597 571 596 1652 597 571 596 571 597 571 596 571 597 571 596 1652 597 1652 597 571 597 1652 597 1652 597 1653 596 1653 596 39523 9207 2229 596 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9223 4459 633 564 629 538 629 538 628 539 627 541 624 544 622 570 597 570 597 1651 597 1651 597 1651 597 1651 598 1651 597 1651 597 1651 597 1651 597 570 597 1651 597 1651 597 1651 597 1651 597 570 598 1651 597 570 597 1651 597 570 597 570 597 570 597 570 597 1652 596 570 597 1652 596 39519 9202 2226 597 -# -name: Mode -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9241 4434 633 565 602 565 628 539 628 539 627 540 626 542 624 545 622 547 620 1628 620 1629 619 1629 619 1652 596 1653 595 1629 619 1652 596 1653 595 571 596 1653 595 571 596 571 596 572 595 571 596 1653 595 572 595 1653 595 571 596 1653 595 1653 595 1653 595 1653 596 572 595 1653 595 39519 9226 2229 597 -# -name: Power -type: raw -frequency: 40000 -duty_cycle: 0.4 -data: 20592 6864 2288 9152 11440 2288 2288 6864 2288 2288 2288 13728 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1328 375 1304 375 460 1196 1327 379 1299 379 430 1221 459 1221 458 1222 483 1197 483 1198 481 1222 1299 7068 1297 382 1297 382 453 1227 1296 382 1297 382 453 1227 453 1228 452 1228 452 1227 453 1228 452 1228 1296 7322 1295 383 1296 383 452 1228 1296 383 1296 383 452 1228 452 1228 452 1228 452 1228 452 1228 452 1228 1296 7070 1295 383 1296 384 451 1228 1296 383 1296 383 452 1228 452 1229 451 1228 451 1229 451 1228 451 1229 1295 -# -name: Speed_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1327 373 1306 377 459 1194 1329 373 1305 377 1276 402 433 1219 486 1194 486 1195 484 1219 1302 379 1299 7067 1299 380 1299 381 455 1224 1299 381 1298 381 1298 381 455 1225 455 1225 455 1225 455 1225 1298 381 1298 7344 1298 381 1299 381 455 1225 1298 381 1298 381 1298 381 455 1225 455 1225 455 1225 455 1226 1298 381 1298 7069 1298 382 1298 382 454 1225 1298 382 1298 382 1298 382 454 1226 454 1226 454 1226 454 1226 1297 382 1297 -# -name: Speed_dn -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1330 377 1302 377 459 1193 1330 376 1303 376 1277 401 435 1218 487 1194 486 1194 1329 377 1299 380 456 7911 1299 380 1299 380 456 1225 1299 380 1299 380 1299 380 456 1224 456 1225 455 1225 1298 381 1299 380 456 8149 1299 381 1298 381 455 1225 1299 380 1299 381 1299 381 455 1225 455 1225 455 1226 1298 381 1299 381 455 7913 1298 381 1299 381 455 1226 1298 381 1299 381 1298 382 454 1225 455 1226 454 1226 1298 382 1298 381 455 -# -name: Rotate -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1329 373 1306 377 459 1193 1330 377 1302 377 1277 402 434 1219 485 1195 1329 377 1300 379 456 1224 455 7911 1299 381 1298 381 455 1224 1299 380 1299 381 1298 381 455 1225 455 1225 1299 380 1299 381 455 1225 455 8165 1298 381 1299 381 455 1225 1299 381 1299 381 1298 381 455 1225 455 1226 1298 381 1299 381 455 1225 455 7913 1298 381 1298 381 455 1226 1297 381 1298 382 1297 382 454 1226 454 1226 1297 382 1298 382 454 1226 454 -# -name: Timer -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 1330 376 1303 376 460 1192 1331 376 1302 376 459 1191 463 1217 462 1218 1331 376 459 1195 484 1219 460 7907 1300 379 1300 380 456 1223 1300 380 1300 380 456 1224 456 1224 456 1224 1299 380 456 1224 456 1224 456 8166 1299 380 1299 380 456 1224 1299 380 1299 380 456 1224 456 1224 455 1224 1299 380 456 1224 455 1224 456 7909 1299 380 1299 380 455 1224 1299 380 1299 380 456 1224 455 1225 455 1225 1298 381 455 1225 454 1225 455 diff --git a/assets/resources/infrared/assets/projectors.ir b/assets/resources/infrared/assets/projectors.ir deleted file mode 100644 index 2fef79ee43..0000000000 --- a/assets/resources/infrared/assets/projectors.ir +++ /dev/null @@ -1,1497 +0,0 @@ -Filetype: IR library file -Version: 1 -# Last Updated 1st Oct, 2023 -# Last Checked 1st Oct, 2023 -# -# TEMP FIX FOR POWER -# -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 310 27591 171 27662 241 27731 307 27575 107 27749 306 27551 130 55520 243 27614 217 55584 129 27743 119 27756 115 27747 163 27712 308 27502 243 27650 217 27732 175 27693 167 27698 166 27689 171 27622 215 27712 133 27658 216 27716 129 27732 162 27698 305 27571 131 27753 310 27570 170 27707 162 27707 175 10960 9194 4518 618 542 618 543 725 434 672 1623 671 1647 646 514 592 568 592 568 592 1702 592 568 592 567 593 1702 592 568 618 1676 618 1676 618 1676 618 543 617 543 617 543 617 1677 617 544 616 544 616 544 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1679 615 1678 616 1678 616 40239 9196 2250 617 -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 310 27591 171 27662 241 27731 307 27575 107 27749 306 27551 130 55520 243 27614 217 55584 129 27743 119 27756 115 27747 163 27712 308 27502 243 27650 217 27732 175 27693 167 27698 166 27689 171 27622 215 27712 133 27658 216 27716 129 27732 162 27698 305 27571 131 27753 310 27570 170 27707 162 27707 175 10960 9194 4518 618 542 618 543 725 434 672 1623 671 1647 646 514 592 568 592 568 592 1702 592 568 592 567 593 1702 592 568 618 1676 618 1676 618 1676 618 543 617 543 617 543 617 1677 617 544 616 544 616 544 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1679 615 1678 616 1678 616 40239 9196 2250 617 -# -name: Vol_up -type: parsed -protocol: NEC -address: 08 00 00 00 -command: 48 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 08 00 00 00 -command: 49 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 08 00 00 00 -command: 14 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 08 00 00 00 -command: 0B 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 08 00 00 00 -command: 0B 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 40 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 48 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 44 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 83 7C 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 82 7D 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 08 13 00 00 -command: 87 78 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9055 4338 672 1551 669 1553 618 1603 619 481 617 482 616 481 617 507 591 1605 645 479 619 1577 645 1578 644 1578 644 479 619 480 618 1581 641 480 617 1605 617 1606 616 1606 615 483 615 1608 614 484 614 484 614 484 614 484 614 484 614 484 614 1609 614 484 614 1609 614 1609 613 1609 613 40058 9000 2068 614 95467 9022 2068 614 -# -name: Power -type: parsed -protocol: NECext -address: 08 13 00 00 -command: 87 78 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9055 4338 672 1551 669 1553 618 1603 619 481 617 482 616 481 617 507 591 1605 645 479 619 1577 645 1578 644 1578 644 479 619 480 618 1581 641 480 617 1605 617 1606 616 1606 615 483 615 1608 614 484 614 484 614 484 614 484 614 484 614 484 614 1609 614 484 614 1609 614 1609 613 1609 613 40058 9000 2068 614 95467 9022 2068 614 -# -name: Mute -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 29 D6 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 08 F7 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 04 FB 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 93 6C 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 15 00 00 00 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9032 4462 598 501 627 1604 627 530 598 531 677 423 706 422 706 421 707 451 677 1554 677 451 598 1633 598 1634 597 1634 598 1634 598 1634 625 1606 681 1550 626 502 598 530 599 529 600 1632 600 528 600 528 601 528 601 528 601 1631 600 1631 625 1607 625 504 625 1607 624 1608 624 1608 623 -# -name: Mute -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 02 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 1D 00 00 00 -# -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9096 4436 620 505 647 478 648 501 623 1599 647 1624 623 502 623 503 621 504 619 1628 618 507 617 507 617 1630 617 508 616 1630 617 1630 617 1631 616 508 616 508 617 508 616 1631 616 508 617 508 617 508 616 508 616 1630 616 1630 616 1631 616 508 616 1630 617 1630 617 1630 617 1631 617 509 616 508 616 509 616 509 616 509 616 509 615 509 616 508 617 1631 616 1631 615 1631 616 1631 616 1631 616 1631 616 1631 615 1631 616 14435 9093 2186 615 96359 9095 2184 617 -# -name: Power -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 1D 00 00 00 -# -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9096 4436 620 505 647 478 648 501 623 1599 647 1624 623 502 623 503 621 504 619 1628 618 507 617 507 617 1630 617 508 616 1630 617 1630 617 1631 616 508 616 508 617 508 616 1631 616 508 617 508 617 508 616 508 616 1630 616 1630 616 1631 616 508 616 1630 617 1630 617 1630 617 1631 617 509 616 508 616 509 616 509 616 509 616 509 615 509 616 508 617 1631 616 1631 615 1631 616 1631 616 1631 616 1631 616 1631 615 1631 616 14435 9093 2186 615 96359 9095 2184 617 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9091 4465 594 530 595 530 594 530 594 1651 595 1652 595 529 621 504 620 504 619 1628 618 507 617 508 616 1631 616 509 615 1631 616 1631 616 1632 615 509 616 509 616 509 615 1631 616 509 616 508 616 1631 616 509 616 1631 615 1631 616 1631 617 508 616 1631 616 1631 616 508 616 1631 617 508 617 509 616 509 616 509 616 509 616 509 616 509 616 509 616 1631 616 1631 616 1631 616 1631 616 1631 615 1631 615 1631 615 1631 616 14435 9090 2190 615 -# -name: Vol_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9092 4439 620 506 619 506 618 530 593 1627 620 1630 643 504 620 505 618 506 617 1630 617 508 616 508 616 1632 616 508 617 1631 616 1631 616 1631 616 1631 616 509 616 508 616 1631 616 509 616 509 615 1632 616 509 616 508 616 1631 616 1631 616 508 616 1631 615 1631 616 509 615 1632 615 509 616 509 616 509 616 509 616 509 616 510 615 509 616 509 616 1631 616 1631 615 1631 616 1631 615 1631 615 1631 615 1631 615 1631 615 14434 9088 2191 615 96339 9115 2189 616 96343 9117 2189 616 96343 9114 2189 616 -# AV-Mute -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9092 4439 620 506 618 506 618 530 594 1627 619 1629 643 505 619 505 619 506 617 1629 617 508 616 508 616 1631 616 508 616 1630 616 1630 616 1630 617 1630 616 1630 616 1631 616 508 616 508 616 508 616 1631 616 508 617 508 616 508 616 508 616 1630 616 1631 615 1631 616 508 616 1631 616 508 617 508 616 509 615 509 616 508 616 509 615 509 616 508 616 1631 615 1631 615 1631 616 1631 615 1631 615 1631 615 1631 615 1631 616 14433 9088 2191 615 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9014 4332 661 1570 661 471 660 473 658 474 657 476 655 498 633 498 634 502 633 499 633 1599 632 1599 632 1599 632 1599 632 1599 632 1600 631 1603 632 500 632 501 631 501 631 501 631 501 631 501 631 1601 631 504 631 1601 631 1601 631 1601 631 1601 631 1601 630 1601 630 501 631 1601 631 38177 8983 2149 630 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9014 4332 661 1570 661 471 660 473 658 474 657 476 655 498 633 498 634 502 633 499 633 1599 632 1599 632 1599 632 1599 632 1599 632 1600 631 1603 632 500 632 501 631 501 631 501 631 501 631 501 631 1601 631 504 631 1601 631 1601 631 1601 631 1601 631 1601 630 1601 630 501 631 1601 631 38177 8983 2149 630 -# -name: Vol_up -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 11 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 4C 00 00 00 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9042 4306 690 1541 665 468 664 468 664 469 663 470 662 471 660 495 636 499 636 497 634 1597 634 1598 633 1598 633 1599 633 1599 632 1599 633 1603 632 1599 633 499 633 499 633 500 632 499 633 500 632 1600 632 503 633 500 632 1600 632 1600 632 1600 633 1600 632 1600 632 500 632 1600 632 37912 8986 2145 633 -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3522 1701 472 426 444 1269 472 426 444 426 443 427 443 427 443 426 444 427 443 426 444 427 442 428 441 429 440 431 438 1304 437 433 437 433 438 433 437 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1305 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 459 411 459 411 459 411 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 459 411 459 411 1330 411 1330 411 460 410 1330 411 1330 411 1331 410 1330 411 74392 3516 1736 436 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 435 435 1305 436 435 435 435 435 1306 435 435 435 435 435 435 435 436 434 436 434 436 434 435 435 436 434 436 434 436 434 1330 411 1331 410 1330 411 1330 411 1330 411 459 411 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 435 436 434 436 1306 435 435 435 435 435 1306 435 435 435 435 435 435 435 435 435 435 435 436 434 436 434 435 435 436 434 435 435 1306 435 1330 411 1307 434 1331 410 1308 433 436 434 436 434 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 435 435 434 436 434 436 434 436 434 436 434 436 1306 435 435 435 435 435 435 435 1306 435 435 435 436 434 1306 435 435 435 436 434 436 434 435 435 436 434 436 434 460 410 460 410 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 434 436 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 435 435 435 435 434 436 1306 435 434 436 435 435 435 435 1306 435 436 434 435 435 1306 435 435 435 436 434 436 434 436 434 436 434 460 410 437 433 459 411 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3514 1736 437 434 436 1304 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 435 435 434 436 434 436 435 435 434 436 1305 436 435 435 435 435 435 435 1306 435 435 435 435 435 1306 435 435 435 436 434 435 435 459 411 436 434 435 435 459 411 459 411 459 411 459 411 1330 411 1306 435 1330 411 1330 411 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 529 7218 126 6585 219 703 180 5362 427 18618 177 -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3522 1701 472 426 444 1269 472 426 444 426 443 427 443 427 443 426 444 427 443 426 444 427 442 428 441 429 440 431 438 1304 437 433 437 433 438 433 437 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1305 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 459 411 459 411 459 411 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 459 411 459 411 1330 411 1330 411 460 410 1330 411 1330 411 1331 410 1330 411 74392 3516 1736 436 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 435 435 1305 436 435 435 435 435 1306 435 435 435 435 435 435 435 436 434 436 434 436 434 435 435 436 434 436 434 436 434 1330 411 1331 410 1330 411 1330 411 1330 411 459 411 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 435 436 434 436 1306 435 435 435 435 435 1306 435 435 435 435 435 435 435 435 435 435 435 436 434 436 434 435 435 436 434 435 435 1306 435 1330 411 1307 434 1331 410 1308 433 436 434 436 434 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 435 435 434 436 434 436 434 436 434 436 434 436 1306 435 435 435 435 435 435 435 1306 435 435 435 436 434 1306 435 435 435 436 434 436 434 435 435 436 434 436 434 460 410 460 410 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 434 436 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 435 435 435 435 434 436 1306 435 434 436 435 435 435 435 1306 435 436 434 435 435 1306 435 435 435 436 434 436 434 436 434 436 434 460 410 437 433 459 411 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3514 1736 437 434 436 1304 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 435 435 434 436 434 436 435 435 434 436 1305 436 435 435 435 435 435 435 1306 435 435 435 435 435 1306 435 435 435 436 434 435 435 459 411 436 434 435 435 459 411 459 411 459 411 459 411 1330 411 1306 435 1330 411 1330 411 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 -# ON -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 529 7218 126 6585 219 703 180 5362 427 18618 177 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9069 4362 622 486 621 487 621 491 622 1608 623 1603 622 487 621 487 621 491 622 1604 621 487 622 491 622 1604 621 491 622 1608 622 1609 621 1604 622 486 622 487 621 491 621 1605 621 487 621 491 622 1604 622 491 621 1609 621 1609 621 1604 622 491 621 1609 622 1604 621 491 621 1604 622 487 621 487 622 486 622 487 621 488 621 487 621 488 620 491 621 1609 622 1609 620 1609 621 1609 621 1609 621 1609 621 1609 621 1618 621 14330 9047 2137 620 -# -name: Vol_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9047 4362 621 486 622 463 645 490 622 1609 622 1604 622 487 620 487 621 491 622 1604 622 484 625 490 621 1605 649 463 621 1609 620 1611 621 1608 622 1605 621 486 622 491 622 1604 621 487 621 492 620 1604 621 488 621 492 620 1609 622 1604 621 492 622 1609 620 1605 621 491 622 1603 622 488 621 488 620 488 620 488 621 488 620 487 622 485 621 492 596 1635 621 1609 622 1585 643 1611 620 1608 621 1610 619 1611 620 1619 619 14332 9074 2109 647 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9073 4336 648 461 647 484 624 489 623 1607 623 1603 622 486 622 486 622 491 622 1604 621 487 621 491 622 1604 622 491 621 1609 621 1609 621 1609 621 1608 622 1609 621 1604 621 486 622 486 622 491 622 1604 622 486 622 487 621 487 621 491 622 1608 622 1609 621 1604 622 491 621 1604 621 487 621 486 622 487 621 487 621 487 621 487 621 487 621 491 622 1608 622 1608 622 1609 621 1608 622 1608 622 1608 622 1609 621 1617 622 14330 9047 2137 620 -# ON -name: Power -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 4F B0 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 80 19 00 00 -command: 10 EF 00 00 -# ON -name: Power -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 4F B0 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 80 19 00 00 -command: 10 EF 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 80 19 00 00 -command: 1C E3 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 80 19 00 00 -command: 46 B9 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 51 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 40 40 00 00 -command: 0A F5 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 4E B1 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 80 00 00 00 -command: 51 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 40 40 00 00 -command: 0A F5 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 4E B1 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 0E F1 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 0D F2 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 4F B0 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 4F B0 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 00 30 00 00 -command: 14 EB 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 08 16 00 00 -command: 87 78 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 08 16 00 00 -command: 87 78 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 08 16 00 00 -command: C8 37 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 01 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 01 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 02 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 28 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 29 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 84 F4 00 00 -command: 0B F4 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 33 00 00 00 -command: 00 FF 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 84 F4 00 00 -command: 0B F4 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 33 00 00 00 -command: 00 FF 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 33 00 00 00 -command: 1E E1 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 33 00 00 00 -command: 1D E2 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 33 00 00 00 -command: 0B F4 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 90 6F 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 90 6F 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 99 66 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 98 67 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 DF 00 00 -command: 1C E3 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 DF 00 00 -command: 1C E3 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 00 DF 00 00 -command: 4F B0 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 00 DF 00 00 -command: 4B B4 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 02 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 2E 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 02 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 2E 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 52 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 20 00 00 00 -command: 41 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 20 00 00 00 -command: 41 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 20 00 00 00 -command: 51 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 20 00 00 00 -command: 56 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 20 00 00 00 -command: 5A 00 00 00 -# -name: Power -type: parsed -protocol: SIRC15 -address: 54 00 00 00 -command: 15 00 00 00 -# -name: Power -type: parsed -protocol: SIRC15 -address: 54 00 00 00 -command: 15 00 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 82 7D 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 83 7C 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 14 EB 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 91 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 90 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 91 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 90 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 31 00 00 00 -command: D0 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 89 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 86 00 00 00 -command: 00 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 86 00 00 00 -command: 00 00 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 86 00 00 00 -command: 30 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 86 00 00 00 -command: 31 00 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 86 00 00 00 -command: 32 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 30 00 00 00 -command: 00 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 0D 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9032 4479 597 560 572 558 564 566 566 1666 589 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 1669 596 560 562 1671 594 1666 588 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 566 566 563 569 1664 591 1669 596 1664 590 565 567 1667 598 1661 593 1666 588 1671 594 562 570 560 562 568 564 565 567 563 569 560 562 568 564 565 567 1666 588 1671 594 1665 589 1670 595 1665 590 1669 596 1664 590 1668 597 13983 9029 2222 599 96237 9030 2221 589 96244 9034 2217 594 96244 9033 2218 592 96249 9038 2213 597 96239 9037 2214 596 96238 9028 2223 598 96221 9032 2215 595 -# -name: Power -type: parsed -protocol: NECext -address: 30 00 00 00 -command: 00 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 0D 00 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9032 4479 597 560 572 558 564 566 566 1666 589 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 1669 596 560 562 1671 594 1666 588 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 566 566 563 569 1664 591 1669 596 1664 590 565 567 1667 598 1661 593 1666 588 1671 594 562 570 560 562 568 564 565 567 563 569 560 562 568 564 565 567 1666 588 1671 594 1665 589 1670 595 1665 590 1669 596 1664 590 1668 597 13983 9029 2222 599 96237 9030 2221 589 96244 9034 2217 594 96244 9033 2218 592 96249 9038 2213 597 96239 9037 2214 596 96238 9028 2223 598 96221 9032 2215 595 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9034 4482 593 563 569 561 571 559 563 1698 566 1694 570 559 563 568 564 566 566 1695 569 560 572 559 563 1671 593 563 569 1692 562 1671 593 1693 571 558 564 567 565 565 567 1693 571 532 590 567 565 1695 569 560 562 1698 566 1694 570 1663 591 539 593 1693 571 1688 566 564 568 1691 563 567 565 565 567 563 569 561 571 559 563 567 565 565 567 563 569 1690 564 1695 569 1691 563 1696 568 1691 563 1697 567 1692 562 1697 567 13988 9030 2223 597 96250 9035 2219 591 96245 9032 2221 589 96240 9038 2215 595 96235 9033 2220 590 -# -name: Vol_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9028 4482 593 563 569 561 571 558 564 1696 568 1690 564 566 566 563 569 561 571 1688 566 563 569 561 571 1688 566 563 569 1690 564 1695 569 1689 565 1668 596 560 562 568 564 1695 569 560 562 568 564 1695 569 560 562 568 564 1695 569 1690 564 566 566 1692 572 1687 567 563 569 1690 564 566 566 564 568 562 570 559 563 567 565 565 567 562 570 560 562 1696 568 1665 589 1670 594 1665 589 1670 594 1664 590 1669 647 1612 590 13987 9031 2220 590 96223 9033 2217 593 96223 9034 2218 592 96225 9032 2219 591 96221 9087 2164 595 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9031 4479 596 560 572 558 564 566 566 1693 571 1688 566 563 569 561 571 559 563 1696 568 561 571 559 563 1697 567 562 570 1689 565 1694 570 1688 566 1693 571 1661 593 1693 571 558 564 566 566 564 568 1691 563 541 591 564 568 562 570 560 562 1697 567 1692 562 1696 568 562 570 1689 565 564 568 561 571 559 563 567 565 564 568 562 570 560 562 567 565 1694 570 1689 565 1694 570 1688 566 1693 571 1688 566 1693 571 1662 592 13987 9031 2220 590 96231 9034 2217 593 96234 9030 2222 588 96247 9037 2215 595 -# -name: Vol_up -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 11 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 14 00 00 00 -# OFF -name: Power -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 4E B1 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 1D 00 00 00 -# OFF -name: Power -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 4E B1 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 1D 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 11 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 15 00 00 00 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9075 4307 677 433 675 456 651 461 651 1579 650 1576 649 459 649 460 648 465 648 1578 647 461 622 491 622 1604 647 465 647 1583 622 1608 647 1579 647 461 647 466 622 1604 647 465 647 1579 647 461 645 463 648 465 648 1583 646 1580 646 466 647 1579 622 491 647 1583 622 1608 647 1579 647 461 647 461 622 486 622 486 647 461 647 462 646 462 622 491 646 1584 622 1608 647 1584 621 1608 647 1583 646 1584 647 1584 646 1592 622 14330 9047 2137 621 -# -name: Power -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: E6 00 00 00 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9075 4307 677 433 675 456 651 461 651 1579 650 1576 649 459 649 460 648 465 648 1578 647 461 622 491 622 1604 647 465 647 1583 622 1608 647 1579 647 461 647 466 622 1604 647 465 647 1579 647 461 645 463 648 465 648 1583 646 1580 646 466 647 1579 622 491 647 1583 622 1608 647 1579 647 461 647 461 622 486 622 486 647 461 647 462 646 462 622 491 646 1584 622 1608 647 1584 621 1608 647 1583 646 1584 647 1584 646 1592 622 14330 9047 2137 621 -# -name: Power -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: E6 00 00 00 -# -name: Vol_up -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: 07 00 00 00 -# -name: Vol_dn -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: 0B 00 00 00 -# -name: Mute -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: 0F 00 00 00 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3523 1701 472 426 444 1269 472 426 444 426 442 429 443 427 443 426 444 426 444 426 443 427 442 429 440 430 439 432 438 1304 437 433 437 432 438 432 438 433 437 433 437 433 437 433 437 433 437 433 437 1304 437 433 437 433 437 433 437 1304 437 433 437 433 437 1304 437 433 437 434 436 433 437 434 436 434 436 434 436 433 437 433 437 434 436 1304 437 1305 436 1305 436 1305 436 1305 436 1305 436 434 436 434 436 1305 436 1305 436 1305 436 434 436 1305 436 1305 436 1306 435 1306 435 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 1304 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 436 434 435 435 1307 434 1331 410 1307 434 1307 434 1330 411 1307 434 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 434 436 433 437 433 437 1304 437 434 436 434 436 434 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 435 435 434 436 1305 436 434 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1306 435 1307 434 1307 434 1307 434 1331 410 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 1304 437 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 437 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3523 1701 472 426 444 1269 472 426 444 426 442 429 443 427 443 426 444 426 444 426 443 427 442 429 440 430 439 432 438 1304 437 433 437 432 438 432 438 433 437 433 437 433 437 433 437 433 437 433 437 1304 437 433 437 433 437 433 437 1304 437 433 437 433 437 1304 437 433 437 434 436 433 437 434 436 434 436 434 436 433 437 433 437 434 436 1304 437 1305 436 1305 436 1305 436 1305 436 1305 436 434 436 434 436 1305 436 1305 436 1305 436 434 436 1305 436 1305 436 1306 435 1306 435 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 1304 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 436 434 435 435 1307 434 1331 410 1307 434 1307 434 1330 411 1307 434 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 434 436 433 437 433 437 1304 437 434 436 434 436 434 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 435 435 434 436 1305 436 434 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1306 435 1307 434 1307 434 1307 434 1331 410 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 1304 437 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 437 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9093 4441 620 507 618 530 594 531 593 1652 595 1653 620 505 620 505 619 506 617 1630 616 508 616 508 616 1632 615 509 615 1631 616 1632 615 1632 615 510 615 509 615 1632 615 509 615 1632 615 510 615 510 614 509 615 1632 614 1633 614 509 615 1633 614 509 615 1632 615 1632 614 1633 614 510 614 510 615 510 615 510 614 510 614 510 615 510 615 510 614 1632 615 1632 614 1632 615 1632 615 1632 615 1632 615 1632 615 1633 614 14439 9088 2192 614 96349 9112 2190 616 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9093 4441 620 507 618 530 594 531 593 1652 595 1653 620 505 620 505 619 506 617 1630 616 508 616 508 616 1632 615 509 615 1631 616 1632 615 1632 615 510 615 509 615 1632 615 509 615 1632 615 510 615 510 614 509 615 1632 614 1633 614 509 615 1633 614 509 615 1632 615 1632 614 1633 614 510 614 510 615 510 615 510 614 510 614 510 615 510 615 510 614 1632 615 1632 614 1632 615 1632 615 1632 615 1632 615 1632 615 1633 614 14439 9088 2192 614 96349 9112 2190 616 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 -# OFF -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 219 27658 217 27663 216 27658 216 27634 216 27642 215 27646 217 27662 217 27637 216 27649 216 27649 218 27656 217 27658 215 27640 214 27636 217 27649 216 27644 218 27635 217 27630 215 27645 216 27631 215 27632 216 27650 216 27628 217 27630 214 27627 217 27623 215 27632 215 27641 216 27634 214 27633 215 27648 215 27648 217 27651 215 27635 216 27629 216 27630 216 2021 9254 4461 618 542 618 542 618 542 618 1675 619 1676 618 541 619 541 619 542 618 1677 617 543 617 543 617 1678 616 568 592 1702 592 1702 618 1676 618 542 618 542 618 543 617 1677 617 543 617 544 616 1678 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1678 616 544 616 1678 616 40239 9200 2247 617 99930 110 27739 119 27738 123 27750 126 27738 175 27617 214 27716 203 27604 213 27639 217 27631 214 27722 136 27753 119 27736 175 27618 246 27683 177 27619 245 27685 171 55486 244 27693 158 27635 241 27695 170 27693 129 27717 340 27530 113 27757 106 27751 124 27728 172 27707 126 27666 215 27708 123 27733 123 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 18 E9 00 00 -command: 49 B6 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 14 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 14 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 48 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 40 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 18 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: B8 57 00 00 -command: 0C F3 00 00 -# -name: Power -type: parsed -protocol: NECext -address: B8 57 00 00 -command: 0C F3 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: B8 57 00 00 -command: 0D F2 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: B8 57 00 00 -command: 1E E1 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: B8 57 00 00 -command: 1F E0 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 81 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 81 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 8F 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 32 00 00 00 -command: 8C 00 00 00 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9066 4428 608 507 609 1622 609 507 609 507 609 1623 608 1623 609 507 609 506 610 1623 609 507 609 1622 610 1623 608 507 609 506 610 1622 609 1623 609 506 610 1622 610 506 610 1623 637 478 690 425 638 478 637 1594 637 1594 664 451 636 1594 610 506 610 1621 611 1621 610 1621 610 505 611 40183 9065 2156 637 95953 9037 2185 608 -# -name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: A8 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: A8 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 88 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 9C 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 8C 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 87 45 00 00 -command: 17 E8 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 87 45 00 00 -command: 17 E8 00 00 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9064 4354 666 1559 666 1562 662 1586 638 475 636 477 635 477 635 478 635 1590 635 1591 634 478 635 1591 634 478 634 478 635 478 634 1591 635 478 634 1591 634 478 635 478 634 478 635 1591 634 478 634 1591 635 478 634 478 634 1591 634 1591 635 1591 634 478 635 1591 634 478 634 1591 635 40957 9035 2144 634 95483 9047 2155 632 95484 9048 2153 633 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 87 45 00 00 -command: 50 AF 00 00 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9034 4385 638 1587 664 1562 663 1587 637 476 635 478 634 478 635 478 635 1591 634 1591 634 478 635 1591 635 478 634 478 635 478 635 1591 635 478 634 478 634 1591 634 478 635 479 634 1591 635 478 634 1591 635 478 634 1592 634 478 634 1591 635 1591 635 478 634 1592 634 478 634 1591 634 40958 9033 2144 635 -# -name: Power -type: parsed -protocol: NECext -address: FF FF 00 00 -command: E8 17 00 00 -# -name: Power -type: parsed -protocol: NECext -address: FF FF 00 00 -command: E8 17 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: FF FF 00 00 -command: BD 42 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: FF FF 00 00 -command: F2 0D 00 00 -# -name: Power -type: parsed -protocol: Kaseikyo -address: 41 54 32 00 -command: 05 00 00 00 -# -name: Power -type: parsed -protocol: Kaseikyo -address: 41 54 32 00 -command: 05 00 00 00 -# -name: Vol_up -type: parsed -protocol: Kaseikyo -address: 41 54 32 00 -command: 70 01 00 00 -# -name: Vol_dn -type: parsed -protocol: Kaseikyo -address: 41 54 32 00 -command: 71 01 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 81 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 17 E8 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 81 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 83 F4 00 00 -command: 17 E8 00 00 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9010 4413 532 1617 532 1617 533 489 533 489 533 489 558 464 558 465 557 1593 557 465 557 466 556 1594 555 467 555 1595 529 1621 554 1595 581 1569 581 441 581 1569 581 441 581 441 581 441 581 441 581 441 581 1569 581 1569 581 441 581 1569 580 1569 580 1570 580 1595 554 1595 555 468 554 42156 8983 2135 556 -# -name: Vol_dn -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9032 4390 556 1592 559 1591 559 463 559 463 558 464 557 465 556 465 557 1593 583 440 581 441 580 1569 581 441 581 1569 580 1569 581 1569 581 1570 580 1596 554 1596 554 468 554 468 554 468 554 442 580 442 580 1596 554 469 553 469 553 1596 554 1596 553 1597 550 1598 553 1598 552 469 551 42155 9008 2107 531 95218 9006 2108 582 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9011 4388 557 1617 532 1617 532 489 533 489 558 464 558 440 582 440 582 1593 556 466 556 466 556 1594 556 467 555 1595 555 1595 529 1620 554 1596 554 467 554 468 555 1595 579 443 581 1569 581 441 581 441 580 442 581 1569 581 1569 581 441 581 1569 580 441 581 1569 581 1569 581 1570 579 42152 8957 2159 556 -# -name: Power -type: parsed -protocol: NECext -address: 4F 50 00 00 -command: 02 FD 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 4F 50 00 00 -command: 02 FD 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 4F 50 00 00 -command: 08 F7 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 4F 50 00 00 -command: 0B F4 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 81 03 00 00 -command: F0 0F 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 87 45 00 00 -command: 51 AE 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 87 45 00 00 -command: 52 AD 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 -# -name: Vol_up -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 06 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 09 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 1A 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 00 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 00 00 00 00 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9035 4437 563 548 563 548 563 522 594 1645 591 1639 592 518 593 548 563 552 563 1640 592 548 563 553 562 1668 564 524 592 1642 594 1674 562 1673 563 1639 593 548 563 552 564 1669 562 548 563 520 615 529 586 1645 587 529 587 1650 586 1646 586 529 586 1650 586 1649 587 1646 586 524 587 524 587 524 587 524 587 525 643 467 644 440 671 467 644 472 643 1592 644 1593 643 1593 642 1594 641 1594 587 1649 585 1651 563 1682 562 14430 9008 2205 562 -# -name: Vol_up -type: parsed -protocol: NECext -address: 84 F4 00 00 -command: 2C D3 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 84 F4 00 00 -command: 2F D0 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 4F 50 00 00 -command: 0F F0 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 12 00 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 12 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 06 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 00 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 04 B1 00 00 -command: 58 A7 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 04 B1 00 00 -command: 58 A7 00 00 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9107 4376 681 1573 681 472 655 472 654 474 652 475 652 476 651 476 652 476 651 476 651 1604 651 1604 651 1604 651 1603 652 1604 651 1604 651 1604 651 1604 650 476 651 477 650 477 650 476 651 477 651 1604 650 476 651 477 650 1604 651 1604 650 1604 651 1604 650 1604 651 477 650 1604 650 39498 9079 2178 651 -# -name: Power -type: parsed -protocol: NECext -address: 8B CA 00 00 -command: 12 ED 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 8B CA 00 00 -command: 12 ED 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 8B CA 00 00 -command: 44 BB 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 8B CA 00 00 -command: 46 B9 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 8B CA 00 00 -command: 11 EE 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 81 03 00 00 -command: F0 0F 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 40 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 BD 00 00 -command: 01 FE 00 00 -# -name: Power -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 40 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 00 BD 00 00 -command: 01 FE 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 00 BD 00 00 -command: 10 EF 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 00 BD 00 00 -command: 0C F3 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 00 BD 00 00 -command: 6A 95 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 02 00 00 00 -command: 11 00 00 00 -# -name: Mute -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 51 AE 00 00 -# -name: Pause -type: parsed -protocol: NECext -address: 00 30 00 00 -command: A8 57 00 00 -# -name: Play -type: parsed -protocol: NECext -address: 00 30 00 00 -command: A9 56 00 00 -# -name: Play -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 5E A1 00 00 -# -name: Pause -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 5B A4 00 00 -# -name: Play -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 93 00 00 00 -# -name: Pause -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 93 00 00 00 -# -name: Play -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 15 00 00 00 -# -name: Pause -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 15 00 00 00 -# -name: Play -type: parsed -protocol: NECext -address: B8 57 00 00 -command: 18 E7 00 00 -# -name: Play -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8981 4411 532 1616 557 1592 557 465 556 465 556 466 555 467 554 468 554 1595 554 467 554 468 554 1595 554 467 580 1569 581 1568 581 1568 581 1569 580 441 581 1569 580 442 580 1570 579 1594 555 467 555 443 579 442 580 1594 554 467 555 1594 555 467 555 468 554 1595 554 1595 528 1620 529 42169 9008 2106 531 -# -name: Pause -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8981 4411 532 1616 557 1592 557 465 556 465 556 466 555 467 554 468 554 1595 554 467 554 468 554 1595 554 467 580 1569 581 1568 581 1568 581 1569 580 441 581 1569 580 442 580 1570 579 1594 555 467 555 443 579 442 580 1594 554 467 555 1594 555 467 555 468 554 1595 554 1595 528 1620 529 42169 9008 2106 531 -# -name: Play -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 41 00 00 00 -# -name: Pause -type: parsed -protocol: NEC -address: 31 00 00 00 -command: 41 00 00 00 -# -name: Play -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 03 00 00 00 -# -name: Pause -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 03 00 00 00 -# -name: Mute -type: parsed -protocol: NEC -address: 03 00 00 00 -command: 02 00 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 17 E8 00 00 -# -name: Power -type: parsed -protocol: NECext -address: 87 4E 00 00 -command: 17 E8 00 00 -# -name: Vol_up -type: parsed -protocol: NECext -address: 4F 50 00 00 -command: 07 F8 00 00 -# -name: Vol_dn -type: parsed -protocol: NECext -address: 4F 50 00 00 -command: 0A F5 00 00 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324 -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 363 1732 365 685 363 1734 363 1735 362 686 362 1735 362 687 361 685 362 1735 363 686 267 1830 362 1734 363 686 268 1830 361 687 267 42739 267 1829 268 780 268 1830 267 1829 268 781 267 781 267 1832 265 1830 267 780 268 1830 267 781 267 780 268 1830 267 781 267 1830 267 42723 267 1830 267 781 362 1735 267 1829 268 781 267 1830 267 782 266 780 268 1830 267 781 267 1831 266 1830 267 781 267 1829 268 782 266 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 270 1825 272 780 268 1827 349 1747 350 699 349 698 351 698 350 1748 350 698 350 1748 349 699 349 697 351 699 349 1746 351 698 350 44740 349 1745 352 698 350 1747 350 1747 350 698 350 1747 355 1742 355 692 356 1742 355 694 355 1743 354 1742 355 1742 355 694 354 1742 355 40555 355 1742 355 693 355 1742 355 1743 354 693 355 694 354 693 355 1743 354 693 355 1742 355 693 355 692 357 693 355 1741 356 694 354 -# -name: Vol_dn -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 353 1742 355 693 355 1742 355 1742 355 693 355 1744 353 694 354 1743 354 693 355 1743 354 694 354 693 355 694 354 1742 355 695 353 43687 354 1743 354 696 352 1744 353 1744 353 694 354 695 353 1742 355 694 354 1743 354 694 354 1743 354 1742 355 1742 355 694 354 1744 353 41606 351 1745 352 696 352 1746 351 1746 351 697 351 1747 350 696 352 1745 352 698 350 1746 351 698 350 696 352 698 350 1746 351 699 349 -# -name: Power -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: 02 00 00 00 -# -name: Power -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: 02 00 00 00 -# -name: Mute -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: D1 00 00 00 diff --git a/assets/resources/music_player/Marble_Machine.fmf b/assets/resources/music_player/Marble_Machine.fmf deleted file mode 100644 index 7403c9a0f1..0000000000 --- a/assets/resources/music_player/Marble_Machine.fmf +++ /dev/null @@ -1,6 +0,0 @@ -Filetype: Flipper Music Format -Version: 0 -BPM: 130 -Duration: 8 -Octave: 5 -Notes: E6, P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6, 4P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6 diff --git a/assets/resources/rfidfuzzer/example_uids_em4100.txt b/assets/resources/rfidfuzzer/example_uids_em4100.txt deleted file mode 100644 index 46ce16ba8c..0000000000 --- a/assets/resources/rfidfuzzer/example_uids_em4100.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Example file, P.S. keep empty line at the end! -0000000000 -FE00000000 -CAFE000000 -00CAFE0000 -0000CAFE00 -000000CAFE -00000000CA diff --git a/assets/resources/rfidfuzzer/example_uids_h10301.txt b/assets/resources/rfidfuzzer/example_uids_h10301.txt deleted file mode 100644 index 95ea9ac280..0000000000 --- a/assets/resources/rfidfuzzer/example_uids_h10301.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Example file, P.S. keep empty line at the end! -000000 -F00000 -E00000 -FE0000 -CAFE00 -00CAFE -0000CA diff --git a/assets/resources/rfidfuzzer/example_uids_hidprox.txt b/assets/resources/rfidfuzzer/example_uids_hidprox.txt deleted file mode 100644 index 88683caf1a..0000000000 --- a/assets/resources/rfidfuzzer/example_uids_hidprox.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Example file, P.S. keep empty line at the end! -000000000000 -00FE00000000 -00CAFE000000 -0000CAFE0000 -000000CAFE00 -00000000CAFE -0000000000CA diff --git a/assets/resources/rfidfuzzer/example_uids_pac.txt b/assets/resources/rfidfuzzer/example_uids_pac.txt deleted file mode 100644 index 56ed2069be..0000000000 --- a/assets/resources/rfidfuzzer/example_uids_pac.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Example file, P.S. keep empty line at the end! -00000000 -F0000000 -FE000000 -CAFE0000 -00CAFE00 -0000CAFE -000000CA diff --git a/assets/resources/subplaylist/example_playlist.txt b/assets/resources/subplaylist/example_playlist.txt deleted file mode 100644 index efa883cb08..0000000000 --- a/assets/resources/subplaylist/example_playlist.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Example file, it will not work, you need to add paths to your files! -sub: /ext/subghz/Vehicles/Tesla/Tesla_charge_AM270.sub -sub: /ext/subghz/Vehicles/Tesla/Tesla_charge_AM650.sub -sub: /ext/subghz/Test1.sub -sub: /ext/subghz/Test2.sub \ No newline at end of file diff --git a/assets/resources/swd_scripts/100us.swd b/assets/resources/swd_scripts/100us.swd deleted file mode 100644 index 3ad89a0ab1..0000000000 --- a/assets/resources/swd_scripts/100us.swd +++ /dev/null @@ -1 +0,0 @@ -swd_clock_delay 100 diff --git a/assets/resources/swd_scripts/call_test_1.swd b/assets/resources/swd_scripts/call_test_1.swd deleted file mode 100644 index 03f5575f41..0000000000 --- a/assets/resources/swd_scripts/call_test_1.swd +++ /dev/null @@ -1,6 +0,0 @@ - -message 0 "gonna call call_test_2" dialog - -call call_test_2 - -message 0 "back now" dialog diff --git a/assets/resources/swd_scripts/call_test_2.swd b/assets/resources/swd_scripts/call_test_2.swd deleted file mode 100644 index f358b6ece3..0000000000 --- a/assets/resources/swd_scripts/call_test_2.swd +++ /dev/null @@ -1,7 +0,0 @@ - -# first do a beeeeeep -beep 1 - -message 0 "Seems to work" dialog - -beep 0 diff --git a/assets/resources/swd_scripts/dump_0x00000000_1k.swd b/assets/resources/swd_scripts/dump_0x00000000_1k.swd deleted file mode 100644 index a8870fe30e..0000000000 --- a/assets/resources/swd_scripts/dump_0x00000000_1k.swd +++ /dev/null @@ -1,6 +0,0 @@ -ap_select 0 -max_tries 50 -block_size 4 -mem_dump /ext/swd_scripts/flash.bin 0x00000000 0x100000 2 -beep 1 -message 5 "Reading sucessful" diff --git a/assets/resources/swd_scripts/dump_0x00000000_4b.swd b/assets/resources/swd_scripts/dump_0x00000000_4b.swd deleted file mode 100644 index a8870fe30e..0000000000 --- a/assets/resources/swd_scripts/dump_0x00000000_4b.swd +++ /dev/null @@ -1,6 +0,0 @@ -ap_select 0 -max_tries 50 -block_size 4 -mem_dump /ext/swd_scripts/flash.bin 0x00000000 0x100000 2 -beep 1 -message 5 "Reading sucessful" diff --git a/assets/resources/swd_scripts/dump_STM32.swd b/assets/resources/swd_scripts/dump_STM32.swd deleted file mode 100644 index e675537c98..0000000000 --- a/assets/resources/swd_scripts/dump_STM32.swd +++ /dev/null @@ -1,6 +0,0 @@ -ap_select 0 -max_tries 50 -block_size 1024 -mem_dump /ext/swd_scripts/flash.bin 0x08000000 0x100000 2 -beep 1 -message 0 "Reading finished" dialog diff --git a/assets/resources/swd_scripts/goto_test.swd b/assets/resources/swd_scripts/goto_test.swd deleted file mode 100644 index 680285653b..0000000000 --- a/assets/resources/swd_scripts/goto_test.swd +++ /dev/null @@ -1,7 +0,0 @@ -beep 1 -goto l2 -.label l1 -beep 0 -.label l2 -beep 1 -goto l1 diff --git a/assets/resources/swd_scripts/halt.swd b/assets/resources/swd_scripts/halt.swd deleted file mode 100644 index 6aad4c194b..0000000000 --- a/assets/resources/swd_scripts/halt.swd +++ /dev/null @@ -1,11 +0,0 @@ - -# make sure errors do not cause a script abort -errors ignore - -message 0 "HAMMER TIME! Trying to halt CPU" -ap_select 0 - -# loop writing the halt bits -.label l1 -mem_write 0xE000EDF0 0xA05F0003 -goto l1 diff --git a/assets/resources/swd_scripts/reset.swd b/assets/resources/swd_scripts/reset.swd deleted file mode 100644 index 1872757fb4..0000000000 --- a/assets/resources/swd_scripts/reset.swd +++ /dev/null @@ -1,8 +0,0 @@ -errors ignore -status 0 -message 0 "HAMMER TIME! Try to halt the CPU" -.label l1 -ap_select 0 -mem_write 0xE000EDF0 0xA05F0001 -mem_write 0xE000ED0C 0x05FA0004 -goto l1 diff --git a/assets/resources/swd_scripts/test_write.swd b/assets/resources/swd_scripts/test_write.swd deleted file mode 100644 index df69461fde..0000000000 --- a/assets/resources/swd_scripts/test_write.swd +++ /dev/null @@ -1,3 +0,0 @@ -mem_write 0x20002000 0xdeadbeef -mem_write 0xE000EDF0 0xA05F0001 -mem_write 0xE000EDF0 0xA05F0007 From 78af3214c3f551072ebdffacaca4d8b56b4f89be Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:05:23 +0400 Subject: [PATCH 027/111] api 44 --- targets/f7/api_symbols.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 863ae3ef0f..365faafb88 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,41.0,, +Version,+,44.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, From c8c191c0a464fb4b747d779e09eefa02c856c5cc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:09:01 +0400 Subject: [PATCH 028/111] fix ir assets --- .../resources/infrared/assets/fans.ir | 2105 +++++++++++++++++ .../resources/infrared/assets/projectors.ir | 1497 ++++++++++++ 2 files changed, 3602 insertions(+) create mode 100644 applications/main/infrared/resources/infrared/assets/fans.ir create mode 100644 applications/main/infrared/resources/infrared/assets/projectors.ir diff --git a/applications/main/infrared/resources/infrared/assets/fans.ir b/applications/main/infrared/resources/infrared/assets/fans.ir new file mode 100644 index 0000000000..938bc96a60 --- /dev/null +++ b/applications/main/infrared/resources/infrared/assets/fans.ir @@ -0,0 +1,2105 @@ +Filetype: IR library file +Version: 1 +#Last Updated 1st Oct, 2023 +#Last Checked 1st Oct, 2023 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1301 370 1299 371 558 1107 1301 371 1272 398 557 1108 562 1107 562 1107 561 1108 560 1110 559 1110 1272 7254 1269 423 1244 425 530 1140 1242 427 1241 428 527 1142 527 1142 527 1142 527 1142 527 1142 527 1142 1241 7285 1241 428 1241 428 528 1142 1240 428 1241 428 528 1142 527 1142 527 1142 527 1142 527 1142 527 1142 1240 7284 1240 428 1241 429 527 1142 1241 429 1240 429 527 1142 527 1142 528 1142 527 1142 527 1142 527 1142 1240 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1268 400 1267 424 505 1164 1243 426 1242 427 504 1166 503 1166 503 1166 528 1141 1242 428 503 1166 503 8024 1241 427 1241 428 528 1141 1241 428 1241 428 503 1166 503 1167 502 1167 527 1142 1241 428 503 1166 503 8024 1241 427 1242 427 504 1167 1241 428 1241 428 503 1167 503 1167 503 1166 528 1142 1242 428 503 1167 502 8024 1241 428 1241 428 503 1167 1241 428 1241 428 503 1167 503 1167 503 1167 503 1167 1241 428 528 1142 503 8025 1240 429 1240 429 502 1167 1241 429 1240 429 502 1167 503 1167 503 1167 502 1167 1240 429 502 1168 501 8026 1239 430 1239 430 501 1169 1238 431 1238 430 501 1170 524 1170 475 1171 499 1170 1237 456 475 1195 474 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1242 425 1242 401 554 1140 1242 426 1243 428 504 1165 504 1167 1240 427 530 1141 530 1139 504 1167 527 7998 1241 427 1242 427 503 1166 1241 427 1242 428 529 1141 503 1167 1239 429 528 1141 503 1168 529 1140 528 7999 1240 428 1241 427 504 1167 1240 429 1239 429 528 1142 502 1167 1241 428 503 1167 503 1166 528 1142 528 7999 1240 429 1240 429 501 1168 1240 428 1242 427 528 1143 527 1142 1241 428 503 1167 502 1167 528 1142 502 8026 1239 428 1242 428 528 1142 1215 455 1240 427 504 1169 500 1168 1241 427 503 1168 527 1141 504 1166 530 7999 1239 429 1240 429 503 1167 1240 430 1239 430 502 1168 502 1168 1238 430 503 1169 526 1142 503 1167 502 8026 1239 429 1240 429 502 1168 1239 430 1241 428 502 1168 502 1168 1239 429 502 1167 503 1167 502 1168 502 8025 1239 428 1241 429 527 1143 1239 428 1241 429 502 1168 526 1142 1242 428 502 1167 502 1168 502 1167 502 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9086 4339 707 443 681 445 681 422 704 445 681 445 681 445 681 445 681 445 656 1563 683 1563 682 1563 707 1539 706 1562 682 1562 682 1564 680 1565 679 1566 679 448 679 1567 678 448 679 448 679 448 679 1566 679 448 679 448 678 1566 679 448 678 1566 678 1566 678 1566 678 448 678 1566 679 39846 9058 2132 679 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9083 4342 705 446 679 447 679 447 680 447 680 447 679 425 702 447 679 447 654 1564 681 1564 680 1565 680 1591 680 1565 680 1565 679 1565 679 1566 678 1568 677 1569 676 1569 676 451 676 450 677 450 677 1569 676 451 676 451 675 450 677 450 677 1570 675 1570 675 1569 676 451 676 1569 676 39854 9056 2136 677 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9267 4339 708 443 682 445 682 422 734 416 711 416 711 416 711 415 712 415 658 1563 682 1564 682 1565 681 1587 684 1562 683 1563 683 1563 682 1564 681 1565 680 447 679 447 679 1566 679 1566 680 447 680 447 680 447 680 447 680 1566 679 1566 679 447 680 447 680 1566 679 1566 679 1566 679 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9085 4343 705 446 679 447 680 447 680 447 679 425 701 447 679 423 703 446 655 1563 682 1563 681 1565 679 1589 681 1564 680 1563 680 1564 679 1566 678 449 677 450 676 1568 677 1568 677 1569 676 450 676 450 676 450 677 1568 677 1568 677 450 676 450 676 450 677 1568 676 1568 677 1568 676 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1284 419 1288 442 417 1258 1288 415 1282 448 411 1264 443 1258 439 1263 444 1258 1288 415 444 1258 439 8679 1284 419 1288 441 407 1267 1289 441 1256 447 412 1262 435 1267 440 1262 435 1293 1263 413 435 1267 440 8679 1284 418 1289 441 407 1267 1289 414 1283 447 412 1262 435 1267 440 1261 436 1267 1289 413 435 1267 440 8678 1285 418 1289 440 408 1267 1289 440 1257 447 412 1262 435 1267 440 1262 435 1268 1288 414 434 1268 439 8679 1284 418 1289 440 408 1267 1289 413 1284 445 414 1261 436 1266 441 1260 437 1266 1290 412 436 1266 441 8676 1287 415 1282 448 411 1264 1282 421 1286 443 416 1258 439 1263 434 1267 440 1262 1284 419 440 1262 435 8683 1280 422 1285 444 415 1287 1259 444 1253 449 410 1292 415 1285 412 1264 443 1259 1287 416 443 1259 438 8680 1284 420 1287 414 434 1268 1288 415 1282 447 412 1289 408 1267 440 1262 435 1267 1289 414 434 1268 439 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1283 420 1287 443 416 1259 1287 443 1254 449 410 1292 415 1260 437 1266 1290 439 409 1266 442 1261 436 8684 1290 413 1284 446 413 1263 1283 447 1261 442 417 1258 439 1263 444 1259 1287 442 417 1259 438 1264 443 8677 1287 416 1281 449 410 1265 1281 449 1259 444 415 1260 437 1265 442 1260 1286 443 416 1260 437 1265 442 8676 1288 415 1282 447 412 1263 1283 447 1261 442 417 1257 440 1263 434 1268 1288 441 418 1257 440 1263 434 8685 1289 414 1283 447 412 1263 1283 447 1261 442 417 1258 439 1263 445 1258 1288 442 417 1258 439 1264 444 8676 1288 415 1282 448 411 1264 1282 448 1260 444 415 1259 438 1264 443 1260 1286 417 442 1260 437 1265 442 8677 1286 416 1281 449 410 1265 1281 422 1285 444 415 1260 437 1265 442 1260 1286 417 442 1260 437 1265 442 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1281 422 1285 444 415 1260 1286 444 1263 439 409 1266 441 1261 1285 445 414 1261 436 1266 441 1261 436 8682 1281 421 1286 444 415 1260 1286 445 1262 439 409 1266 441 1261 1285 445 414 1261 436 1266 441 1260 437 8683 1280 422 1286 444 415 1261 1285 445 1262 440 408 1267 440 1262 1284 446 413 1262 435 1268 439 1262 435 8684 1279 423 1284 445 414 1261 1285 445 1263 439 409 1265 442 1260 1286 444 415 1260 437 1265 442 1259 438 8682 1281 421 1286 443 416 1259 1287 443 1254 448 411 1264 443 1259 1287 443 416 1259 438 1265 442 1260 437 8682 1281 421 1286 444 415 1260 1286 444 1253 450 409 1266 441 1261 1285 445 414 1261 436 1266 441 1261 436 8683 1290 413 1284 445 414 1262 1283 446 1261 441 407 1294 413 1262 1284 420 439 1263 434 1267 440 1262 435 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1294 371 1351 322 443 1200 1295 346 1297 373 444 1224 445 1198 1300 345 498 1170 473 1171 496 1173 471 8057 1295 372 1297 347 444 1224 1296 347 1296 349 494 1172 470 1174 1293 376 469 1174 469 1200 468 1175 468 8082 1293 351 1318 349 468 1175 1292 377 1294 349 468 1177 491 1175 1293 350 442 1226 468 1175 468 1201 467 8083 1293 350 1268 401 467 1175 1293 351 1318 350 467 1175 442 1226 1293 350 467 1177 466 1200 468 1176 441 8133 1292 351 1267 400 468 1175 1292 351 1292 376 466 1177 467 1202 1291 352 442 1202 465 1203 441 1201 442 8132 1267 376 1267 402 440 1202 1266 377 1290 379 439 1226 416 1253 1241 402 415 1228 439 1229 414 1228 415 8161 1239 403 1240 404 439 1229 1240 403 1240 429 414 1229 414 1231 1264 404 413 1229 414 1255 413 1229 413 8161 1238 404 1239 405 437 1230 1239 404 1238 430 412 1230 412 1232 1262 405 412 1231 412 1256 412 1231 412 8162 1238 406 1237 406 411 1258 1237 406 1237 431 412 1256 387 1256 1213 456 386 1256 386 1257 412 1256 386 8164 1237 431 1212 431 386 1283 1212 431 1212 432 411 1257 386 1257 1211 457 386 1257 386 1258 411 1258 385 8164 1212 431 1212 432 411 1256 1213 431 1236 433 386 1256 387 1257 1238 430 386 1257 386 1282 386 1256 386 +#ON/Speed_up +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1330 364 1385 320 523 1158 1332 365 1329 364 479 1213 481 1213 481 1213 481 1213 481 1213 1332 390 480 8072 1358 362 1329 364 478 1216 1328 366 1328 367 476 1218 476 1218 476 1218 476 1218 476 1218 1328 367 476 8103 1328 367 1327 367 476 1218 1327 367 1327 367 476 1218 476 1218 476 1218 476 1218 476 1218 1327 367 476 8103 1327 367 1327 367 476 1218 1327 368 1326 367 476 1218 476 1219 475 1219 475 1219 475 1219 1326 368 475 8104 1327 368 1326 368 475 1219 1327 368 1326 368 475 1219 475 1219 475 1219 475 1219 475 1219 1326 368 475 8105 1326 368 1326 368 475 1219 1327 368 1326 368 475 1220 474 1220 474 1220 475 1220 474 1220 1325 368 475 8105 1326 369 1325 369 474 1220 1326 369 1325 369 474 1220 474 1220 474 1220 474 1220 474 1220 1326 369 474 8106 1325 369 1325 369 474 1220 1325 369 1326 369 474 1220 474 1220 474 1220 474 1220 474 1221 1324 370 473 8132 1273 422 1272 422 446 1248 1272 422 1297 397 421 1275 419 1300 419 1276 419 1275 419 1250 1297 398 445 8135 1297 397 1297 397 447 1247 1298 397 1297 397 447 1247 447 1247 447 1247 447 1247 447 1247 1298 397 447 8134 1298 397 1298 397 447 1248 1297 397 1297 397 447 1248 446 1248 446 1248 447 1248 447 1248 1297 397 447 8133 1298 397 1297 397 447 1248 1297 397 1297 397 447 1248 446 1248 447 1248 446 1248 446 1248 1297 398 446 8134 1297 398 1296 398 446 1248 1297 398 1296 398 446 1248 446 1248 446 1248 446 1248 446 1248 1296 398 446 8134 1295 398 1296 398 446 1248 1296 398 1296 398 446 1249 445 1249 445 1249 445 1249 445 1249 1295 399 445 8134 1295 399 1295 399 445 1249 1295 399 1295 399 445 1249 445 1249 445 1250 444 1249 445 1249 1295 399 445 8135 1295 400 1294 400 444 1250 1294 400 1295 400 444 1250 444 1250 444 1250 444 1250 444 1250 1294 401 443 8137 1293 401 1294 401 442 1251 1294 401 1293 401 442 1253 442 1252 442 1252 442 1252 442 1252 1293 427 417 8140 1292 426 1268 427 417 1277 1243 452 1267 427 417 1278 417 1277 417 1278 417 1277 417 1277 1268 427 417 8163 1242 452 1242 452 417 1278 1242 453 1241 453 391 1303 391 1303 392 1303 391 1303 391 1303 1242 453 415 8165 1241 453 1241 453 391 1304 1241 453 1241 454 390 1305 389 1304 390 1304 390 1305 389 1305 1240 480 364 8216 1214 480 1214 480 363 1331 1214 480 1214 480 364 1331 363 1331 363 1331 363 1331 363 1331 1214 481 363 8218 1213 481 1213 481 363 1333 1212 508 1186 508 335 1359 335 1359 335 1359 335 1359 335 1360 1186 508 335 8247 1184 509 1185 535 307 1387 1159 536 1158 589 253 1389 306 1415 278 1416 252 1442 199 1575 1052 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1318 346 1298 324 494 1201 1291 345 1297 375 466 1174 470 1200 442 1201 467 1201 441 1201 466 1204 1265 7229 1373 322 1266 375 466 1202 1267 325 1318 378 465 1200 442 1202 465 1202 442 1201 441 1202 467 1171 1297 7281 1268 325 1399 323 439 1200 1293 348 1351 322 441 1202 522 1145 498 1172 439 1204 496 1171 471 1148 1346 7227 1323 322 1266 325 574 1145 1322 322 1347 322 496 1115 528 1171 497 1145 497 1148 521 1145 497 1145 1291 7285 1322 322 1321 323 521 1147 1321 322 1346 322 496 1172 470 1147 522 1145 498 1146 519 1149 497 1171 1297 7252 1323 322 1321 323 520 1144 1323 322 1343 323 498 1144 498 1147 522 1146 496 1146 571 1123 471 1145 1323 7229 1345 322 1321 322 570 1097 1322 323 1320 323 521 1146 496 1146 519 1148 498 1145 498 1149 520 1145 1322 7229 1347 323 1320 322 464 1202 1323 323 1320 322 522 1145 497 1145 466 1228 471 1116 527 1147 522 1147 1321 7226 1348 322 1266 375 442 1202 1292 348 1295 375 523 1146 442 1201 442 1202 466 1200 442 1200 467 1202 1268 7281 1292 375 1270 346 471 1201 1293 373 1270 374 523 1145 445 1199 443 1200 469 1199 443 1200 524 1144 1269 7258 1292 374 1269 374 468 1200 1269 374 1269 400 443 1200 443 1201 467 1201 498 1146 497 1172 497 1145 1324 +# +name: Power +type: parsed +protocol: NECext +address: 4B 14 00 00 +command: 80 7F 00 00 +# +name: Mode +type: parsed +protocol: NECext +address: 4B 14 00 00 +command: 20 DF 00 00 +# +name: Rotate +type: parsed +protocol: NECext +address: 4B 14 00 00 +command: C0 3F 00 00 +# +name: Timer +type: parsed +protocol: NECext +address: 4B 14 00 00 +command: A0 5F 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9137 4430 630 1594 661 464 659 1625 630 1624 631 1625 630 1596 659 1596 660 1624 631 492 631 1624 632 1623 632 1593 662 1597 658 1624 631 1625 630 1595 660 491 632 1595 661 1595 660 1624 631 1624 631 1596 660 1624 631 1624 631 1593 663 435 689 491 632 434 690 491 632 491 632 463 661 490 634 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9107 4400 660 1622 633 464 659 1623 632 1596 659 1622 633 1622 633 1623 632 1622 633 493 630 1593 662 1595 660 1623 632 1593 662 1623 632 1596 659 1623 632 1622 633 420 703 1594 661 424 699 1592 663 1594 661 1595 660 1597 658 423 700 1623 632 489 634 1593 662 461 662 431 692 488 635 489 634 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9106 4428 632 1595 660 463 660 1595 660 1622 633 1593 662 1622 633 1622 633 1595 660 490 633 1595 660 1595 660 1592 663 1593 662 1623 632 1623 632 1594 661 1623 632 1595 660 1594 661 1596 659 489 634 1595 660 1595 660 1596 659 463 660 490 633 462 662 422 701 1595 660 464 659 490 633 428 695 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9136 4401 659 1624 631 463 660 1624 631 1593 663 1623 632 1624 632 1596 660 1594 661 464 659 1597 658 1593 662 1623 632 1595 660 1623 632 1624 631 1594 662 489 634 1623 633 431 693 1594 662 1623 633 1594 662 1595 661 1593 663 1592 663 461 663 1623 632 463 660 434 689 463 660 463 661 426 698 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9142 4400 661 1593 662 491 632 1624 632 1598 657 1594 662 1594 662 1596 660 1596 660 490 634 1596 660 1596 660 1624 631 1594 662 1623 632 1594 662 1624 632 1624 632 491 632 1624 632 1595 661 1624 631 1624 632 1594 662 1624 631 491 633 1624 632 464 659 492 632 432 692 430 694 464 660 432 692 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9134 4428 632 1597 658 355 768 1624 631 1623 632 1623 632 1597 658 1623 632 1622 633 463 660 1595 660 1622 633 1596 659 1595 660 1622 633 1623 632 1596 659 1622 633 1623 632 436 687 1623 633 1622 633 1622 633 1622 633 1598 657 434 689 489 634 1622 633 466 657 489 634 490 633 490 633 440 684 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8980 4377 620 1599 620 489 620 488 621 488 621 488 622 488 621 488 621 488 621 488 621 1597 622 1598 621 1598 621 1597 622 1597 622 1597 622 1597 622 1597 621 1598 646 463 646 464 645 464 645 465 644 466 643 466 643 466 643 466 643 1575 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 466 643 466 643 466 643 466 643 466 643 466 643 466 644 466 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9011 4375 620 1598 621 462 647 488 621 488 621 488 676 434 621 488 621 489 620 488 621 1598 621 1598 621 1598 621 1598 621 1597 622 1598 621 1597 622 1597 622 1598 621 488 621 489 644 1576 644 466 643 466 643 466 643 466 643 466 644 1576 643 1576 643 466 643 1576 643 1576 643 1576 643 1576 643 1576 643 466 644 466 643 1576 643 466 644 466 643 466 644 466 643 466 643 1575 644 1576 643 466 643 1575 643 1576 643 1576 643 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2262 692 881 585 883 583 903 564 903 1294 882 584 881 584 882 559 820 673 895 1300 882 611 881 584 882 584 882 584 882 585 881 584 881 584 882 585 880 1317 879 586 829 1371 827 640 825 51208 2245 690 772 1424 831 50893 2240 691 772 1429 771 50906 2244 690 773 1400 772 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2264 668 770 721 745 694 772 694 796 1401 826 639 771 694 771 693 772 694 771 1426 798 668 769 1429 795 670 795 1404 794 672 794 1405 767 1431 767 1430 794 672 794 1404 794 1404 794 1404 766 50878 2261 645 817 1405 794 50857 2238 668 795 1428 769 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2264 642 823 669 797 643 822 644 822 1376 822 642 799 668 797 669 823 645 820 1401 796 670 794 1379 792 700 793 1381 790 1407 793 674 791 700 793 1406 792 673 793 673 792 673 793 673 792 50785 2262 666 796 1401 797 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2323 611 796 669 800 664 802 692 773 1424 774 691 801 664 774 691 774 692 773 1423 800 668 822 667 798 1376 769 1427 771 696 769 696 770 1429 795 1404 794 672 794 1404 794 671 795 1404 794 51269 2214 691 769 1427 796 +# +#ON/SPEED +name: Power +type: parsed +protocol: NECext +address: 00 FC 00 00 +command: 84 7B 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1285 426 1258 427 414 1267 1248 436 1258 427 414 1267 406 1276 408 1248 435 1247 437 1271 413 1244 1282 7286 1279 432 1252 433 409 1273 1253 431 1253 432 410 1273 411 1271 413 1243 440 1241 442 1240 433 1275 1251 7292 1283 427 1257 428 413 1268 1258 427 1257 428 414 1268 416 1240 433 1249 434 1248 435 1246 438 1245 1281 7287 1278 406 1278 434 408 1274 1252 432 1252 433 409 1274 410 1246 438 1244 440 1243 440 1241 443 1240 1275 7294 1281 402 1282 430 412 1270 1256 428 1256 429 412 1243 441 1241 443 1240 433 1249 434 1247 436 1246 1280 7288 1277 433 1251 434 408 1248 1278 432 1251 433 409 1273 411 1245 439 1244 440 1242 442 1240 433 1249 1277 7292 1283 400 1304 406 435 1246 1248 436 1279 405 436 1246 406 1249 434 1247 436 1245 438 1244 440 1242 1284 7285 1280 430 1285 400 442 1241 1285 399 1285 400 441 1240 444 1212 440 1241 443 1240 433 1249 434 1273 1284 7259 1306 404 1280 405 436 1245 1281 403 1281 404 438 1244 440 1216 468 1214 438 1244 440 1242 442 1241 1305 7262 1303 408 1276 407 434 1249 1277 407 1276 407 435 1248 436 1220 464 1219 464 1217 466 1242 441 1214 1312 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1280 431 1252 431 410 1244 1281 430 1253 430 411 1270 413 1242 441 1240 433 1249 434 1247 1278 433 408 8129 1277 433 1250 433 408 1274 1251 432 1251 433 409 1273 410 1271 412 1269 414 1241 432 1250 1275 435 406 8133 1283 426 1257 427 414 1267 1248 436 1247 436 405 1276 407 1248 435 1247 436 1245 438 1244 1281 429 412 8126 1280 430 1253 430 411 1270 1255 429 1254 429 412 1269 414 1241 432 1276 407 1248 435 1247 1278 432 410 8129 1277 432 1251 433 408 1273 1252 431 1252 431 411 1272 411 1269 414 1241 442 1240 433 1275 1250 434 408 8130 1276 434 1249 434 407 1275 1250 433 1250 433 408 1274 409 1272 411 1244 439 1268 415 1240 1275 436 405 8133 1283 427 1256 427 414 1267 1248 436 1258 426 405 1277 406 1274 409 1273 410 1245 438 1244 1281 429 412 8125 1281 429 1254 429 412 1269 1256 428 1255 428 413 1268 415 1266 407 1248 435 1247 436 1245 1280 430 411 8127 1279 431 1252 431 411 1271 1254 430 1253 430 412 1270 413 1268 415 1240 433 1275 408 1247 1278 432 410 8128 1278 432 1251 432 410 1272 1253 431 1252 431 410 1271 412 1242 441 1267 406 1250 433 1248 1277 433 409 8129 1277 433 1250 433 409 1272 1253 431 1252 431 410 1271 412 1242 441 1241 432 1250 433 1248 1277 433 409 8129 1277 433 1250 434 408 1246 1279 432 1251 432 409 1245 438 1244 439 1242 441 1240 433 1249 1276 434 407 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2356 608 934 1216 825 1320 826 1348 793 646 824 673 792 667 792 663 792 720 793 692 793 687 818 657 817 652 817 649 816 1310 815 1306 815 102511 2320 668 819 1332 817 1328 817 1325 815 655 815 650 815 645 815 640 815 699 814 671 814 666 814 661 814 657 813 652 813 1337 788 1333 787 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 390 9025 2296 666 821 1329 821 1324 821 1319 821 647 822 642 822 666 793 661 793 1385 793 691 819 660 819 655 818 652 816 1314 815 644 815 1305 815 101869 2318 668 818 1331 817 1328 816 1323 816 654 815 649 815 645 814 640 814 1363 815 670 815 665 814 660 815 655 814 1316 814 645 814 1306 814 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2346 612 875 1275 931 1214 929 1211 875 594 875 588 876 582 822 633 821 691 848 1303 847 659 819 1321 817 652 816 1314 815 1310 814 640 814 101247 2320 667 819 1330 818 1327 816 1324 815 654 815 649 815 644 815 639 815 698 814 1335 815 665 814 1325 814 655 814 1315 815 1310 815 640 814 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2313 646 841 1308 841 1303 841 1298 841 626 843 621 842 614 819 634 820 1359 844 1333 817 1328 815 1324 814 1321 812 1317 812 647 812 1308 811 99132 2317 670 816 1333 815 1330 813 1326 813 657 812 652 812 647 812 642 812 1366 812 1338 811 1332 812 1328 812 1323 811 1317 813 647 812 1307 812 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3533 1663 476 420 450 1234 508 421 449 394 477 420 450 420 474 387 459 421 475 396 558 385 432 386 484 386 485 386 434 1263 479 419 447 395 475 394 476 393 478 393 478 392 478 1264 477 393 478 1264 478 1266 500 387 483 395 475 1268 473 397 474 397 474 397 474 397 474 1268 473 397 474 397 474 397 474 397 474 1268 472 1270 472 398 473 398 473 1269 473 398 473 398 473 397 474 1268 473 1269 473 398 473 398 473 1269 472 398 473 1269 472 398 473 1269 472 398 473 1269 472 398 473 74625 3552 1673 473 397 472 1270 472 399 471 399 471 400 470 400 470 401 470 401 470 401 469 401 470 402 469 402 469 402 469 1272 471 400 470 400 470 401 469 402 468 427 444 427 444 1273 471 400 470 1272 470 1271 471 400 470 401 469 1273 470 401 469 427 443 428 442 428 417 1300 469 426 443 428 417 453 442 428 417 1325 443 1274 468 427 443 428 417 1325 443 427 417 454 416 454 416 1326 417 1325 442 427 417 453 417 1325 417 453 417 1325 418 453 416 1326 416 453 417 1326 416 454 416 +#Timer UP +name: Timer +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 07 00 00 00 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1367 345 1338 347 491 1170 1361 349 1334 350 488 1198 489 1198 488 1199 488 1199 487 1200 1334 352 486 7953 1333 353 1332 353 486 1200 1333 353 1332 353 486 1200 486 1201 486 1200 486 1200 487 1201 1332 353 486 7953 1332 353 1333 353 485 1201 1332 353 1332 353 485 1201 485 1201 485 1201 486 1200 486 1200 1333 353 486 7953 1333 353 1332 354 485 1201 1332 353 1332 353 485 1201 486 1201 485 1201 485 1201 486 1201 1332 354 484 7954 1332 353 1332 353 485 1201 1332 354 1331 354 485 1201 485 1201 486 1201 486 1201 486 1201 1333 354 484 7954 1332 354 1331 354 485 1201 1332 355 1331 354 484 1201 485 1201 486 1202 485 1201 486 1201 1331 355 484 7953 1332 354 1331 354 484 1201 1332 355 1330 355 483 1202 484 1201 486 1201 485 1202 484 1202 1331 355 484 7954 1331 354 1331 354 484 1202 1331 355 1330 354 484 1202 485 1201 485 1202 484 1202 485 1202 1330 355 484 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1367 335 1349 346 492 1166 1365 346 1338 347 491 1170 516 1172 514 1173 1359 349 489 1197 489 1198 488 7950 1334 352 1332 352 486 1200 1333 352 1333 352 486 1200 486 1200 486 1200 1333 352 486 1200 486 1200 487 7953 1332 352 1333 353 485 1200 1333 353 1332 353 485 1200 486 1200 486 1200 1333 353 485 1200 486 1200 487 7951 1333 353 1332 352 486 1200 1333 353 1332 353 486 1200 486 1200 486 1200 1333 353 486 1200 486 1200 486 7952 1333 352 1333 353 485 1200 1333 353 1332 353 486 1200 486 1200 486 1200 1333 353 485 1200 486 1200 486 7952 1332 353 1332 353 485 1200 1333 353 1331 353 485 1200 486 1201 485 1201 1332 353 485 1201 485 1201 486 7952 1331 353 1332 354 484 1201 1332 354 1331 354 484 1201 485 1202 484 1201 1332 354 484 1202 484 1202 485 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1337 374 1311 374 464 1194 1339 373 1340 345 493 1165 521 1165 1365 347 492 1167 519 1168 518 1168 518 7920 1363 347 1336 349 489 1197 1334 352 1333 353 485 1200 486 1200 1333 352 486 1200 486 1200 486 1200 486 +# +name: Power +type: parsed +protocol: NECext +address: BA F0 00 00 +command: 03 FC 00 00 +# +name: Speed_up +type: parsed +protocol: NECext +address: BA F0 00 00 +command: 50 AF 00 00 +# +name: Speed_dn +type: parsed +protocol: NECext +address: BA F0 00 00 +command: 41 BE 00 00 +# +name: Timer +type: parsed +protocol: NECext +address: BA F0 00 00 +command: 40 BF 00 00 +# +name: Power +type: parsed +protocol: NECext +address: BA F0 00 00 +command: 53 AC 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9069 4477 591 1695 622 528 625 525 618 505 617 532 621 527 626 497 625 525 618 532 621 1661 615 1640 595 1688 599 1684 613 1643 592 1691 626 1656 620 1636 620 1663 624 526 617 505 628 523 620 529 624 525 618 504 618 531 622 527 616 1643 623 1662 625 1658 618 1637 598 1685 622 1659 627 1655 570 1685 622 528 625 523 620 502 620 529 624 524 619 503 588 561 623 526 617 1667 619 1639 627 1654 622 1660 616 1638 617 1664 622 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9014 4522 597 1687 610 512 590 559 594 555 619 503 599 550 593 556 597 551 613 509 593 1688 588 1692 615 1639 596 1684 592 1689 587 1666 590 1691 595 1685 591 1689 566 1689 597 551 592 557 638 485 596 553 590 558 595 527 595 554 589 559 594 1686 611 1643 592 1688 588 1692 646 1607 597 1684 592 1688 619 1634 591 558 595 553 590 532 590 558 595 553 590 559 615 506 596 553 590 1693 594 1685 571 1683 593 1686 590 1690 565 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9066 4476 623 1662 625 525 618 504 587 561 623 526 617 531 622 501 621 528 625 524 619 1661 564 1692 625 1655 621 1660 565 1690 627 1654 622 1659 586 1668 618 1663 623 1657 619 1636 620 529 624 524 619 502 620 529 624 524 619 529 614 509 624 525 618 1663 613 1640 626 1655 621 1659 617 1637 619 1662 625 1656 620 1633 623 527 626 523 620 529 614 508 625 524 619 530 623 499 593 557 617 1663 623 1657 619 1635 621 1660 616 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9038 4530 589 1665 590 559 594 555 598 551 613 510 592 558 595 553 590 532 590 559 594 1686 590 1690 617 1637 598 1683 593 1687 620 1634 591 1691 595 1685 622 1632 593 557 596 1684 592 530 592 557 596 552 591 558 616 506 596 553 590 1690 596 526 596 1684 592 1688 588 1692 563 1690 596 1684 592 1687 620 503 589 1691 595 553 621 501 591 558 595 553 590 532 590 558 595 1689 597 551 623 1630 595 1686 590 1689 587 1666 589 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4609 4601 539 1515 545 1511 539 1518 542 1514 546 1510 540 1517 543 2559 541 1515 545 1511 539 2563 537 1519 541 2561 539 1516 544 1512 548 2554 546 1510 540 1515 4604 4605 545 1509 541 1515 545 1538 512 1518 542 1514 546 1511 539 2563 537 1519 541 1515 545 2557 543 1513 547 2555 545 1510 540 1516 544 2559 541 1514 546 13748 9234 2298 545 52033 9228 2304 539 52041 9230 2303 540 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4605 4603 547 1508 541 1515 545 1511 539 1518 542 1514 546 1511 539 2562 538 1518 542 1515 545 1511 538 2564 547 1509 541 1517 543 1513 547 2555 545 2556 544 1512 4607 4603 547 1508 542 1515 545 1511 539 1518 542 1514 546 1511 538 2564 546 1510 539 1517 543 1514 546 2557 543 1513 547 1510 540 1517 543 2559 541 2561 539 13755 9237 2297 546 52031 9229 2306 547 52033 9237 2297 546 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4608 4604 546 1509 541 1516 544 1513 547 1509 540 1516 544 1513 547 2555 545 1512 537 2565 545 1511 539 1518 542 2561 539 1516 544 1513 547 1510 539 2563 537 1517 4612 4599 541 1514 546 1511 539 1518 542 1514 546 1511 539 1518 542 2561 539 1518 542 2561 539 1517 543 1514 546 2557 543 1513 547 1510 540 1516 544 2559 541 13755 9237 2298 545 52044 9237 2298 545 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4606 4604 546 1509 541 1516 544 1512 548 1509 541 1516 544 1512 548 2554 546 1510 540 1517 543 2559 541 1515 545 2557 543 2558 542 1514 546 1511 539 1517 543 1512 4607 4601 539 1516 544 1513 547 1509 541 1516 544 1512 548 1508 542 2561 539 1516 544 1513 547 2555 545 1511 538 2589 522 2555 545 1511 539 1518 542 1515 545 13389 9233 2301 542 51673 9227 2306 547 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1273 401 1272 426 416 1232 1273 427 1247 426 405 1242 442 1231 442 1231 1274 426 405 1242 431 1242 431 7968 1274 400 1273 426 416 1232 1273 426 1247 426 405 1241 432 1241 432 1241 1275 425 406 1267 406 1240 433 7967 1275 398 1275 424 407 1240 1276 424 1249 424 407 1239 434 1238 435 1238 1278 422 409 1237 436 1237 436 7963 1279 394 1279 420 411 1262 1243 430 1243 430 412 1234 439 1234 439 1233 1272 428 414 1232 441 1232 441 7958 1273 426 1248 426 405 1241 1275 426 1248 425 406 1266 407 1240 433 1240 1276 424 407 1265 408 1238 435 7965 1277 422 1251 423 408 1238 1278 423 1250 423 408 1264 409 1238 435 1264 1251 422 409 1264 409 1237 436 7964 1278 422 1251 422 409 1264 1251 422 1251 422 409 1263 410 1262 411 1262 1254 420 411 1262 411 1235 438 7961 1270 429 1244 429 413 1260 1245 428 1245 427 415 1258 415 1231 432 1241 1274 425 406 1267 406 1240 433 7965 1277 423 1250 423 408 1265 1250 423 1250 423 408 1264 409 1237 436 1237 1278 421 410 1262 411 1262 411 7961 1270 429 1244 429 413 1260 1245 429 1244 429 413 1260 413 1233 440 1259 1246 427 415 1259 414 1258 415 7959 1272 427 1246 427 415 1259 1246 427 1246 427 415 1257 406 1267 406 1241 1274 425 406 1266 407 1266 407 7965 1277 422 1251 422 409 1264 1251 421 1252 421 410 1262 411 1261 412 1235 1270 429 413 1260 413 1233 440 7958 1273 426 1247 425 406 1267 1249 425 1248 424 407 1266 407 1265 408 1265 1250 423 408 1264 409 1263 410 7962 1280 420 1253 420 411 1261 1244 430 1243 429 413 1259 414 1232 441 1231 1274 427 415 1257 406 1240 433 7965 1277 423 1250 423 408 1264 1251 422 1251 422 409 1262 411 1235 438 1235 1280 420 411 1260 413 1232 441 7957 1274 426 1247 425 406 1266 1250 425 1248 424 407 1265 408 1238 435 1264 1251 423 408 1263 410 1236 437 +#Osc +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1338 374 1311 374 464 1194 1339 374 1311 374 464 1194 492 1194 492 1194 520 1165 1368 346 492 1167 519 7918 1364 347 1337 348 490 1195 1336 351 1333 352 486 1200 486 1201 485 1201 486 1201 1332 353 485 1201 485 7952 1332 353 1332 353 485 1200 1333 353 1332 353 485 1201 485 1201 485 1201 485 1201 1332 354 485 1201 485 7954 1331 354 1331 354 485 1201 1332 354 1331 354 485 1201 485 1202 485 1202 485 1202 1331 354 485 1201 485 7954 1331 354 1331 354 484 1202 1331 355 1330 355 483 1202 484 1203 483 1203 484 1203 1330 355 484 1203 483 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1280 394 1280 420 411 1236 1269 430 1254 419 412 1235 438 1234 439 1233 440 1233 440 1233 1272 427 415 8311 1279 395 1278 420 411 1236 1269 430 1243 430 412 1234 439 1234 439 1233 440 1233 440 1233 1272 427 415 8312 1277 396 1277 422 409 1264 1252 421 1252 421 410 1236 437 1235 438 1233 440 1259 414 1259 1246 427 415 8312 1277 422 1251 423 408 1264 1251 423 1250 423 408 1238 435 1237 436 1237 436 1263 410 1237 1278 421 410 8315 1274 425 1248 425 406 1240 1276 424 1249 424 407 1265 408 1238 435 1264 409 1237 436 1237 1279 421 410 8318 1271 402 1271 428 414 1233 1272 427 1246 427 415 1231 432 1241 432 1240 433 1240 433 1240 1276 424 407 8319 1271 429 1245 428 414 1233 1272 428 1245 427 415 1231 442 1231 432 1240 433 1240 433 1239 1277 423 408 8318 1271 401 1272 427 415 1232 1273 427 1246 426 405 1267 406 1240 433 1239 434 1238 435 1238 1278 422 409 8317 1272 401 1272 426 405 1242 1274 426 1248 425 406 1240 433 1239 434 1238 435 1238 435 1238 1278 421 410 8317 1272 401 1272 426 405 1242 1274 425 1249 425 406 1240 434 1239 434 1238 435 1238 435 1238 1278 421 410 8316 1273 400 1273 426 405 1241 1275 425 1248 425 406 1239 434 1239 434 1238 435 1238 435 1238 1278 421 410 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1341 395 1311 395 455 1220 1341 395 1311 396 454 1248 456 1248 457 1248 456 1247 457 1247 457 1247 1338 7184 1335 399 1307 399 450 1254 1307 399 1307 400 449 1255 450 1255 450 1254 450 1254 450 1254 450 1254 1307 8332 1306 401 1306 400 449 1256 1306 400 1306 401 449 1255 450 1255 449 1255 449 1255 449 1255 449 1255 1306 7214 1306 400 1307 400 450 1255 1306 400 1306 400 450 1255 449 1255 449 1255 449 1255 449 1255 449 1256 1306 8333 1306 401 1307 401 449 1256 1307 402 1306 402 448 1256 449 1256 448 1256 448 1256 448 1256 448 1256 1307 7215 1305 401 1306 402 448 1256 1306 402 1305 402 448 1257 447 1257 447 1257 447 1256 448 1256 448 1257 1305 8333 1304 402 1305 402 448 1257 1304 402 1304 403 447 1257 447 1257 448 1257 447 1257 447 1257 447 1257 1304 7216 1304 403 1303 403 446 1257 1304 403 1304 403 446 1258 447 1258 446 1257 447 1257 447 1258 447 1257 1304 8334 1304 402 1305 403 446 1257 1304 403 1303 403 447 1258 446 1257 448 1258 446 1258 446 1258 446 1258 1303 7217 1303 404 1303 404 445 1259 1302 404 1302 428 421 1259 446 1283 421 1259 445 1283 421 1283 421 1284 1278 8361 1277 429 1278 429 420 1284 1278 429 1277 429 420 1284 420 1284 420 1284 420 1284 420 1284 419 1285 1277 7243 1277 429 1277 429 421 1284 1277 429 1277 429 420 1284 420 1284 420 1284 420 1284 420 1284 420 1284 1277 8361 1277 430 1276 430 420 1285 1276 430 1277 430 419 1285 419 1285 419 1285 420 1284 420 1285 420 1285 1276 7244 1276 431 1276 430 419 1286 1276 430 1276 431 418 1286 418 1286 418 1286 418 1286 418 1285 419 1285 1276 8363 1275 431 1276 431 418 1286 1275 431 1276 432 417 1286 418 1286 418 1286 418 1286 419 1286 418 1286 1275 7246 1275 432 1275 432 417 1288 1274 457 1249 457 392 1312 392 1312 392 1289 415 1312 392 1312 392 1313 1249 8390 1248 458 1249 458 391 1313 1249 458 1248 458 391 1313 391 1314 390 1313 391 1314 390 1314 390 1314 1248 7273 1247 459 1223 484 390 1315 1247 460 1222 510 339 1365 364 1315 365 1339 390 1315 388 1341 339 1340 1222 8415 1222 484 1222 510 339 1365 1197 510 1197 510 339 1365 340 1365 339 1365 339 1365 339 1365 339 1365 1197 7324 1196 511 1196 511 338 1366 1196 511 1195 511 338 1366 338 1367 337 1367 337 1367 337 1367 337 1367 1195 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1341 368 1338 392 457 1219 1370 364 1341 364 485 1190 540 1164 513 1190 513 1191 512 1194 1365 366 481 8031 1338 370 1336 371 477 1226 1335 371 1335 371 477 1226 477 1226 477 1226 477 1226 477 1226 1334 371 477 9153 1334 371 1334 371 477 1226 1334 371 1335 371 477 1227 476 1227 476 1227 476 1227 476 1227 1334 372 476 8036 1334 372 1333 372 476 1227 1333 372 1333 372 476 1227 476 1227 476 1227 476 1227 476 1227 1334 373 475 9154 1334 373 1333 373 475 1228 1332 373 1332 373 475 1229 475 1228 475 1229 474 1229 474 1229 1331 374 474 8038 1331 374 1332 374 474 1229 1331 374 1331 398 450 1253 450 1253 450 1253 450 1253 450 1253 1307 398 450 9179 1307 398 1307 398 450 1253 1307 399 1306 398 450 1254 449 1254 449 1253 450 1254 449 1254 1306 398 450 8063 1306 398 1307 399 449 1254 1306 399 1306 399 449 1254 449 1254 449 1254 449 1254 449 1254 1307 399 449 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1372 339 1369 363 486 1190 1374 363 1344 363 486 1190 515 1191 513 1191 513 1192 1370 365 483 1220 484 8036 1339 371 1337 371 478 1227 1337 371 1337 371 478 1227 478 1227 478 1227 478 1227 1336 371 478 1227 478 9159 1336 372 1336 371 478 1227 1336 371 1336 372 477 1227 478 1227 477 1227 477 1227 1336 372 477 1228 477 8041 1335 372 1335 372 477 1227 1336 372 1335 372 477 1228 477 1228 477 1228 477 1228 1335 373 476 1228 477 9160 1335 373 1334 373 476 1228 1335 373 1334 373 476 1229 475 1229 476 1229 475 1229 1334 374 475 1229 475 8042 1334 375 1331 398 451 1230 1333 398 1309 398 451 1254 450 1254 451 1253 451 1253 1308 398 450 1254 450 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1342 366 1370 339 511 1191 1373 364 1343 364 511 1165 540 1165 514 1192 1371 364 484 1219 485 1220 484 8036 1339 370 1337 371 478 1227 1336 371 1336 371 478 1227 478 1227 478 1227 1336 372 477 1227 478 1227 478 9162 1336 372 1335 372 477 1228 1335 372 1335 373 476 1228 477 1228 477 1228 1336 372 477 1228 477 1228 476 8044 1336 372 1335 373 476 1228 1335 398 1309 398 451 1254 451 1254 451 1255 1308 400 449 1255 450 1256 449 9188 1306 403 1304 428 421 1260 1303 428 1279 429 419 1284 421 1284 420 1284 1279 429 420 1285 420 1284 420 8099 1276 457 1250 457 391 1313 1225 483 1224 484 364 1338 366 1338 392 1313 1225 484 364 1339 366 1339 390 9244 1225 509 1198 510 338 1366 1197 510 1197 537 311 1367 338 1393 311 1394 1169 591 256 11618 1170 539 1168 565 282 1395 1169 565 1142 538 310 1341 365 1312 419 1259 1305 400 449 1254 450 1255 449 9186 1307 400 1306 400 448 1255 1307 399 1307 400 448 1255 449 1255 449 1255 1307 400 448 1255 449 1255 449 8068 1306 400 1306 400 448 1256 1306 400 1306 401 447 1256 448 1256 448 1256 1306 401 447 1256 448 1257 447 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 170 535 1281 413 1276 418 436 1259 1275 423 1276 444 410 1258 431 1264 435 1260 439 1256 433 1262 437 1258 1276 6529 173 475 1280 414 1275 419 435 1260 1274 450 1249 419 435 1259 430 1265 434 1261 438 1257 432 1263 436 1259 1275 7177 1280 414 1275 445 410 1260 1274 450 1249 445 410 1259 430 1266 433 1262 437 1258 431 1264 435 1260 1274 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1280 415 1274 447 407 1288 1256 442 1247 448 406 1288 411 1284 405 1291 408 1287 412 1283 1251 444 411 8009 1282 413 1276 445 409 1286 1248 450 1249 446 408 1286 403 1292 407 1262 437 1285 404 1291 1253 442 413 8007 1274 446 1253 442 402 1292 1252 447 1252 442 402 1292 407 1288 411 1284 405 1264 435 1286 1248 447 407 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1278 444 1255 439 405 1289 1255 444 1255 439 405 1290 409 1259 1275 447 407 1288 411 1257 432 1263 436 8009 1282 440 1249 446 408 1286 1248 451 1248 446 408 1286 413 1256 1278 444 410 1284 405 1264 435 1261 438 8008 1283 439 1250 444 410 1284 1250 450 1249 445 409 1284 405 1265 1279 442 412 1282 407 1262 437 1258 431 8015 1276 445 1254 440 404 1292 1252 446 1253 441 403 1292 407 1288 1256 439 405 1290 409 1286 403 1292 407 8012 1279 416 1273 448 406 1289 1255 443 1246 449 405 1289 410 1285 1249 446 408 1287 412 1283 406 1289 410 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1278 417 1282 439 405 1290 1254 444 1255 439 405 1289 410 1285 404 1292 1252 442 412 1283 406 1289 410 8010 1280 414 1275 446 408 1287 1247 451 1248 446 408 1286 413 1282 407 1263 1281 440 404 1291 408 1261 438 8007 1273 421 1278 443 411 1284 1249 449 1250 444 410 1258 431 1291 408 1288 1256 439 405 1289 410 1286 403 8017 1273 421 1278 443 411 1284 1249 449 1250 444 410 1285 404 1291 408 1262 1282 440 404 1264 435 1260 439 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1282 413 1276 445 409 1260 1274 450 1249 445 410 1259 430 1265 434 1261 438 1258 1276 445 409 1285 404 8017 1274 420 1279 442 412 1283 1251 448 1251 443 411 1283 406 1290 409 1260 439 1256 1278 443 411 1284 405 8015 1276 418 1281 440 404 1291 1253 446 1253 440 404 1291 408 1287 412 1283 406 1263 1281 440 404 1291 408 8011 1280 414 1275 446 408 1287 1247 452 1247 447 407 1261 438 1283 406 1289 410 1259 1275 447 407 1287 412 8006 1275 446 1253 441 403 1292 1252 447 1252 442 402 1292 407 1288 411 1284 405 1291 1253 442 402 1292 407 +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 97 00 00 00 +# +name: Mode +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 87 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 86 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 9C 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 95 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 47 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2229 716 749 747 728 715 750 720 756 714 751 719 757 1447 751 719 756 714 751 1453 755 742 723 747 729 742 754 716 749 721 754 715 729 741 724 1454 754 1450 758 1445 753 1451 757 714 751 50967 2228 717 748 1455 753 51028 2198 746 730 1449 749 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2225 719 756 741 724 720 755 715 750 720 755 716 749 1456 752 719 746 726 749 1456 731 741 755 1451 757 714 751 1455 753 718 757 1448 750 1456 752 720 755 715 750 721 754 1452 756 1449 759 51509 2201 743 732 1473 756 51069 2202 717 758 1448 750 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2234 712 752 743 732 713 751 720 755 716 749 722 753 1453 754 716 759 712 752 1453 754 718 757 1449 758 713 751 1455 752 1454 753 718 757 741 723 748 727 745 730 1449 758 739 725 1455 752 51252 2234 713 751 1454 753 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2229 714 750 747 727 717 757 713 751 719 755 715 749 1455 751 719 755 715 749 1455 751 1453 753 717 757 714 750 1454 752 718 756 714 750 746 728 742 722 722 752 1452 754 716 758 1445 751 50982 2229 716 727 1479 748 51048 2205 712 752 1450 756 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2258 688 781 718 751 694 775 696 784 687 782 690 779 1427 782 690 779 693 776 1431 778 721 748 724 756 716 753 719 750 722 747 725 744 727 773 1407 781 1426 783 1425 774 1433 776 696 773 51015 2224 748 721 1458 783 51084 2249 697 783 1425 784 51089 2253 694 775 1432 756 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2212 836 751 1591 749 825 752 817 750 1577 753 806 750 804 752 796 750 857 751 828 749 825 752 816 751 813 743 816 751 803 743 805 752 102392 2214 834 743 1599 751 823 744 825 752 1575 745 840 727 801 745 804 752 854 744 835 752 822 745 824 743 821 746 813 743 810 746 802 744 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2214 838 748 1596 751 824 751 819 746 1584 753 807 747 807 747 803 751 857 750 830 745 830 745 825 750 814 751 810 745 1574 752 798 746 101740 2213 838 748 1597 750 824 751 819 746 1583 754 806 748 807 747 802 752 856 751 830 745 829 746 824 751 814 751 809 745 1574 753 797 747 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2216 836 750 1594 754 821 754 816 749 1580 747 813 752 803 751 798 746 1626 753 827 748 1591 746 824 751 1578 749 811 754 1566 750 799 745 99411 2218 833 753 1591 746 829 746 823 752 1577 749 810 755 800 754 795 749 1623 745 835 751 1588 749 821 754 1574 752 807 747 1572 755 795 749 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2219 732 750 729 753 722 749 1384 755 1374 755 705 746 708 753 697 754 754 749 731 751 723 749 721 750 715 746 713 748 1370 749 701 749 99627 2216 735 747 732 750 725 747 1387 753 1376 753 706 755 700 751 698 753 755 748 732 750 725 747 722 749 716 745 714 747 1372 747 702 749 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2215 735 746 730 751 722 749 1383 745 1381 747 711 749 704 746 702 748 1422 748 730 751 722 749 720 751 712 748 1373 745 1372 746 702 748 102284 2218 732 749 729 752 720 751 1380 748 1378 750 709 751 701 749 700 750 1420 750 728 753 719 752 717 754 709 752 1370 748 1369 749 699 751 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2221 729 752 725 746 727 754 1377 751 1375 753 706 755 699 751 697 753 753 749 729 752 1384 755 1377 751 713 747 711 749 1367 751 1361 746 101691 2216 734 747 731 750 723 748 1383 745 1381 747 711 749 705 745 703 747 759 754 725 746 1390 749 1383 756 708 752 706 754 1362 745 1366 752 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2220 831 751 1618 729 820 751 818 753 1575 751 835 726 829 721 802 748 885 728 851 720 854 728 815 756 809 752 807 754 1565 750 825 725 101743 2220 830 752 1618 729 819 752 844 727 1574 752 835 726 802 748 801 749 884 729 850 721 827 755 815 746 845 726 806 755 1590 725 798 752 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2218 857 725 1592 754 846 725 844 727 1574 752 834 727 827 723 826 724 883 720 1597 750 851 720 1586 750 840 721 1577 749 831 730 1557 748 99247 2221 854 728 1590 746 854 728 841 720 1583 753 832 729 825 725 824 726 881 722 1596 751 849 722 1585 751 839 722 1576 750 831 719 1568 747 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2217 832 750 1594 753 821 750 819 752 1576 750 809 804 750 800 748 750 1622 756 1587 749 1589 747 1586 750 1578 748 1575 751 1568 747 802 748 101181 2218 831 751 1592 755 819 752 817 754 1574 752 808 753 801 749 800 751 1620 748 1596 751 1587 749 1584 752 1576 750 1574 752 1566 749 800 803 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2274 662 756 711 749 718 752 715 797 669 801 1399 749 718 783 684 797 670 800 1400 748 720 750 716 754 713 747 720 750 718 752 714 777 718 752 1448 752 714 756 711 749 1450 750 1423 777 51569 2192 718 752 1447 753 50954 2223 712 748 1423 798 50911 2245 661 746 1451 749 50917 2218 715 755 1415 754 50965 2191 716 754 1445 755 50923 2223 710 750 1421 800 50893 2190 717 753 1445 776 50902 2242 664 796 1402 777 50907 2217 717 753 1418 803 50880 2223 710 750 1421 800 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2225 712 747 719 751 716 754 713 757 711 748 1451 748 718 752 715 754 712 747 1451 748 718 752 716 754 1445 754 1444 724 1449 750 716 754 713 746 1453 746 720 749 1450 749 1451 727 1446 753 50693 2219 717 752 1445 754 50927 2194 714 756 1442 747 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2216 733 744 734 754 1382 751 1381 752 712 744 714 753 700 746 702 754 753 745 733 755 719 748 720 747 717 750 709 747 706 750 1361 751 99476 2211 738 749 729 748 1389 754 1377 745 718 748 710 746 707 749 699 747 760 748 730 747 726 751 718 748 715 752 706 750 703 753 1359 753 49283 2217 670 745 1350 751 50661 2212 676 749 1345 746 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2213 737 751 727 750 1387 746 1385 748 716 751 707 749 704 752 696 750 1420 754 724 753 720 747 721 746 718 749 1373 749 1367 745 703 753 102141 2215 735 753 726 751 1385 747 1384 748 715 752 706 750 704 752 695 751 1419 755 723 754 720 747 721 746 718 749 1399 723 1367 755 693 753 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 33 2223 753 725 720 748 750 728 743 725 746 722 1458 750 749 750 722 726 747 721 1460 748 724 755 744 724 721 747 724 754 744 724 747 752 720 748 1458 750 722 756 715 753 718 750 1456 752 51488 2221 727 721 1460 748 51156 2203 744 724 1456 752 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 33 2225 718 750 719 749 721 747 722 746 723 745 1458 750 720 748 722 726 744 724 1452 756 741 727 742 726 1451 746 1457 750 1454 754 716 752 717 720 1483 725 745 723 1454 754 1450 747 1456 752 50811 2199 744 745 1433 754 51049 2229 715 743 1435 752 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2221 721 727 1450 779 1452 756 714 744 1487 721 723 756 714 754 715 774 774 726 1505 724 772 748 1482 726 770 751 1454 754 1477 721 1484 745 101867 2197 745 723 1453 776 1455 722 748 720 1483 756 713 755 714 744 725 775 774 726 1478 751 771 729 1475 754 769 720 1483 756 1448 750 1481 727 50476 2201 714 755 1448 781 50602 2221 720 748 1454 775 50604 2250 743 694 1456 773 50628 2226 715 743 1433 775 50652 2191 748 720 1430 778 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2193 750 729 1421 777 1454 754 715 753 1478 751 719 728 715 753 716 773 1510 729 1501 728 1477 752 1505 755 1475 754 1477 721 749 751 772 696 102110 2246 748 720 1455 774 1430 747 723 746 1484 724 745 723 720 748 721 779 1477 752 1478 751 1480 749 1508 752 1478 751 1479 729 741 748 748 721 50743 2195 747 721 1454 775 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2247 750 729 1447 782 1450 748 722 726 1506 723 720 748 722 757 713 776 772 728 769 751 1505 755 1476 753 1452 756 740 749 747 753 1452 756 101517 2220 749 719 1457 772 1433 754 715 753 1478 719 750 729 715 753 716 773 776 724 772 748 1508 752 1479 750 1481 727 769 731 739 750 1454 754 50114 2228 742 726 1450 779 50645 2197 746 722 1454 775 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1275 434 1253 430 408 1275 1251 431 1257 427 411 1272 415 1241 436 1248 439 1244 433 1250 437 1247 1279 7142 1276 433 1255 428 410 1247 1279 430 1247 435 414 1269 408 1249 438 1272 415 1241 436 1248 439 1244 1282 8244 1274 434 1253 429 409 1274 1252 430 1247 435 414 1242 435 1275 412 1270 407 1277 411 1246 441 1242 1274 7146 1282 426 1251 431 407 1276 1250 432 1255 426 412 1271 406 1276 411 1272 415 1242 435 1248 439 1244 1282 8243 1275 433 1255 428 410 1272 1254 429 1248 434 415 1267 410 1273 414 1269 408 1275 412 1245 432 1251 1275 7144 1273 435 1252 430 408 1274 1252 430 1247 436 413 1269 408 1249 438 1245 442 1267 410 1274 413 1270 1256 8241 1277 431 1257 425 413 1270 1256 425 1252 430 408 1275 412 1270 407 1276 411 1246 441 1267 410 1247 1279 7140 1277 431 1256 426 412 1270 1256 425 1252 430 408 1274 413 1270 407 1276 411 1245 442 1267 410 1247 1279 8245 1273 435 1252 430 408 1274 1252 430 1247 435 414 1269 408 1275 412 1270 407 1277 410 1246 441 1268 1248 7145 1273 435 1252 430 408 1274 1252 430 1247 435 414 1242 435 1248 439 1244 433 1250 437 1246 441 1242 1274 8249 1279 403 1274 434 415 1242 1274 434 1253 429 409 1247 440 1242 435 1248 439 1244 433 1250 437 1246 1280 7138 1279 403 1274 434 415 1268 1247 434 1253 429 409 1246 441 1268 409 1248 439 1244 433 1250 437 1245 1281 8241 1277 405 1283 426 412 1270 1256 426 1251 430 408 1274 413 1270 407 1276 411 1271 416 1267 410 1246 1280 7138 1279 429 1248 434 415 1268 1247 434 1274 407 442 1240 416 1267 410 1247 440 1243 434 1249 438 1271 1255 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1279 430 1247 436 413 1270 1256 427 1250 432 406 1277 410 1273 414 1269 408 1275 1251 432 406 1277 410 7983 1284 425 1252 430 408 1275 1251 431 1256 426 412 1270 407 1250 437 1272 415 1242 1274 435 414 1270 407 9092 1275 433 1254 428 410 1273 1253 428 1249 434 415 1268 409 1274 413 1269 408 1276 1250 432 406 1277 410 7984 1283 425 1252 431 407 1275 1251 431 1256 426 412 1270 407 1276 411 1272 415 1241 1275 435 414 1269 408 9090 1277 431 1256 426 412 1270 1256 426 1251 431 407 1275 412 1270 407 1276 411 1272 1254 428 410 1273 414 7978 1278 430 1247 435 414 1269 1247 435 1252 430 408 1274 413 1243 434 1275 412 1271 1255 427 411 1272 415 9082 1275 434 1253 428 410 1273 1253 428 1249 433 405 1277 410 1272 415 1268 409 1274 1252 430 408 1275 412 7980 1276 432 1255 427 411 1271 1255 427 1250 432 406 1276 411 1271 416 1267 410 1247 1279 430 408 1275 412 9085 1282 426 1251 431 407 1276 1250 432 1255 426 412 1270 407 1276 411 1272 415 1268 1247 435 414 1269 408 7985 1282 427 1250 432 406 1277 1249 433 1254 428 410 1272 415 1267 410 1274 413 1269 1257 426 412 1271 406 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9204 4469 604 562 604 562 604 562 604 563 603 564 602 565 601 567 599 568 598 1642 598 1643 607 1634 606 1634 606 1634 606 1635 605 1636 604 1637 603 1638 602 565 601 1641 599 568 598 569 607 560 606 1635 605 588 578 562 604 1637 603 563 603 1638 602 1639 601 1641 599 594 572 1643 607 39280 9217 2212 601 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9225 4468 605 563 603 564 602 565 601 566 600 568 598 569 597 570 606 561 605 1636 604 1638 602 1640 600 1641 599 1643 607 1634 606 1636 603 1637 603 1639 601 1640 600 1641 599 595 571 568 598 569 597 1645 605 588 578 562 604 563 603 564 602 1639 601 1641 599 1643 597 596 580 1635 605 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9214 4475 598 568 598 597 579 560 606 588 578 589 577 563 603 590 576 564 602 1666 574 1667 573 1641 599 1669 571 1643 597 1644 606 1635 605 1663 577 563 603 564 602 591 575 592 574 592 574 593 573 1668 572 594 572 1669 571 1669 571 1670 570 1644 606 1635 605 1636 604 590 576 1637 603 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9275 4475 608 560 606 563 603 565 601 568 598 570 606 563 603 565 601 567 599 1645 605 1638 602 1642 598 1645 605 1639 601 1642 598 1645 605 1637 603 1641 599 569 607 561 605 1639 601 1642 608 560 606 563 603 565 601 568 598 1644 606 1637 603 565 601 567 599 1644 606 1636 604 1639 601 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1279 428 1246 434 413 1265 1246 434 1250 430 407 1271 413 1240 434 1245 439 1240 434 1245 439 1240 1271 7282 1276 430 1244 436 411 1267 1255 426 1248 432 405 1248 437 1243 431 1248 436 1243 431 1248 436 1243 1279 7276 1272 434 1250 430 407 1271 1251 430 1244 436 411 1267 407 1247 437 1241 433 1246 438 1241 433 1246 1276 7279 1279 427 1247 433 404 1248 1274 433 1251 429 408 1271 413 1239 435 1245 440 1239 435 1244 430 1249 1273 7282 1276 430 1244 436 411 1267 1244 436 1248 432 405 1273 411 1242 432 1247 437 1241 433 1247 437 1241 1281 7274 1274 432 1252 428 409 1270 1252 428 1246 434 413 1266 408 1244 441 1239 435 1244 440 1239 435 1244 1278 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1275 405 1279 401 436 1243 1279 401 1273 407 440 1238 436 1244 1278 429 408 1244 440 1239 435 1244 440 8113 1272 434 1250 430 407 1246 1276 430 1254 426 411 1242 432 1247 1275 432 405 1247 437 1242 432 1247 437 8115 1280 426 1248 432 405 1248 1274 433 1251 428 409 1244 440 1239 1272 434 413 1239 435 1245 439 1239 435 8118 1277 429 1245 435 412 1240 1271 436 1248 431 406 1247 437 1242 1280 427 410 1242 432 1247 437 1242 432 8121 1274 406 1278 428 409 1244 1278 402 1272 408 439 1240 434 1245 1277 404 433 1246 438 1240 434 1245 439 8114 1281 399 1275 405 432 1247 1275 406 1278 401 436 1244 430 1249 1273 407 440 1240 434 1245 439 1239 435 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1248 412 1274 438 405 1284 1245 445 1251 435 408 1281 405 1282 404 1285 411 1251 435 1253 1276 437 406 8011 1242 445 1251 435 408 1255 1274 442 1244 443 410 1278 408 1280 406 1282 435 1254 411 1277 1242 445 408 8011 1242 443 1243 444 409 1279 1250 440 1246 441 412 1277 409 1279 407 1281 405 1282 404 1286 1243 443 410 8008 1245 442 1244 443 410 1278 1251 440 1246 441 402 1286 410 1278 408 1280 406 1256 430 1284 1276 411 411 8006 1247 439 1247 439 404 1285 1244 446 1250 437 406 1282 404 1283 403 1285 411 1278 408 1254 1275 438 405 8013 1251 436 1250 410 433 1282 1247 443 1243 443 410 1279 407 1281 405 1282 404 1258 438 1276 1243 444 409 7981 1272 414 1272 441 412 1250 1269 448 1248 438 405 1257 429 1259 437 1277 429 1232 433 1281 1248 412 431 7986 1267 446 1250 436 407 1255 1274 443 1243 443 410 1253 433 1280 406 1282 404 1258 428 1287 1273 387 435 7981 1272 414 1272 415 438 1276 1243 447 1249 411 432 1282 435 1228 489 1198 436 1278 408 1280 1249 411 432 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1281 410 1289 429 415 1276 1257 435 1264 428 416 1248 440 1250 438 1253 435 1282 1261 404 440 1277 411 8127 1288 404 1284 434 410 1281 1262 403 1285 432 412 1280 408 1256 443 1249 439 1278 1265 401 443 1275 413 8125 1289 429 1260 432 412 1253 1291 401 1287 431 413 1251 437 1253 446 1246 442 1249 1284 435 409 1256 443 8122 1282 437 1262 429 415 1277 1256 409 1290 428 416 1249 439 1251 437 1254 445 1247 1286 433 411 1254 445 8120 1283 409 1290 428 416 1275 1258 434 1254 437 418 1274 414 1249 439 1252 436 1281 1262 404 440 1251 437 8128 1286 432 1256 436 408 1257 1286 405 1283 435 409 1282 417 1247 441 1250 438 1253 1290 428 416 1249 439 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1286 406 1282 438 416 1275 1257 435 1263 430 1258 434 410 1282 416 1276 412 1254 444 1248 1284 436 1262 7278 1288 403 1285 435 408 1257 1285 433 1255 438 1260 431 413 1279 409 1283 415 1250 438 1254 1288 405 1283 7285 1281 411 1287 432 412 1253 1289 430 1258 435 1263 429 415 1250 438 1254 444 1248 440 1252 1290 429 1259 7283 1283 408 1290 428 416 1249 1283 436 1262 431 1257 435 408 1256 442 1250 438 1255 443 1248 1284 436 1262 7278 1288 430 1258 435 408 1256 1286 433 1255 437 1261 431 413 1252 436 1282 416 1249 439 1254 1288 404 1284 7282 1284 408 1290 428 416 1249 1283 436 1262 430 1258 434 410 1255 443 1249 439 1253 445 1247 1285 408 1290 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1276 403 1271 435 412 1241 1270 409 1276 431 1254 427 410 1242 1280 427 1247 433 404 1249 1273 407 430 8123 1273 433 1251 429 408 1245 1277 429 1245 435 1250 430 407 1272 1250 430 1255 426 411 1268 1254 426 411 8117 1279 401 1273 433 404 1249 1273 433 1251 429 1245 435 412 1240 1271 435 1249 431 406 1246 1276 431 406 8122 1274 406 1278 427 410 1270 1252 428 1246 434 1251 429 408 1271 1251 403 1271 436 411 1267 1244 436 411 8116 1279 427 1247 433 404 1275 1247 433 1251 429 1245 434 413 1266 1245 435 1250 430 407 1272 1250 431 406 8121 1274 431 1254 427 410 1269 1253 427 1247 433 1251 429 408 1271 1251 429 1245 434 413 1266 1245 435 412 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1280 457 1229 426 468 1236 1282 456 1230 427 468 1236 470 1235 471 1235 471 1235 471 1235 492 1240 1289 7171 1290 422 1284 399 464 1238 1291 422 1284 399 464 1239 467 1238 468 1238 468 1238 489 1217 500 1233 1285 7176 1285 426 1280 403 471 1232 1286 426 1281 403 471 1232 464 1242 496 1210 496 1237 469 1236 470 1236 1282 7206 1286 399 1287 422 441 1262 1287 398 1288 449 435 1215 491 1241 465 1240 466 1240 466 1240 466 1240 1289 7199 1283 429 1257 426 469 1235 1283 428 1258 426 469 1234 473 1234 472 1234 472 1233 474 1233 463 1243 1306 7182 1289 448 1258 398 465 1238 1291 448 1258 425 438 1239 467 1238 468 1238 468 1237 490 1216 490 1242 1287 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1289 448 1227 454 441 1238 1280 457 1228 454 441 1238 468 1239 1289 448 436 1215 491 1242 475 1233 473 8012 1281 457 1249 406 468 1237 1281 457 1260 396 467 1238 468 1239 1290 448 436 1242 475 1233 473 1234 472 8013 1291 421 1285 397 466 1238 1291 421 1285 398 465 1239 467 1240 1288 423 472 1233 473 1233 473 1234 472 8014 1289 449 1257 398 465 1239 1289 422 1284 398 465 1239 467 1240 1309 403 471 1233 473 1234 472 1235 471 8015 1309 402 1283 426 437 1241 1308 403 1283 427 436 1241 496 1237 1281 404 470 1235 471 1235 471 1236 491 8021 1282 402 1284 452 443 1235 1283 402 1284 452 443 1209 497 1236 1282 402 472 1233 463 1243 495 1212 494 8019 1284 427 1258 450 445 1233 1285 426 1259 450 444 1233 473 1233 1285 426 437 1240 497 1209 497 1236 470 8016 1288 450 1225 431 464 1241 1288 451 1255 402 472 1232 474 1232 1286 452 443 1235 523 1183 471 1235 471 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1286 424 1251 430 465 1241 1287 423 1252 457 437 1241 465 1242 464 1243 474 1234 472 1235 1314 423 440 8017 1286 451 1255 401 462 1242 1286 452 1254 402 472 1232 474 1233 473 1234 472 1234 493 1241 1287 424 439 8014 1289 422 1284 426 437 1241 1287 451 1255 403 471 1234 472 1234 472 1235 492 1215 491 1242 1286 399 464 8018 1285 426 1280 403 471 1234 1284 454 1252 404 470 1235 471 1236 491 1215 491 1243 526 1181 1285 427 436 8019 1315 423 1262 395 468 1236 1313 400 1286 424 439 1238 489 1218 499 1235 471 1236 470 1236 1282 430 464 8019 1284 401 1284 426 468 1236 1282 404 1281 456 438 1240 466 1240 466 1241 465 1241 465 1242 1286 426 468 8015 1288 424 1261 448 436 1242 1286 426 1259 451 444 1234 472 1234 472 1235 471 1235 471 1236 1282 456 438 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9090 4295 561 1708 570 561 563 569 565 567 567 565 569 563 561 570 564 568 566 565 569 1701 567 1702 566 1703 565 1705 563 1706 562 1707 561 1708 570 1699 569 1700 568 564 570 562 562 570 564 567 567 565 569 1700 568 563 591 563 561 1708 571 1699 569 1700 568 1702 566 1703 565 565 569 40667 9086 2139 562 97339 9092 2132 569 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9092 4292 563 1732 536 569 565 567 567 591 543 589 534 597 537 595 539 566 568 564 570 1699 569 1700 568 1702 566 1703 565 1705 563 1707 561 1708 570 1700 568 1701 567 591 543 1700 568 564 570 588 535 570 564 1732 556 571 563 568 566 1703 565 567 567 1702 566 1703 565 1705 563 567 567 40657 9086 2135 566 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9091 4292 564 1706 562 569 565 567 567 565 569 562 561 570 564 568 566 565 569 562 561 1708 570 1698 570 1700 568 1728 540 1703 565 1705 563 1706 562 1708 570 1699 569 1700 568 563 571 561 562 569 565 566 568 1701 587 566 568 563 571 561 562 1706 562 1707 561 1708 570 1699 569 562 561 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9000 4466 564 1681 564 566 572 559 569 561 567 564 564 567 571 558 570 560 568 563 565 1678 567 1679 566 1681 564 1682 564 1679 566 1678 567 1686 570 1676 569 1674 571 559 569 563 565 1679 566 564 564 566 572 550 567 563 565 565 563 1679 566 1677 568 563 565 1678 567 1676 569 1681 564 1680 565 1680 565 565 563 569 569 1674 571 560 567 563 565 555 573 558 570 559 569 1674 571 1670 565 564 564 1678 567 1676 569 1675 570 12978 9003 2207 566 96147 9000 2203 570 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9005 4459 570 1701 544 560 568 563 565 566 572 559 569 563 565 566 572 559 569 561 567 1678 567 1676 569 1675 570 1673 572 1671 564 1680 565 1685 571 1671 564 1677 568 561 567 562 566 564 564 565 563 565 563 557 571 557 571 557 571 1670 565 1678 567 1674 571 1668 567 1671 564 1686 570 1670 565 1675 570 559 569 560 568 561 567 562 566 563 565 553 564 563 565 564 564 1677 569 1673 573 1671 564 1678 567 1674 571 1671 564 13005 8998 2203 570 96194 8995 2204 569 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9004 4451 568 1683 573 558 570 561 567 565 563 568 570 560 568 562 566 564 564 566 572 1680 565 1688 568 1685 571 1685 571 1684 572 1683 573 1674 571 1681 564 567 571 1679 566 1686 570 561 566 565 563 567 571 551 566 563 565 1690 566 565 563 569 569 1684 572 1682 563 1690 566 1681 564 1689 567 564 564 1689 567 1687 569 563 565 566 572 559 569 551 566 563 565 1684 572 558 570 560 567 1682 563 1687 569 1683 562 1688 568 12989 8992 2208 565 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9005 4457 572 1673 572 559 569 561 567 563 565 566 562 567 571 558 570 560 568 562 566 1676 569 1673 572 1698 537 1708 537 1704 541 1703 542 1708 537 567 571 1669 566 1701 544 558 570 560 568 561 567 564 564 557 571 1671 564 565 563 566 572 1670 565 1703 542 1673 562 1678 567 1683 562 566 562 1677 568 1701 544 559 569 561 567 563 565 565 563 557 571 1668 567 563 565 563 565 1676 569 1699 546 1669 566 1678 567 1676 569 +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 8C 00 00 00 +# +name: Speed_dn +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 83 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 85 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8958 4439 570 1663 563 561 562 563 560 564 569 556 567 557 566 558 565 559 564 560 563 1669 567 1663 563 1670 566 1665 561 1670 566 1666 560 1680 567 1665 561 1670 566 558 565 560 563 1669 567 557 566 559 564 552 561 563 560 565 568 1663 563 1668 568 556 567 1665 561 1670 566 1674 562 1668 568 1663 563 561 562 562 561 1670 566 558 565 559 564 552 561 563 560 564 569 1661 565 1667 569 555 568 1664 562 1668 568 1663 563 12896 8966 2197 565 95701 8962 2200 562 95719 8975 2194 568 95705 8970 2195 567 95693 8970 2196 566 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8965 4443 566 1668 568 557 566 559 564 562 561 564 569 583 540 559 564 561 562 563 560 1673 563 1671 565 1669 567 1666 560 1673 563 1670 566 1676 560 1673 563 1670 566 558 565 561 562 564 569 556 567 559 564 553 560 565 568 558 565 1668 568 1666 570 1663 563 1670 566 1667 569 1671 565 1668 568 1665 561 564 569 557 566 559 564 562 561 564 569 547 566 560 563 562 561 1672 564 1669 567 1666 570 1664 562 1671 565 1668 568 12942 8972 2198 564 95779 8964 2201 561 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8970 4439 570 1665 561 564 569 556 567 558 565 561 562 563 560 566 567 557 566 559 564 1669 567 1667 569 1665 561 1672 564 1670 566 1668 569 1674 562 563 560 1673 563 1671 565 561 562 564 569 556 567 559 564 553 560 1673 563 563 560 566 567 1667 569 1664 562 1672 564 1669 567 1676 560 566 567 1667 569 1664 562 563 570 555 568 558 565 560 563 553 560 1674 562 563 560 565 569 1667 570 1664 562 1671 565 1667 569 1663 563 12935 8969 2204 568 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8963 4430 569 1673 563 561 562 562 561 564 570 555 568 557 566 559 564 561 562 563 560 1680 567 1674 562 1680 567 1675 561 1681 566 1676 561 1671 565 1675 562 563 560 1682 565 1678 569 557 566 559 564 562 561 555 568 557 566 1675 562 564 569 555 568 1674 563 1678 569 1673 564 1670 566 1675 561 563 570 1672 564 1677 570 555 568 558 565 560 563 552 561 564 559 1682 565 560 563 563 560 1681 566 1675 561 1681 566 1676 561 12950 8975 2195 567 95870 8966 2201 561 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8973 4429 570 1673 563 562 561 565 568 557 566 559 564 561 562 564 569 557 566 559 564 1679 568 1674 562 1680 567 1676 560 1682 565 1676 571 1663 563 1679 568 558 565 1677 570 1673 563 562 561 565 568 557 566 1667 559 565 568 1673 563 563 560 565 568 1674 562 1679 568 1675 561 556 567 1675 561 564 569 1672 564 1677 570 556 567 559 564 561 562 1670 566 559 564 1676 571 555 568 557 566 1675 562 1680 567 1675 561 564 569 12939 8975 2197 565 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8972 4427 561 1681 566 560 563 562 561 565 568 557 566 559 564 561 562 563 560 565 568 1673 563 1676 571 1671 565 1676 571 1672 564 1677 570 1664 562 1679 568 1674 562 562 561 1680 567 558 565 560 563 562 561 555 568 556 567 558 565 1676 560 564 569 1670 566 1675 561 1680 567 1665 561 1680 567 1675 561 564 569 1673 563 562 561 564 569 556 567 548 565 561 562 562 561 1680 567 558 565 1677 570 1672 564 1678 569 1673 563 12942 8971 2195 567 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4477 566 1725 541 537 575 530 572 534 568 537 565 540 572 533 569 537 565 540 572 1690 566 1697 569 1720 546 1690 566 1697 569 1694 572 1691 565 1698 568 1694 572 533 569 537 565 540 572 533 569 536 566 540 572 532 570 536 566 1696 570 1692 574 1715 541 1696 570 1693 573 1689 567 1696 570 1693 573 532 570 536 566 539 573 532 570 535 567 538 574 531 571 534 568 1695 571 1691 565 1725 541 1695 571 1692 574 1688 568 11738 9017 2223 574 95995 9023 2216 570 95998 9020 2219 567 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9025 4477 576 1687 569 564 548 531 571 534 568 537 565 540 572 533 569 536 566 539 573 1689 567 1696 570 1693 573 1689 567 1696 570 1693 573 1690 566 1697 569 1694 572 560 542 1694 572 560 542 537 565 540 572 533 569 536 566 539 573 1689 567 565 547 1689 567 1696 570 1692 574 1689 567 1696 570 1693 573 559 543 1693 573 559 543 536 566 539 573 532 570 535 567 538 574 1688 568 564 548 1688 568 1695 572 1691 575 1688 568 11738 9016 2223 574 95995 9022 2216 570 96000 9019 2219 630 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9022 4480 573 1691 565 540 572 533 569 537 565 540 572 533 569 536 566 540 572 532 570 1693 573 1690 566 1697 569 1694 572 1717 539 1724 542 1721 545 1718 548 1715 541 1722 544 535 567 538 574 531 571 535 567 538 574 531 571 534 568 538 564 1698 568 1695 571 1692 574 1689 567 1722 544 1719 547 1716 540 1723 543 536 566 540 572 533 569 536 566 539 573 532 570 536 566 539 573 1689 567 1696 570 1693 573 1690 566 1724 542 11737 9015 2224 572 95995 9025 2215 571 95997 9020 2219 567 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9021 4481 572 1692 574 531 571 535 567 538 574 531 571 534 568 538 564 541 571 534 568 1694 572 1691 575 1688 568 1722 544 1719 547 1690 566 1697 569 1694 572 1691 565 540 572 533 569 1694 572 533 569 537 565 540 572 533 569 537 565 1698 568 1694 572 533 569 1695 571 1692 574 1715 541 1722 544 1693 573 531 571 535 567 1696 570 535 567 539 573 532 570 535 567 538 574 1689 567 1696 570 535 567 1696 570 1693 573 1716 540 11739 9024 2216 570 95999 9020 2219 567 96001 9020 2220 566 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 92 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 89 00 00 00 +# +name: Speed_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 9F 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 87 00 00 00 +# +name: Mode +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 81 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1286 402 1289 426 414 1241 1280 408 1283 431 409 1245 435 1246 434 1247 433 1249 442 1239 441 1240 1281 7121 1281 404 1276 436 414 1239 1282 429 1251 433 407 1246 434 1246 434 1247 433 1247 433 1247 433 1247 1284 8216 1275 408 1283 428 412 1267 1253 430 1250 432 408 1245 435 1244 436 1244 436 1244 436 1244 436 1243 1277 7120 1282 401 1279 403 437 1243 1277 404 1276 407 433 1247 433 1246 434 1246 434 1246 434 1246 434 1245 1275 8224 1277 405 1275 407 433 1247 1273 409 1282 400 440 1240 440 1239 441 1239 441 1239 441 1238 442 1238 1282 7115 1276 405 1275 407 433 1247 1273 409 1281 400 440 1240 440 1239 441 1239 441 1239 441 1238 432 1248 1283 8216 1275 407 1283 399 441 1238 1282 400 1280 402 438 1241 439 1241 439 1240 440 1239 441 1239 441 1238 1282 7114 1277 405 1275 406 434 1246 1274 407 1273 408 432 1248 432 1247 433 1247 1246 434 1245 435 1245 1275 8223 1279 403 1277 404 436 1244 1276 406 1274 407 433 1247 433 1246 434 1245 435 1245 435 1244 436 1244 1276 7120 1282 400 1280 401 439 1241 1279 402 1278 404 436 1244 436 1243 437 1243 437 1242 438 1241 439 1241 1279 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1286 401 1279 434 406 1249 1282 404 1276 436 414 1240 440 1241 1280 432 408 1247 433 1248 432 1248 443 7955 1277 408 1283 430 410 1244 1276 408 1283 429 411 1243 437 1243 1278 408 432 1248 432 1249 441 1265 415 9057 1286 400 1280 431 409 1271 1250 435 1256 429 411 1243 437 1243 1278 433 407 1248 432 1248 432 1248 432 7966 1277 407 1284 427 413 1241 1280 431 1249 435 405 1249 442 1239 1282 402 438 1243 437 1243 437 1243 437 9062 1280 404 1276 435 405 1248 1283 401 1279 432 408 1245 435 1246 1275 436 414 1239 441 1239 441 1239 441 7956 1276 407 1284 427 413 1241 1280 404 1276 434 406 1247 433 1247 1284 400 440 1240 440 1240 440 1240 440 9058 1284 399 1281 429 411 1243 1278 432 1248 435 405 1249 431 1248 1283 401 439 1241 439 1241 439 1241 439 7957 1275 408 1283 401 439 1241 1280 430 1250 406 434 1246 434 1246 1275 409 441 1238 432 1249 431 1248 432 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1292 403 1288 431 409 1246 1285 406 1285 432 408 1247 433 1249 442 1240 1280 407 443 1239 441 1267 413 7957 1286 401 1279 434 406 1248 1283 404 1287 427 413 1241 439 1241 439 1241 1280 407 433 1248 432 1248 443 9055 1277 409 1282 431 409 1244 1276 409 1282 431 409 1244 436 1245 435 1245 1286 426 414 1267 413 1241 439 7956 1276 409 1282 430 410 1244 1277 408 1283 429 411 1269 411 1243 437 1243 1278 407 433 1248 432 1248 432 9065 1277 407 1284 428 412 1242 1278 406 1285 427 413 1241 439 1241 439 1241 1280 406 434 1247 433 1247 433 7962 1281 405 1275 436 414 1266 1254 431 1249 436 414 1265 415 1239 441 1240 1280 432 408 1246 434 1247 433 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1274 408 1283 426 414 1239 1282 427 1253 428 412 1241 439 1240 440 1240 440 1239 1281 427 413 1267 413 7957 1275 434 1246 436 404 1275 1256 427 1253 429 411 1267 413 1267 413 1240 440 1240 1280 428 412 1267 413 9060 1282 400 1280 428 412 1267 1253 429 1251 430 410 1243 437 1243 437 1242 438 1242 1278 429 411 1241 439 7958 1274 434 1246 436 414 1264 1256 426 1254 428 412 1266 414 1240 492 1188 440 1240 1280 428 412 1240 440 9059 1272 436 1254 426 414 1239 1281 426 1254 427 413 1240 440 1239 441 1239 441 1238 1282 426 414 1239 441 7955 1277 405 1275 407 433 1246 1274 408 1282 399 441 1239 441 1239 441 1239 441 1239 1281 400 440 1240 440 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1309 376 1276 409 433 1252 1305 379 1273 410 463 1222 441 1244 460 1221 463 1221 442 1242 462 1222 1283 7692 1310 375 1309 377 464 1220 1305 379 1273 410 463 1223 460 1222 441 1242 462 1221 431 1252 463 1220 1305 7668 1281 401 1303 378 432 1252 1284 399 1305 378 464 1220 464 1217 466 1216 467 1215 468 1214 459 1223 1303 7665 1305 379 1304 381 440 1244 1282 405 1278 409 433 1254 440 1246 437 1248 467 1219 433 1252 463 1224 1312 7687 1304 385 1309 380 461 1227 1278 410 1305 382 439 1250 433 1253 462 1228 466 1222 461 1226 468 1219 1306 7689 1313 375 1308 380 441 1248 1309 379 1284 402 460 1228 466 1222 441 1248 467 1222 462 1227 467 1222 1304 7688 1283 403 1312 374 468 1219 1306 382 1312 377 464 1224 439 1249 434 1252 442 1245 459 1228 435 1253 1304 7683 1277 411 1304 383 438 1249 1308 380 1303 381 461 1225 438 1248 435 1252 442 1247 436 1252 442 1246 1311 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1274 436 1247 436 406 1277 1249 437 1257 427 415 1269 415 1270 413 1272 1254 432 410 1274 410 1271 413 8525 1277 431 1253 430 412 1272 1254 430 1254 428 413 1270 414 1271 413 1272 1254 430 412 1271 413 1272 412 8537 1275 435 1249 437 415 1270 1256 427 1257 427 415 1268 405 1279 415 1268 1247 437 404 1279 415 1269 415 8528 1274 433 1251 432 410 1272 1254 430 1254 431 411 1273 411 1271 413 1244 1282 429 413 1271 413 1271 413 8532 1281 429 1255 430 412 1272 1254 429 1255 428 414 1271 413 1245 439 1273 1253 432 410 1274 410 1273 411 8539 1273 435 1249 433 409 1273 1253 429 1255 430 412 1271 413 1270 414 1267 1248 434 408 1274 410 1273 410 8532 1280 428 1256 427 414 1268 1247 436 1248 436 406 1250 434 1275 409 1272 1254 429 413 1270 414 1270 414 8534 1278 431 1253 433 409 1273 1253 429 1255 429 413 1269 415 1267 406 1277 1249 436 405 1277 407 1249 435 8531 1281 427 1246 436 406 1275 1251 434 1250 433 409 1272 412 1270 414 1269 1246 436 405 1277 407 1274 410 8537 1275 435 1249 435 407 1278 1248 437 1257 428 414 1268 405 1277 407 1276 1250 434 408 1274 410 1272 412 8531 1281 429 1255 429 413 1243 1283 427 1257 428 414 1271 413 1272 412 1272 1254 432 410 1273 411 1247 437 8536 1276 432 1252 432 410 1273 1253 432 1252 433 409 1275 409 1275 409 1275 1251 435 407 1278 405 1277 407 8535 1278 430 1254 428 414 1268 1247 436 1248 435 406 1276 408 1277 407 1277 1249 435 407 1277 407 1277 407 8540 1273 435 1249 434 408 1273 1253 429 1255 429 413 1271 413 1270 414 1270 1256 430 412 1271 413 1271 413 8538 1275 435 1249 435 407 1275 1251 432 1252 433 409 1275 409 1274 410 1274 1252 432 410 1275 409 1276 408 8542 1281 429 1255 431 411 1273 1253 431 1253 432 410 1273 411 1271 413 1271 1255 431 411 1273 411 1272 412 8529 1273 436 1258 427 415 1268 1258 427 1257 429 413 1272 412 1273 411 1249 1277 436 406 1279 415 1268 415 8536 1276 432 1252 430 412 1271 1255 431 1253 432 410 1275 409 1274 410 1273 1253 432 410 1274 410 1272 412 8527 1275 435 1249 435 407 1276 1250 433 1251 434 408 1277 407 1279 415 1269 1257 427 415 1271 413 1270 413 8556 1277 436 1258 431 411 1277 1259 430 1254 436 406 1280 414 1272 412 1275 1251 438 414 1272 412 1272 412 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1280 431 1253 432 410 1275 1250 433 1251 434 408 1277 406 1280 414 1272 412 1274 410 1276 1250 436 405 8543 1331 352 1280 432 410 1275 1251 433 1251 435 407 1275 409 1272 412 1271 413 1272 412 1273 1253 432 410 8527 1275 435 1249 436 405 1276 1250 431 1253 429 413 1270 413 1270 413 1269 414 1269 415 1268 1247 436 405 8534 1278 430 1253 428 414 1268 1247 435 1248 435 407 1277 406 1276 408 1274 410 1274 410 1248 1277 436 405 8558 1275 438 1256 431 411 1277 1259 428 1256 431 411 1275 409 1278 416 1271 413 1273 411 1277 1259 429 413 8559 1285 428 1256 430 412 1273 1253 433 1251 434 408 1277 406 1280 414 1274 410 1279 415 1273 1253 437 415 8550 1283 430 1254 433 409 1279 1257 431 1253 434 408 1279 415 1274 410 1278 416 1274 410 1278 1258 431 411 8553 1280 432 1252 435 407 1279 1257 427 1256 427 404 1277 406 1276 408 1248 435 1274 410 1273 1253 430 412 8535 1277 433 1251 435 406 1279 1257 429 1255 431 411 1275 409 1277 406 1278 416 1271 413 1272 1254 431 411 8545 1278 433 1251 434 408 1278 1258 428 1255 432 410 1274 410 1279 415 1273 411 1278 416 1273 1253 437 415 8557 1286 429 1254 437 415 1274 1252 436 1258 430 412 1277 406 1283 411 1278 405 1282 412 1275 1250 438 414 8561 1282 433 1251 438 414 1274 1252 438 1256 433 409 1277 407 1280 414 1272 412 1275 409 1278 1258 429 413 8549 1284 430 1254 434 408 1279 1257 429 1255 433 409 1279 415 1273 411 1277 407 1280 414 1274 1252 436 405 8557 1276 436 1258 431 411 1274 1252 434 1250 437 415 1272 412 1275 408 1280 414 1275 409 1280 1256 433 409 8558 1285 428 1256 431 411 1276 1250 438 1256 432 410 1278 416 1272 412 1273 411 1273 411 1273 1253 432 410 8539 1273 437 1247 436 405 1277 1249 434 1250 435 406 1276 408 1275 409 1275 409 1275 409 1275 1251 435 406 8543 1280 430 1254 431 411 1274 1252 431 1253 431 411 1273 410 1272 412 1273 411 1274 410 1276 1250 434 408 8542 1280 432 1252 434 408 1279 1257 430 1254 433 408 1277 407 1278 416 1270 413 1272 412 1275 1251 435 406 8540 1283 427 1257 429 413 1272 1254 431 1253 432 410 1275 409 1276 408 1278 406 1277 406 1276 1250 435 407 8538 1274 434 1250 434 408 1277 1249 436 1258 428 414 1273 411 1272 412 1270 414 1271 412 1273 1253 433 409 8535 1277 433 1251 436 406 1282 1254 433 1250 438 414 1274 410 1278 405 1280 414 1271 413 1276 1260 429 412 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1280 433 1251 436 405 1281 1255 431 1253 434 408 1278 405 1279 1257 431 411 1277 407 1280 414 1274 410 8547 1275 437 1257 428 413 1270 1255 430 1254 430 412 1273 411 1275 1251 436 405 1280 414 1271 412 1273 410 8550 1283 431 1253 434 408 1278 1258 427 1256 428 414 1271 412 1272 1254 434 408 1279 415 1271 412 1247 436 8544 1279 433 1251 435 407 1279 1257 430 1254 434 407 1278 405 1277 1249 437 415 1269 414 1269 415 1269 415 8537 1275 435 1248 435 406 1276 1250 436 1248 436 406 1278 406 1277 1248 437 404 1280 414 1269 414 1271 412 8532 1280 428 1255 428 413 1269 1246 437 1246 435 406 1276 408 1277 1248 436 406 1278 406 1279 415 1268 416 8538 1274 436 1258 427 415 1269 1257 427 1257 430 412 1272 412 1273 1252 434 408 1277 407 1280 414 1272 412 8553 1280 405 1279 434 408 1276 1250 435 1259 429 413 1273 411 1274 1252 434 408 1277 407 1279 415 1270 414 8548 1275 436 1258 428 414 1272 1254 433 1250 435 406 1277 406 1278 1258 429 413 1272 412 1275 409 1277 406 8549 1284 427 1257 430 412 1274 1251 433 1251 434 407 1277 406 1278 1248 437 415 1269 415 1271 412 1273 410 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8982 4433 598 1657 570 519 594 547 566 522 591 549 575 540 573 541 572 542 571 543 570 1659 568 1635 592 1637 600 1655 572 1657 570 1660 566 1636 601 1654 572 1630 597 517 596 545 568 520 593 547 566 548 576 539 574 540 573 540 573 1629 598 1631 596 1633 593 1635 592 1637 600 1629 597 1631 596 1633 593 521 592 522 591 549 575 540 573 514 599 542 571 543 570 544 569 1659 568 1635 592 1637 600 1628 598 1630 597 1632 595 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8990 4430 601 1628 598 517 596 519 594 547 566 549 574 540 573 542 571 544 569 546 567 1663 574 1628 598 1658 568 1662 575 1654 572 1658 568 1660 566 1663 574 1656 570 544 569 1660 566 548 575 539 574 541 572 542 571 544 569 545 568 1662 575 539 574 1656 570 1658 568 1661 565 1664 573 1656 570 1659 567 548 565 1664 573 542 571 543 570 545 568 546 567 548 565 549 574 1655 571 543 570 1659 567 1661 576 1628 598 1658 568 +# +name: Speed_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0E 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 05 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1313 407 1306 414 432 1273 1306 415 1308 447 409 1275 438 1275 437 1275 438 1273 1306 449 407 1278 434 7939 1307 438 1285 408 427 1278 1312 409 1304 424 484 1229 432 1280 433 1280 433 1277 1313 415 431 1282 430 7925 1310 408 1304 414 432 1274 1305 415 1308 420 426 1286 427 1285 428 1284 429 1282 1308 420 426 1286 427 7944 1312 407 1306 413 433 1272 1307 413 1310 418 428 1284 429 1283 481 1230 431 1279 1311 417 429 1283 430 7922 1313 406 1307 412 434 1271 1308 412 1311 416 430 1283 429 1282 431 1281 432 1277 1313 415 431 1281 431 7935 1310 409 1365 353 482 1222 1306 415 1308 420 426 1285 428 1284 429 1283 429 1280 1310 418 428 1284 429 7922 1303 415 1308 411 435 1270 1310 411 1312 415 431 1280 432 1279 433 1278 435 1274 1305 423 433 1278 435 7930 1305 413 1310 409 426 1277 1313 408 1305 422 434 1277 436 1275 427 1284 429 1280 1310 418 428 1283 430 7918 1307 411 1312 407 428 1275 1304 416 1307 420 436 1275 427 1283 430 1281 431 1277 1313 414 432 1279 433 7928 1307 411 1312 406 429 1274 1305 414 1309 419 427 1283 430 1281 432 1279 434 1275 1304 449 407 1277 435 7909 1305 412 1311 408 427 1276 1303 416 1307 421 435 1275 427 1283 430 1281 431 1277 1313 414 432 1279 433 7925 1310 408 1305 414 432 1271 1308 412 1311 417 429 1281 431 1279 433 1276 436 1272 1308 420 426 1284 429 7914 1311 407 1368 351 433 1271 1308 412 1311 416 430 1281 431 1278 434 1276 426 1281 1309 419 427 1283 430 7928 1307 410 1364 354 481 1221 1307 414 1309 418 428 1282 431 1280 433 1277 436 1272 1307 420 436 1274 428 7913 1312 406 1307 412 434 1269 1310 410 1313 414 432 1278 434 1275 427 1282 431 1277 1313 415 431 1279 433 7922 1313 405 1307 411 435 1268 1311 409 1304 423 433 1277 435 1274 428 1281 431 1276 1314 414 432 1278 434 7905 1310 409 1314 404 431 1271 1308 412 1311 417 429 1280 432 1277 436 1274 428 1279 1311 417 429 1280 432 7922 1313 432 1281 438 408 1267 1312 435 1288 440 406 1277 436 1273 429 1280 432 1275 1315 413 433 1278 434 7903 1311 409 1314 404 431 1269 1310 413 1310 418 428 1306 407 1276 437 1273 429 1278 1312 419 427 1306 407 7921 1315 406 1307 412 434 1292 1287 410 1313 415 431 1302 411 1298 404 1279 434 1274 1316 415 431 1302 411 7901 1314 407 1306 413 433 1294 1285 411 1312 417 429 1304 409 1300 402 1281 432 1275 1315 416 430 1303 410 7917 1308 412 1311 409 426 1274 1305 417 1306 422 434 1274 428 1280 432 1277 436 1297 1283 421 435 1273 429 7906 1309 411 1312 407 428 1274 1306 416 1307 422 434 1274 428 1280 433 1276 426 1281 1309 421 435 1273 429 7901 1313 407 1306 414 432 1270 1310 412 1311 418 428 1280 433 1276 426 1282 431 1276 1314 416 430 1278 435 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1310 411 1312 407 428 1300 1279 417 1306 419 1304 423 433 1302 410 1302 400 1282 1307 421 1302 425 431 7882 1312 409 1304 414 432 1296 1283 413 1310 415 1308 445 401 1308 404 1280 432 1276 1314 414 1309 418 428 7849 1314 406 1306 412 434 1293 1286 410 1302 422 1311 416 430 1304 409 1276 437 1272 1307 420 1303 423 433 7876 1307 413 1309 408 427 1300 1279 416 1307 418 1305 421 435 1299 403 1281 431 1277 1313 415 1308 418 428 7844 1308 411 1302 416 430 1297 1282 413 1310 415 1308 418 428 1305 407 1303 409 1271 1308 419 1304 422 434 7869 1304 416 1307 411 424 1302 1277 418 1305 419 1304 422 434 1299 403 1306 407 1275 1304 422 1301 425 431 7837 1305 414 1309 408 427 1299 1280 414 1309 415 1307 418 428 1305 408 1301 401 1280 1309 416 1307 419 427 7874 1309 410 1303 414 432 1294 1285 409 1303 446 1277 423 433 1299 403 1305 407 1273 1306 420 1303 423 433 7830 1312 407 1305 412 434 1292 1277 417 1306 418 1305 421 425 1307 406 1303 409 1271 1308 417 1305 420 426 7871 1312 407 1306 411 424 1275 1304 416 1307 417 1306 419 427 1280 432 1275 427 1279 1311 415 1308 417 429 7833 1309 410 1303 414 432 1268 1311 408 1305 419 1304 421 425 1282 430 1277 436 1296 1283 416 1307 419 427 7868 1305 414 1309 408 427 1272 1307 412 1310 413 1310 416 430 1277 425 1282 430 1301 1278 421 1302 424 432 7829 1303 415 1308 409 426 1274 1305 414 1308 415 1308 418 428 1279 433 1274 428 1278 1311 413 1310 416 430 7866 1307 411 1301 415 431 1270 1309 410 1303 421 1302 423 433 1275 427 1279 433 1273 1306 419 1304 422 434 7826 1306 413 1310 407 428 1272 1307 412 1311 413 1310 416 430 1277 425 1282 430 1275 1304 421 1302 424 432 7844 1308 410 1302 415 430 1269 1310 410 1302 421 1302 424 432 1274 428 1279 433 1299 1280 419 1304 422 434 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1306 440 1282 436 410 1269 1310 437 1286 442 404 1283 429 1282 430 1282 430 1283 429 1281 1308 419 427 7925 1309 436 1286 406 429 1275 1304 417 1306 447 409 1277 435 1276 436 1276 426 1286 426 1284 1305 448 408 7898 1305 440 1283 435 411 1268 1311 435 1277 450 406 1279 433 1278 434 1277 435 1276 436 1273 1306 448 408 7914 1309 435 1277 440 406 1272 1307 440 1283 444 402 1283 429 1282 430 1281 431 1280 432 1277 1312 442 404 7898 1305 440 1283 436 410 1267 1312 435 1288 440 406 1278 434 1277 435 1276 436 1274 428 1282 1307 446 410 7910 1313 431 1281 437 409 1268 1311 436 1287 440 406 1278 434 1277 435 1275 427 1284 428 1281 1308 445 401 7899 1304 441 1282 436 410 1267 1312 435 1288 439 407 1278 434 1276 436 1275 427 1283 429 1280 1309 444 401 7914 1309 436 1287 431 404 1272 1307 439 1283 443 403 1281 431 1279 433 1277 435 1275 427 1281 1308 445 401 7898 1305 439 1284 434 401 1275 1304 442 1280 446 410 1273 429 1281 431 1279 433 1278 434 1273 1306 448 408 7905 1308 437 1286 432 403 1273 1306 440 1283 444 402 1281 431 1279 433 1276 436 1274 428 1280 1309 444 401 7894 1309 435 1288 430 405 1271 1308 438 1285 442 404 1278 434 1275 437 1273 429 1280 432 1276 1313 440 406 7905 1308 437 1286 431 404 1272 1307 439 1284 442 403 1279 433 1276 436 1273 429 1280 432 1275 1314 439 407 7885 1307 437 1286 431 404 1271 1308 438 1285 442 404 1278 434 1274 428 1282 430 1279 433 1274 1305 448 408 7901 1312 432 1280 436 410 1266 1313 432 1280 446 410 1272 430 1278 434 1275 427 1282 430 1276 1314 440 406 7906 1307 436 1286 431 404 1270 1309 437 1286 440 406 1276 436 1273 429 1279 433 1276 426 1280 1309 444 402 7882 1311 434 1278 438 408 1267 1312 434 1278 447 409 1273 429 1279 433 1275 427 1281 431 1276 1313 439 407 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1316 405 1307 412 434 1270 1309 413 1310 416 1307 421 435 1276 436 1276 436 1275 427 1283 1307 421 1312 7037 1310 411 1312 407 428 1274 1305 418 1305 420 1303 425 431 1278 434 1277 435 1276 436 1274 1305 423 1310 7006 1310 411 1312 406 429 1274 1305 416 1306 419 1304 423 433 1303 409 1275 427 1284 428 1281 1308 420 1303 7043 1314 407 1305 412 434 1295 1284 412 1310 414 1309 419 427 1308 404 1280 432 1279 433 1275 1314 413 1310 7003 1313 407 1305 413 433 1294 1285 411 1301 424 1309 418 428 1306 406 1304 408 1276 436 1273 1306 421 1302 7041 1306 414 1309 410 425 1301 1278 418 1305 420 1303 424 432 1302 400 1310 402 1282 430 1277 1312 415 1307 7003 1313 407 1305 413 433 1294 1285 410 1302 423 1310 416 430 1304 408 1302 400 1284 428 1279 1310 417 1306 7033 1314 406 1307 412 434 1292 1287 408 1304 420 1303 424 432 1301 401 1308 404 1279 433 1274 1305 422 1311 6995 1311 410 1302 415 431 1295 1284 411 1301 423 1310 416 430 1303 409 1299 403 1280 432 1275 1304 422 1301 7034 1313 406 1307 411 424 1301 1278 417 1305 419 1303 423 433 1299 403 1305 407 1275 427 1281 1308 418 1304 6997 1309 411 1301 416 430 1295 1284 411 1301 422 1301 425 431 1301 401 1307 405 1277 435 1271 1308 418 1305 7028 1308 411 1301 415 431 1294 1285 410 1302 422 1301 425 431 1300 402 1306 406 1276 426 1280 1309 416 1307 6993 1312 407 1305 412 434 1291 1278 416 1307 417 1306 420 426 1306 406 1301 401 1281 431 1275 1304 422 1301 7029 1308 411 1301 415 431 1268 1311 409 1303 420 1303 423 433 1273 429 1278 434 1274 428 1277 1312 414 1309 6989 1306 412 1310 406 429 1270 1309 411 1301 422 1301 424 432 1274 428 1279 433 1275 427 1278 1311 414 1309 7019 1307 411 1301 415 431 1268 1311 408 1304 419 1304 421 435 1271 431 1276 426 1282 430 1275 1304 421 1302 6994 1312 406 1306 410 425 1274 1305 414 1309 414 1339 386 429 1277 425 1281 431 1276 426 1305 1284 414 1309 7018 1308 409 1303 413 433 1267 1302 417 1337 386 1337 388 458 1248 433 1273 429 1278 434 1271 1308 416 1338 6956 1308 410 1333 382 464 1236 1333 386 1337 386 1337 388 458 1248 454 1252 429 1277 435 1270 1340 384 1338 6949 1336 382 1330 386 460 1239 1330 388 1335 388 1335 391 455 1250 462 1244 458 1249 463 1241 1338 386 1336 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1289 402 1282 405 442 1268 1263 400 1284 403 433 1276 418 1242 442 1245 439 1248 446 1240 444 1243 1288 7090 1288 402 1282 405 442 1268 1263 400 1284 402 434 1276 418 1241 443 1244 440 1247 447 1240 444 1243 1288 7090 1288 402 1282 405 442 1269 1262 400 1284 402 445 1267 417 1243 441 1246 438 1249 445 1242 442 1245 1286 7092 1286 403 1291 395 441 1244 1287 402 1282 404 443 1242 442 1245 439 1249 445 1242 442 1245 439 1248 1293 7086 1292 398 1286 401 435 1248 1283 407 1287 399 437 1247 437 1250 444 1243 441 1246 438 1249 445 1242 1289 7090 1288 402 1282 405 442 1269 1262 401 1283 403 444 1268 416 1244 440 1247 437 1250 444 1243 441 1247 1284 7096 1293 397 1287 401 435 1275 1256 407 1287 400 436 1274 410 1250 444 1243 441 1247 437 1250 444 1243 1288 7092 1286 404 1280 408 439 1272 1259 403 1281 406 441 1270 414 1246 438 1250 444 1243 441 1246 438 1250 1291 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1291 399 1285 404 443 1243 1288 401 1283 404 443 1243 441 1245 439 1249 1282 407 440 1246 438 1249 445 7935 1291 401 1283 404 443 1268 1263 400 1284 403 444 1268 416 1245 439 1248 1283 408 439 1272 412 1249 445 7935 1291 400 1284 403 444 1268 1263 400 1284 403 433 1277 417 1244 440 1274 1257 407 440 1272 412 1248 446 7935 1290 401 1283 404 443 1242 1289 400 1284 404 443 1242 442 1245 439 1275 1256 407 440 1246 438 1249 445 7936 1290 400 1284 404 443 1243 1288 400 1284 404 443 1243 441 1245 439 1248 1283 407 440 1246 438 1249 445 7935 1290 401 1283 404 443 1268 1263 401 1283 404 443 1268 416 1245 439 1248 1283 408 439 1273 411 1250 444 7937 1288 403 1281 406 441 1271 1260 403 1281 406 441 1270 414 1247 447 1241 1290 400 436 1275 419 1241 443 7940 1286 405 1289 398 438 1273 1258 404 1280 408 439 1272 412 1249 445 1242 1289 402 435 1276 418 1242 442 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1293 395 1289 398 438 1248 1293 395 1289 398 438 1248 446 1241 443 1245 439 1249 1292 396 440 1245 439 7942 1294 395 1289 398 438 1247 1284 404 1290 397 439 1246 438 1250 444 1243 440 1247 1284 405 442 1244 440 7940 1285 404 1290 396 440 1246 1285 403 1291 396 440 1245 438 1249 445 1242 442 1245 1286 403 444 1242 442 7939 1286 402 1292 395 441 1245 1286 402 1292 394 442 1244 440 1248 446 1241 443 1245 1286 402 445 1242 442 7941 1284 404 1290 397 439 1248 1293 395 1289 398 438 1248 446 1242 442 1246 438 1250 1291 396 440 1247 447 7934 1291 398 1286 401 446 1241 1290 399 1285 402 445 1242 442 1246 438 1250 444 1244 1287 401 446 1241 443 7940 1285 403 1291 396 440 1247 1284 404 1290 397 439 1247 437 1251 443 1245 439 1249 1292 396 440 1246 438 7945 1291 398 1286 401 446 1240 1291 398 1286 401 446 1240 444 1244 439 1248 446 1242 1289 399 437 1249 445 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 44 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Mode +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1308 408 1311 402 442 1250 1314 412 1307 405 449 1242 446 1250 449 1246 453 1243 445 1250 449 1248 1316 7148 1306 411 1308 405 449 1242 1311 415 1304 408 446 1246 453 1244 444 1251 448 1247 451 1244 444 1252 1312 7154 1309 406 1303 410 444 1247 1306 420 1309 404 450 1241 447 1249 450 1247 451 1244 444 1252 447 1249 1315 7150 1314 403 1306 407 447 1245 1308 417 1302 412 442 1250 449 1248 450 1246 442 1254 444 1251 448 1249 1304 7160 1314 429 1280 433 421 1248 1305 445 1284 429 415 1252 447 1250 449 1247 441 1254 445 1252 447 1250 1303 7164 1310 404 1305 410 444 1251 1302 420 1309 404 440 1257 441 1255 443 1252 447 1250 449 1248 440 1256 1307 7159 1304 409 1310 403 441 1254 1309 413 1306 407 447 1248 440 1255 443 1252 446 1249 450 1246 442 1253 1362 7102 1310 404 1305 409 445 1251 1302 420 1310 403 441 1254 444 1251 447 1248 440 1255 443 1253 446 1250 1303 7163 1311 403 1306 408 446 1249 1304 418 1301 412 442 1253 446 1250 449 1248 440 1256 442 1254 445 1251 1302 7162 1302 413 1306 407 447 1248 1305 417 1302 412 442 1254 445 1251 448 1248 440 1256 442 1253 446 1249 1304 7155 1308 405 1304 409 445 1251 1302 420 1309 404 440 1255 443 1253 445 1250 448 1248 440 1255 495 1202 1310 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1317 406 1313 409 445 1250 1324 410 1319 403 451 1243 445 1252 447 1250 500 1196 1316 408 446 1248 451 7997 1311 408 1311 407 447 1245 1319 411 1308 408 446 1248 450 1247 452 1246 453 1244 1309 411 443 1251 448 8000 1308 411 1308 408 446 1247 1317 412 1307 410 444 1248 451 1246 442 1253 446 1251 1313 407 447 1246 452 7995 1313 406 1313 404 450 1244 1320 410 1309 409 445 1248 451 1245 454 1243 445 1251 1313 434 420 1245 453 7992 1317 430 1279 437 417 1251 1313 442 1277 439 415 1252 447 1250 449 1248 451 1247 1317 411 454 1241 447 8003 1315 402 1307 407 447 1244 1309 417 1302 411 443 1249 450 1246 442 1253 446 1251 1313 406 448 1272 416 8004 1315 404 1305 410 444 1277 1286 413 1306 408 446 1274 425 1272 416 1253 446 1278 1286 408 446 1274 425 7994 1314 406 1313 406 448 1273 1291 415 1314 406 448 1274 425 1273 415 1253 446 1252 1312 411 443 1278 421 7997 1312 410 1309 409 445 1275 1289 413 1306 412 442 1279 420 1277 422 1275 424 1273 1280 414 440 1253 446 7999 1309 411 1308 409 445 1250 1303 422 1308 409 445 1250 449 1247 441 1255 444 1252 1312 407 447 1248 440 8004 1315 404 1305 412 442 1253 1311 414 1305 410 444 1250 449 1246 442 1253 446 1250 1314 404 440 1257 442 8001 1307 411 1308 408 446 1248 1305 421 1308 407 447 1247 441 1254 445 1252 447 1250 1314 404 440 1257 442 8005 1314 404 1305 413 441 1254 1310 416 1303 413 441 1254 445 1251 448 1248 440 1257 1307 410 444 1252 447 7998 1310 407 1312 405 439 1255 1309 416 1303 411 443 1251 448 1248 440 1254 445 1250 1303 411 443 1251 448 7994 1304 414 1305 411 443 1251 1302 422 1308 408 446 1248 440 1254 445 1251 448 1248 1305 411 443 1253 446 7997 1312 406 1303 413 441 1253 1311 413 1306 408 446 1248 440 1255 444 1253 446 1251 1313 404 440 1256 443 8002 1307 411 1308 408 446 1248 1305 419 1300 414 440 1254 445 1250 449 1247 441 1255 1309 409 445 1249 450 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1313 405 1314 405 449 1250 1313 420 1320 406 448 1252 447 1253 446 1254 445 1255 443 1255 1319 404 450 8002 1316 406 1313 409 445 1253 1311 422 1318 407 499 1202 445 1253 446 1253 445 1254 445 1254 1320 404 450 7999 1309 408 1311 406 448 1248 1305 421 1308 411 443 1253 446 1251 447 1249 449 1248 440 1257 1306 409 445 8000 1308 408 1311 405 449 1247 1306 418 1311 405 449 1248 440 1256 443 1253 446 1251 448 1250 1313 404 450 7996 1312 436 1283 407 447 1250 1313 414 1315 405 449 1250 448 1249 449 1248 450 1246 442 1255 1319 402 452 7991 1307 439 1280 412 442 1255 1308 420 1309 411 443 1255 444 1254 445 1253 446 1253 446 1253 1321 406 448 7998 1320 406 1323 403 451 1249 1314 421 1319 408 446 1254 444 1255 443 1255 443 1256 442 1256 1318 408 446 8004 1314 412 1317 409 445 1255 1318 417 1323 404 450 1250 448 1251 447 1252 447 1253 445 1255 1319 407 447 8003 1315 410 1319 407 447 1252 1322 414 1315 410 444 1256 453 1247 451 1248 450 1247 441 1256 1307 438 416 8002 1316 431 1288 429 415 1252 1311 444 1285 431 423 1241 447 1249 449 1247 451 1248 450 1246 1317 402 442 8002 1316 403 1306 411 443 1251 1312 416 1313 404 450 1244 444 1252 447 1251 447 1249 449 1247 1316 402 442 8002 1316 403 1306 410 444 1250 1313 414 1305 410 444 1249 450 1246 452 1244 444 1253 446 1252 1311 407 447 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1292 433 1265 432 406 1261 1286 438 1260 436 413 1255 443 1253 444 1252 435 1260 438 1259 439 1257 1290 7192 1288 436 1262 434 415 1279 1257 440 1258 438 411 1284 414 1256 442 1254 444 1253 445 1252 435 1261 1286 7196 1284 440 1258 438 411 1257 1290 434 1264 432 417 1252 435 1260 438 1259 439 1257 441 1255 443 1253 1283 7198 1292 431 1256 440 409 1259 1288 436 1262 434 415 1253 434 1261 437 1259 439 1257 441 1255 443 1253 1283 7198 1292 431 1256 439 410 1258 1289 435 1263 433 416 1252 435 1261 437 1258 440 1256 442 1254 444 1252 1284 7198 1292 405 1282 440 409 1258 1289 435 1263 433 416 1252 435 1260 438 1258 440 1256 442 1254 444 1252 1284 7196 1284 440 1258 438 411 1257 1290 433 1265 431 407 1260 438 1284 414 1256 442 1254 444 1252 435 1260 1287 7195 1285 438 1260 436 413 1255 1292 405 1282 440 409 1259 439 1257 441 1255 443 1254 444 1252 435 1260 1287 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1287 410 1288 435 414 1281 1255 414 1284 439 410 1259 439 1283 415 1254 444 1252 435 1261 1286 437 412 8042 1287 410 1288 434 415 1255 1281 440 1258 438 411 1258 440 1255 443 1253 434 1261 437 1259 1288 408 441 8039 1290 406 1292 431 407 1262 1285 411 1287 435 414 1256 442 1253 445 1251 436 1260 438 1258 1289 434 415 8039 1290 406 1281 441 408 1287 1260 409 1289 434 415 1254 433 1261 437 1259 439 1283 415 1255 1292 405 433 8046 1283 413 1285 438 411 1285 1262 434 1264 431 407 1288 410 1259 439 1257 441 441 1253 1283 413 436 8044 1285 412 1286 436 413 1257 1290 405 1282 440 409 1287 411 1284 414 1281 417 1253 434 1261 1286 437 412 8042 1287 436 1262 434 415 1281 1256 440 1258 411 438 1258 440 1255 443 1253 434 1261 437 1258 1289 434 415 8039 1290 406 1281 440 409 1287 1260 409 1289 433 416 1253 434 1261 437 1259 439 1256 442 1254 1293 404 434 8046 1283 413 1285 411 438 1257 1290 406 1281 414 435 1287 411 1257 441 1255 443 1253 434 1261 1286 438 411 8042 1287 409 1289 433 416 1280 1257 439 1259 436 413 1256 442 1253 445 1251 436 1286 412 1283 1264 433 416 8037 1292 404 1283 412 437 1285 1262 408 1290 432 417 1278 409 1259 439 1257 441 1255 443 1253 1283 439 410 8043 1285 437 1261 435 414 1281 1255 440 1258 438 411 1283 415 1254 444 1252 435 1260 438 1258 1289 434 414 8038 1291 432 1255 440 409 1286 1261 435 1263 433 416 1251 436 1259 439 1257 441 1255 443 1253 1283 440 409 8044 1284 411 1287 436 413 1254 1293 431 1256 439 410 1257 441 1281 417 1253 434 1261 437 1259 1288 435 413 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1288 437 1260 435 414 1254 1292 431 1256 440 409 1259 438 1258 1288 435 413 1255 442 1253 444 1252 435 8046 1292 431 1256 440 409 1259 1288 436 1261 434 414 1254 443 1253 1283 440 409 1259 438 1258 439 1256 441 8040 1287 436 1261 434 414 1254 1292 431 1256 439 409 1258 491 1204 1291 433 415 1253 434 1261 436 1260 438 8044 1284 440 1257 438 410 1257 1290 434 1263 432 417 1251 436 1259 1287 436 412 1255 442 1254 443 1252 435 8045 1293 431 1256 439 410 1258 1289 435 1262 433 416 1253 434 1261 1285 439 410 1258 440 1256 442 1255 442 8038 1290 433 1264 431 407 1260 1287 437 1261 435 413 1254 443 1253 1283 440 408 1259 439 1257 440 1255 442 8038 1289 434 1263 432 417 1251 1285 438 1260 436 413 1255 442 1253 1283 441 408 1260 438 1258 440 1256 442 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1289 408 1289 434 414 1245 1291 433 1264 432 416 1270 417 1243 444 1243 444 1243 444 1244 443 1244 1292 7134 1291 405 1292 431 417 1242 1294 429 1268 427 411 1248 439 1248 439 1274 413 1247 440 1247 440 1247 1289 8241 1295 400 1287 436 412 1247 1289 434 1263 432 416 1242 445 1242 445 1242 445 1241 446 1241 435 1251 1295 7130 1295 400 1287 436 412 1246 1290 406 1291 431 417 1268 419 1241 446 1241 435 1251 436 1277 410 1250 1286 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1369 328 1296 427 411 1275 1261 436 1261 434 414 1245 442 1245 442 1244 443 1244 443 1244 1291 431 417 7972 1291 404 1293 429 409 1251 1295 400 1287 436 412 1273 414 1246 441 1245 442 1245 442 1245 1290 405 443 9076 1287 408 1289 433 415 1271 1264 430 1267 428 410 1276 411 1249 438 1248 439 1248 439 1274 1261 434 414 7974 1289 406 1291 431 417 1242 1294 428 1259 436 412 1273 414 1273 414 1245 442 1245 442 1244 1292 430 418 +# +name: Power +type: parsed +protocol: NECext +address: 00 FC 00 00 +command: 80 7F 00 00 +# +name: Speed_up +type: parsed +protocol: NECext +address: 00 FC 00 00 +command: 85 7A 00 00 +# +name: Mode +type: parsed +protocol: NECext +address: 00 FC 00 00 +command: 81 7E 00 00 +# +name: Timer +type: parsed +protocol: NECext +address: 00 FC 00 00 +command: 86 79 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1299 409 1271 412 433 1211 1293 416 1274 409 436 1207 462 1207 462 1208 461 1208 461 1209 460 1209 1296 7131 1300 410 1270 386 459 1210 1294 389 1301 382 463 1206 464 1206 463 1207 463 1208 462 1208 461 1209 1295 8208 1302 382 1298 385 460 1208 1297 387 1293 390 455 1213 456 1214 466 1205 465 1205 464 1206 463 1206 1298 7129 1302 382 1298 385 460 1208 1297 387 1293 390 455 1213 457 1213 467 1204 465 1205 464 1205 464 1206 1298 8207 1293 391 1299 383 452 1218 1297 386 1294 388 457 1213 456 1214 455 1215 454 1216 454 1216 453 1217 1298 7130 1290 415 1275 406 429 1219 1296 409 1271 411 434 1212 457 1213 456 1213 456 1214 455 1188 481 1215 1299 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1296 386 1294 414 431 1212 1292 416 1274 409 436 1206 463 1207 462 1207 463 1207 463 1208 1297 411 434 7953 1292 417 1273 408 437 1206 1298 411 1269 413 432 1210 459 1210 459 1211 458 1211 458 1211 1293 415 430 9123 1293 389 1301 407 438 1205 1299 410 1269 412 433 1209 460 1210 459 1210 459 1210 459 1210 1294 388 457 7957 1298 410 1270 413 432 1211 1293 389 1301 408 437 1205 464 1206 463 1206 463 1207 463 1207 1297 385 460 9120 1296 387 1293 416 429 1214 1301 381 1299 410 435 1208 461 1209 460 1209 460 1210 459 1210 1294 414 431 7958 1297 385 1295 414 431 1212 1303 380 1300 409 436 1207 462 1207 463 1207 462 1209 460 1208 1296 386 459 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1295 410 1270 411 434 1210 1294 412 1268 414 431 1238 431 1212 1302 405 429 1214 455 1266 1249 406 428 7959 1296 410 1270 412 433 1210 1294 413 1267 414 431 1213 456 1213 1301 405 429 1214 455 1266 1249 406 428 9065 1299 406 1274 408 437 1207 1297 409 1271 411 434 1236 433 1210 1294 412 433 1211 458 1264 1240 414 431 7958 1297 409 1271 411 434 1209 1295 412 1268 414 431 1238 431 1212 1302 405 429 1214 455 1267 1248 407 427 9067 1298 383 1297 410 435 1209 1295 412 1268 414 431 1238 431 1212 1292 415 430 1214 455 1266 1249 407 427 7962 1293 414 1276 406 428 1215 1300 408 1272 410 435 1235 434 1209 1295 411 434 1210 459 1262 1242 413 432 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1300 381 1299 410 435 1208 1296 413 1267 415 430 1213 456 1213 456 1214 455 1214 1301 408 437 1207 462 7950 1295 413 1267 416 429 1214 1301 381 1299 410 435 1208 461 1208 461 1208 461 1208 1296 386 459 1211 458 9008 1296 387 1293 415 430 1213 1301 407 1273 410 435 1208 461 1208 461 1208 461 1208 1296 386 459 1210 459 7952 1293 416 1274 408 437 1206 1298 384 1296 413 432 1211 458 1211 458 1211 458 1211 1304 405 429 1214 455 9013 1301 407 1273 410 435 1259 1245 412 1268 415 430 1264 405 1213 456 1213 456 1213 1301 407 438 1256 403 7958 1297 410 1270 413 432 1262 1253 405 1275 407 427 1267 402 1216 453 1216 453 1216 1298 409 436 1259 410 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1294 412 1268 415 430 1265 1250 408 1272 411 434 1260 409 1210 1294 413 432 1264 405 1213 456 1215 454 7959 1296 411 1269 415 430 1265 1250 408 1271 410 435 1260 409 1234 1270 413 432 1264 405 1238 431 1214 455 9016 1298 410 1270 414 431 1264 1251 407 1273 409 436 1260 409 1260 1244 413 432 1263 406 1237 433 1213 456 7959 1296 412 1268 415 430 1265 1250 407 1273 410 435 1260 409 1234 1270 414 431 1264 405 1214 455 1215 454 9017 1297 411 1269 414 431 1264 1251 407 1273 410 435 1260 409 1209 1295 387 458 1237 432 1213 456 1214 456 7959 1296 413 1267 416 429 1239 1276 409 1271 412 433 1235 434 1211 1304 379 455 1239 430 1215 454 1216 453 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1294 388 1291 390 455 1215 1289 392 1298 384 451 1220 460 1210 459 1210 1294 388 457 1212 457 1212 457 7954 1301 382 1298 384 461 1207 1297 387 1293 389 456 1212 457 1212 457 1213 1291 392 463 1205 465 1205 464 9263 1295 414 1266 417 428 1214 1301 409 1271 412 433 1209 460 1209 460 1210 1294 415 430 1213 456 1213 456 7959 1296 413 1267 415 430 1213 1302 408 1271 411 434 1208 461 1209 460 1209 1295 414 431 1212 457 1213 456 9272 1297 412 1268 415 430 1212 1292 417 1273 410 435 1207 462 1208 461 1208 1296 413 432 1211 458 1211 458 7956 1299 410 1270 413 432 1210 1294 390 1300 409 436 1206 463 1207 463 1207 1298 387 458 1210 459 1211 458 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1251 410 1245 415 407 1252 1246 415 1250 410 412 1247 408 1252 413 1247 408 1252 413 1247 408 1252 1246 7350 1248 438 1227 433 389 1245 1253 434 1221 439 383 1250 415 1245 410 1250 405 1255 410 1249 406 1255 1253 7340 1247 439 1226 407 415 1245 1253 433 1222 412 410 1250 405 1255 410 1249 406 1254 411 1249 406 1254 1254 7340 1247 412 1253 407 415 1245 1253 407 1248 412 410 1250 405 1255 410 1249 406 1254 411 1248 407 1253 1245 7349 1249 411 1254 406 405 1254 1254 406 1249 411 411 1248 407 1252 413 1246 409 1251 414 1245 410 1250 1248 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1250 409 1246 441 381 1252 1246 414 1252 435 387 1246 409 1250 405 1255 1243 417 405 1281 384 1275 380 8188 1244 416 1249 437 385 1249 1249 437 1218 443 379 1253 412 1248 407 1253 1245 415 407 1252 413 1246 409 8186 1246 414 1251 435 387 1272 1226 408 1247 439 383 1276 379 1281 384 1249 1249 437 385 1248 407 1253 412 8181 1251 409 1246 440 382 1277 1221 439 1226 434 378 1255 410 1249 406 1254 1244 416 406 1253 412 1247 408 8187 1245 441 1225 435 387 1273 1225 435 1220 440 382 1251 414 1245 410 1250 1248 412 410 1249 406 1254 411 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1243 444 1221 438 384 1276 1222 438 1217 443 379 1254 411 1274 381 1252 413 1246 409 1251 1247 413 409 8185 1247 440 1225 435 387 1246 1252 408 1247 440 382 1277 388 1245 410 1276 379 1280 385 1249 1249 411 411 8184 1248 412 1243 444 378 1282 1216 444 1221 439 383 1250 405 1254 411 1248 407 1253 412 1248 1250 410 412 8182 1250 410 1245 442 380 1253 1245 415 1250 437 385 1247 408 1252 413 1272 383 1251 414 1272 1226 408 414 8180 1252 409 1246 440 382 1252 1246 440 1225 435 387 1272 383 1250 405 1254 411 1249 406 1254 1244 416 406 8188 1244 416 1249 437 385 1249 1249 411 1244 443 379 1253 412 1273 382 1278 387 1246 409 1251 1247 413 409 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1305 379 1283 402 434 1251 431 1253 1276 408 1275 411 435 1249 1280 404 432 1253 1276 408 438 1247 1282 7144 1281 404 1279 407 439 1247 435 1250 1310 375 1307 379 467 1218 1311 374 431 1254 1306 379 436 1249 1311 7119 1305 380 1302 382 464 1222 460 1224 1305 380 1302 382 464 1221 1308 377 469 1217 1302 383 463 1222 1307 7121 1303 380 1303 382 464 1221 462 1223 1306 378 1305 381 465 1220 1309 375 430 1254 1306 380 435 1250 1279 7148 1276 408 1274 411 435 1250 432 1252 1277 408 1274 410 436 1249 1280 404 432 1253 1276 408 438 1247 1282 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1282 402 1281 404 432 1252 430 1254 1275 408 1275 410 436 1248 435 1249 434 1251 1278 405 431 1254 439 7984 1277 407 1276 408 438 1246 436 1248 1281 402 1281 403 433 1251 432 1253 430 1254 1275 408 438 1246 437 7985 1276 407 1276 408 438 1246 436 1248 1281 402 1281 403 433 1252 431 1253 440 1245 1274 409 437 1248 435 7990 1333 351 1332 352 433 1252 431 1254 1326 358 1335 349 436 1249 434 1250 433 1252 1328 355 440 1244 438 7984 1328 355 1328 356 439 1245 438 1247 1333 350 1333 351 434 1250 433 1252 431 1253 1327 357 438 1246 437 7988 1273 410 1273 411 435 1249 434 1250 1279 405 1278 406 440 1244 439 1246 436 1247 1282 402 434 1251 432 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1282 403 1279 405 431 1254 439 1246 1273 412 434 1251 432 1254 1275 410 436 1249 433 1251 431 1254 1275 7152 1283 402 1281 405 431 1254 439 1246 1273 412 434 1251 432 1254 1305 379 436 1249 433 1252 430 1254 1306 7122 1302 382 1311 374 462 1223 459 1226 1303 381 465 1221 461 1223 1306 379 467 1218 464 1221 461 1224 1305 7123 1312 373 1310 375 461 1224 469 1216 1303 381 465 1221 461 1223 1306 379 467 1218 464 1221 461 1224 1305 7123 1312 374 1308 376 460 1225 468 1218 1311 374 462 1223 459 1226 1303 382 464 1222 460 1224 469 1217 1302 7128 1306 379 1303 382 464 1221 461 1224 1305 380 466 1219 463 1221 1277 408 469 1217 465 1219 463 1222 1307 7122 1282 403 1279 405 462 1223 459 1226 1283 402 465 1221 461 1224 1274 410 467 1218 464 1221 461 1224 1274 7155 1280 405 1278 408 438 1247 435 1250 1279 406 440 1245 437 1249 1280 405 431 1255 438 1249 433 1252 1277 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1279 406 1307 377 438 1246 436 1249 1311 373 463 1221 462 1224 469 1216 466 1218 465 1220 463 1222 460 7967 1304 380 1303 382 464 1221 462 1223 1276 409 468 1217 466 1220 463 1223 459 1225 468 1217 465 1220 462 7966 1275 410 1303 382 464 1220 463 1222 1276 409 468 1218 465 1220 463 1223 459 1225 468 1217 435 1250 463 7965 1306 378 1304 381 465 1220 463 1222 1307 378 468 1217 465 1219 433 1253 440 1245 437 1248 434 1251 462 7966 1275 411 1282 404 432 1254 439 1248 1281 404 432 1254 439 1247 436 1250 433 1253 440 1246 437 1248 435 7995 1276 409 1284 402 434 1252 431 1255 1274 411 435 1251 431 1254 439 1247 436 1250 433 1253 440 1246 436 7992 1279 407 1276 410 436 1249 433 1252 1277 409 447 1238 434 1252 431 1254 439 1247 435 1250 433 1253 440 7990 1281 404 1278 406 440 1245 438 1249 1280 405 441 1245 437 1248 435 1251 432 1254 439 1247 435 1250 433 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 01 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 05 00 00 00 +# +name: Speed_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1B 00 00 00 +#Timer DOWN +name: Timer +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 09 00 00 00 +#Rotate +name: Rotate +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 03 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1397 357 1370 357 500 1188 1427 331 1343 383 473 1240 476 1240 476 1240 475 1240 475 1240 475 1240 1370 7358 1367 361 1366 361 496 1220 1366 361 1366 361 496 1220 495 1220 495 1220 495 1221 494 1220 495 1220 1366 7361 1365 361 1366 361 495 1221 1365 362 1365 362 494 1221 494 1221 494 1220 495 1220 495 1221 494 1220 1365 7361 1364 361 1366 362 494 1221 1365 362 1365 362 494 1221 494 1221 494 1221 494 1221 494 1221 494 1221 1365 7361 1364 362 1364 362 494 1221 1364 362 1365 362 495 1221 494 1221 494 1221 494 1221 494 1221 494 1221 1364 7361 1364 363 1364 363 493 1222 1364 363 1363 363 493 1223 492 1223 492 1223 492 1247 468 1223 492 1247 1339 7386 1338 388 1338 388 468 1247 1339 388 1338 388 468 1248 467 1247 468 1247 468 1247 468 1248 467 1247 1338 7387 1337 389 1338 389 467 1248 1338 389 1337 389 467 1248 467 1248 467 1248 467 1248 467 1248 467 1248 1337 7388 1336 389 1337 389 467 1249 1336 390 1337 390 466 1249 466 1249 466 1249 466 1249 466 1249 466 1248 1337 7388 1312 414 1312 414 465 1251 1335 391 1337 390 441 1274 441 1274 465 1249 464 1250 442 1274 441 1273 1337 7388 1311 414 1312 415 441 1274 1311 415 1311 415 441 1274 441 1274 441 1274 441 1274 441 1274 441 1274 1311 7413 1311 415 1312 415 441 1274 1311 415 1311 416 440 1275 440 1275 440 1275 440 1275 440 1275 439 1275 1310 7414 1309 416 1310 417 439 1276 1310 417 1309 417 438 1277 438 1277 438 1277 438 1301 413 1301 414 1301 1285 7439 1284 442 1284 442 414 1301 1284 443 1283 443 413 1302 413 1302 413 1302 413 1302 412 1302 413 1302 1283 7441 1283 443 1284 443 412 1303 1283 444 1282 444 412 1303 411 1303 412 1303 412 1303 412 1303 412 1303 1283 7441 1282 445 1281 445 411 1304 1282 470 1256 471 385 1330 385 1330 385 1330 385 1330 385 1330 385 1330 1256 7468 1255 471 1256 471 384 1331 1255 471 1255 472 383 1331 384 1331 383 1332 383 1332 383 1331 383 1332 1254 7470 1253 498 1228 499 356 1358 1228 499 1227 499 356 1359 356 1359 356 1359 356 1359 355 1360 355 1359 1227 7497 1226 526 1200 527 327 1387 1200 527 1199 554 300 1414 301 1415 299 1415 299 1415 300 1416 299 1415 1172 7553 1170 609 1117 583 270 1471 1117 637 1089 692 118 10334 871 +#Osc +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1395 358 1369 358 499 1215 1399 331 1341 384 472 1241 474 1240 475 1240 1345 383 473 1240 474 1240 474 8239 1366 387 1339 387 469 1246 1339 388 1339 387 469 1246 469 1246 469 1246 1339 388 469 1246 469 1246 469 8242 1339 387 1339 387 469 1246 1339 387 1339 387 469 1246 469 1246 469 1246 1339 387 469 1246 469 1246 468 8243 1338 388 1338 388 469 1246 1339 388 1338 388 468 1246 468 1246 469 1247 1338 388 468 1246 468 1247 468 8243 1338 388 1338 388 468 1246 1338 388 1338 388 468 1246 469 1246 468 1246 1339 387 469 1246 468 1246 468 8218 1363 363 1363 363 493 1222 1363 363 1363 363 493 1222 493 1222 492 1222 1363 363 493 1221 493 1222 493 8217 1363 363 1363 363 493 1222 1363 363 1363 363 493 1246 468 1223 492 1223 1361 375 481 1247 467 1247 467 8243 1337 388 1338 388 468 1247 1337 389 1337 388 468 1247 468 1247 468 1247 1337 389 467 1248 466 1248 466 8243 1337 389 1337 389 467 1248 1336 389 1337 390 466 1248 466 1248 466 1248 1336 390 466 1248 466 1248 466 8244 1336 390 1311 415 465 1249 1336 390 1336 391 465 1250 465 1249 465 1250 1310 415 465 1250 464 1250 439 8270 1310 416 1310 416 440 1275 1310 417 1309 417 438 1300 414 1301 413 1301 1284 442 414 1301 413 1301 413 8297 1284 442 1284 442 413 1301 1284 443 1283 443 413 1302 412 1302 412 1302 1282 443 413 1302 412 1302 412 8298 1282 443 1283 443 413 1302 1283 443 1283 444 412 1303 411 1303 411 1303 1282 444 412 1303 411 1303 411 8299 1280 445 1281 470 385 1329 1255 470 1256 471 384 1330 384 1329 385 1329 1255 471 385 1330 384 1330 384 8326 1253 472 1254 472 384 1331 1253 473 1253 499 356 1357 357 1358 356 1358 1226 499 356 1358 356 1359 355 8355 1224 502 1224 501 355 1385 1199 527 1199 527 328 1386 328 1386 328 1386 1199 527 328 1387 327 1387 327 8409 1171 554 1172 555 300 1414 1172 555 1171 555 300 1416 299 1416 298 1415 1171 556 298 1442 272 1442 272 8438 1143 583 1143 583 271 1471 1115 611 1115 664 179 1536 178 1563 122 1619 1006 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1395 359 1367 358 499 1213 1368 359 1366 359 473 1239 474 1239 474 1238 475 1238 1368 360 497 1215 497 8209 1362 364 1361 366 492 1221 1360 366 1359 366 491 1222 492 1222 491 1221 493 1245 1336 366 492 1222 492 8213 1360 366 1359 389 468 1222 1359 367 1358 390 467 1246 468 1246 468 1245 468 1246 1335 390 467 1246 467 8238 1335 390 1335 390 468 1246 1335 390 1335 390 467 1246 467 1246 468 1246 467 1246 1335 390 467 1246 467 8238 1334 390 1335 390 467 1247 1334 390 1335 391 466 1247 466 1247 466 1247 466 1247 1334 390 467 1247 466 8239 1334 391 1334 391 466 1247 1334 391 1334 391 467 1247 466 1247 466 1247 466 1248 1333 391 466 1248 465 8239 1333 392 1333 392 466 1248 1333 392 1333 392 465 1248 465 1248 465 1248 465 1248 1333 392 465 1248 465 8240 1332 392 1333 392 465 1248 1333 393 1332 393 464 1249 464 1249 464 1249 464 1249 1331 393 465 1249 464 8241 1331 393 1332 393 464 1250 1331 394 1331 394 463 1250 463 1251 462 1250 463 1251 1329 396 462 1251 462 8243 1305 420 1305 444 436 1277 1280 444 1281 445 435 1277 412 1301 436 1277 435 1277 1280 445 436 1277 436 8268 1280 445 1279 445 412 1301 1280 445 1280 445 411 1302 411 1302 411 1302 411 1302 1279 446 411 1302 411 8293 1278 446 1279 446 411 1303 1278 447 1277 447 410 1303 410 1304 409 1329 384 1329 1252 472 385 1329 384 8320 1252 473 1251 473 383 1330 1251 473 1252 473 384 1330 383 1330 383 1330 383 1330 1251 474 382 1330 383 8321 1250 474 1251 475 381 1331 1250 500 1224 500 356 1358 355 1358 355 1358 355 1358 1223 501 355 1358 355 8349 1223 502 1222 528 327 1386 1196 528 1196 530 326 1386 327 1387 326 1386 327 1413 1169 556 299 1414 299 8404 1169 556 1168 584 271 1441 1141 611 1113 637 216 1498 215 1524 178 1562 122 1564 1058 11171 970 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1366 359 1366 360 496 1214 1367 360 1395 331 524 1185 474 1239 474 1238 475 1238 475 1238 1343 383 498 8205 1366 386 1338 386 470 1244 1338 386 1339 386 470 1243 470 1244 469 1244 469 1243 470 1244 1338 386 470 8234 1338 386 1339 386 469 1244 1338 386 1339 386 470 1243 470 1220 493 1219 494 1243 470 1244 1338 362 494 8233 1339 362 1363 386 469 1244 1338 386 1339 386 469 1244 469 1244 469 1244 469 1244 469 1244 1338 387 468 8234 1338 386 1338 387 468 1244 1338 387 1337 387 468 1244 469 1244 468 1245 469 1244 469 1244 1338 387 468 8234 1337 387 1338 387 465 1247 1338 387 1338 387 468 1245 468 1244 444 1269 468 1246 467 1245 1337 387 468 8235 1337 387 1337 388 468 1245 1336 388 1336 388 466 1246 467 1245 468 1245 467 1247 467 1245 1312 412 467 8235 1312 412 1312 412 443 1270 1335 390 1311 412 468 1246 466 1246 467 1246 467 1246 467 1246 1311 412 467 8236 1311 413 1311 413 442 1271 1311 413 1311 413 442 1271 466 1247 466 1246 442 1271 442 1271 1311 413 442 8260 1311 413 1311 413 442 1271 1311 413 1311 414 441 1271 442 1271 465 1248 464 1249 465 1247 1310 414 441 8261 1334 390 1310 414 466 1247 1335 390 1333 391 465 1248 465 1248 465 1248 465 1248 465 1248 1334 390 465 8237 1334 391 1333 390 465 1248 1309 415 1309 416 464 1249 464 1249 463 1250 462 1275 438 1274 1307 394 438 8287 1283 441 1283 441 438 1274 1283 441 1283 442 436 1276 414 1299 438 1275 413 1300 412 1300 1282 442 413 8289 1282 443 1281 443 412 1301 1281 443 1281 443 412 1301 411 1302 411 1302 410 1327 385 1327 1255 469 386 8316 1255 470 1254 470 385 1327 1255 470 1254 470 385 1327 385 1328 384 1328 385 1328 385 1328 1254 470 385 8317 1253 471 1253 470 385 1329 1253 471 1253 471 384 1329 383 1330 382 1330 382 1330 383 1330 1252 473 382 8344 1226 498 1226 498 357 1356 1226 498 1226 498 356 1356 356 1356 356 1356 356 1356 356 1356 1226 499 355 8346 1224 499 1225 525 329 1384 1198 526 1198 525 329 1384 328 1384 328 1384 328 1384 329 1384 1198 526 328 8373 1198 526 1198 527 327 1385 1197 553 1171 553 301 1412 300 1412 300 1412 300 1386 327 1412 1170 554 299 8401 1170 554 1170 555 298 1414 1169 581 1143 582 271 1440 272 1440 272 1440 272 1441 271 1441 1142 609 244 8458 1113 636 1088 663 178 1507 1088 691 1033 +#Timer OFF +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3583 1639 534 386 484 1208 534 386 484 386 485 386 484 387 483 386 484 386 485 386 457 394 476 394 451 419 452 419 476 1237 506 393 477 394 475 395 474 396 473 397 473 398 472 1270 472 399 471 1271 471 1270 471 399 470 401 469 1273 469 402 468 427 443 427 443 427 443 1299 443 427 443 427 444 427 444 427 444 1298 443 1299 442 427 444 427 445 426 445 425 446 425 447 424 447 399 472 398 473 1294 445 426 446 400 471 399 472 1294 446 425 447 398 473 1295 446 400 471 398 472 74544 3552 1672 472 399 471 1271 471 399 471 400 470 400 470 400 470 400 471 400 470 400 470 401 470 401 469 401 469 401 470 1273 470 400 470 401 469 401 469 401 470 401 469 401 470 1273 470 401 469 1273 470 1272 469 401 469 401 469 1274 468 401 469 401 469 401 470 402 469 1273 469 401 469 401 469 402 469 401 469 1273 469 1273 469 401 469 402 468 402 469 426 444 427 444 427 444 426 445 426 444 1274 468 402 469 426 444 427 443 1275 467 403 467 427 443 1275 467 402 468 427 443 +#OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1366 388 1295 387 446 1164 1368 387 1296 387 448 1164 518 1164 493 1189 517 1165 492 1189 493 1189 493 7851 1365 387 1270 388 446 1190 1365 387 1295 387 447 1192 490 1193 489 1193 489 1194 488 1194 488 1193 489 7855 1337 386 1296 386 448 1193 1338 386 1296 386 448 1193 489 1193 489 1193 489 1193 489 1193 489 1193 489 7855 1337 386 1297 386 448 1194 1337 386 1296 386 448 1194 488 1194 488 1194 488 1194 488 1194 488 1194 488 8160 1336 387 1296 387 447 1194 1337 387 1296 386 448 1194 488 1194 488 1194 488 1194 488 1194 488 1194 488 7855 1336 386 1297 387 447 1195 1336 386 1297 386 448 1195 486 1195 487 1195 487 1195 487 1196 486 1196 486 7881 1310 387 1272 397 436 1245 1286 397 1285 397 436 1246 436 1246 436 1247 435 1247 435 1248 434 1248 434 7934 1259 424 1259 424 408 1274 1258 424 1259 424 408 1274 408 1274 408 1274 408 1274 408 1274 408 1274 408 8239 1258 425 1258 424 408 1274 1258 424 1258 425 407 1273 408 1273 409 1273 408 1273 408 1274 408 1273 408 7907 1283 424 1258 424 409 1248 1283 399 1283 400 433 1247 434 1247 434 1247 434 1247 434 1247 434 1247 434 7905 1282 424 1258 425 407 1273 1257 425 1257 425 407 1273 407 1274 407 1274 407 1274 407 1275 406 1275 406 7958 1230 478 1204 478 353 1328 1204 478 1204 453 378 1327 353 1328 353 1302 379 1301 380 1301 380 1300 381 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1341 337 1339 338 496 1178 1395 305 1316 337 1339 338 496 1178 526 1151 526 1151 1366 335 1341 335 498 7888 1338 338 1337 339 495 1182 1336 340 1336 340 1336 340 494 1182 494 1182 494 1183 1336 340 1336 340 494 7892 1336 340 1336 340 494 1183 1336 340 1336 340 1336 340 494 1183 494 1183 494 1183 1335 341 1335 340 494 7893 1335 341 1335 341 493 1183 1336 341 1335 341 1335 341 493 1183 493 1183 494 1183 1335 341 1335 341 493 7893 1335 341 1335 341 493 1184 1334 341 1335 341 1335 341 493 1184 493 1184 493 1184 1335 341 1335 342 492 7894 1334 342 1334 342 492 1184 1334 342 1334 341 1335 342 492 1184 493 1184 492 1185 1334 342 1334 342 492 7894 1334 342 1334 342 492 1185 1333 343 1333 342 1334 342 492 1185 492 1185 492 1185 1333 343 1333 343 491 7895 1333 343 1333 343 491 1185 1333 343 1333 344 1332 344 490 1186 490 1186 491 1186 1333 344 1332 344 490 7897 1331 368 1308 368 466 1211 1308 368 1308 368 1308 368 466 1211 465 1211 466 1211 1307 368 1308 369 465 7921 1307 368 1308 368 466 1211 1308 368 1308 369 1307 368 466 1211 466 1211 465 1211 1308 369 1307 369 465 7921 1307 369 1307 369 465 1212 1307 369 1307 369 1307 369 465 1212 465 1212 465 1212 1307 369 1307 369 465 7922 1306 370 1306 370 464 1212 1307 370 1306 370 1306 370 464 1212 464 1213 464 1212 1306 370 1306 370 464 7922 1306 370 1306 371 463 1213 1306 370 1306 371 1305 371 463 1213 439 1238 439 1238 1305 371 1305 371 462 7924 1280 396 1280 396 438 1238 1281 396 1280 396 1280 396 438 1239 437 1239 438 1239 1280 396 1280 397 437 7949 1279 397 1279 397 437 1240 1279 398 1278 422 1254 422 412 1265 411 1265 412 1265 1254 422 1254 422 412 7974 1254 422 1254 422 412 1265 1254 423 1253 423 1253 449 384 1293 383 1293 383 1294 1225 451 1225 452 382 8056 1171 505 1171 532 188 1463 1169 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1349 364 1321 363 490 1219 1324 364 1294 389 463 1245 1324 363 462 1219 491 1221 462 1223 461 1226 484 8019 1292 393 1292 420 433 1252 1292 393 1292 420 433 1252 1292 393 460 1252 433 1252 460 1226 459 1252 433 8021 1317 393 1292 394 459 1226 1318 393 1292 394 458 1226 1318 394 432 1253 459 1226 459 1253 432 1253 459 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1376 337 1348 363 462 1219 1325 390 1295 388 1322 363 490 1218 466 1219 465 1220 490 1222 1319 369 1316 7187 1292 393 1292 393 460 1252 1292 370 1315 393 1292 420 433 1252 433 1252 460 1252 433 1252 1292 393 1292 7188 1291 393 1318 393 433 1252 1292 393 1319 393 1291 393 460 1226 459 1252 432 1253 459 1225 1319 393 1292 +# +name: Power +type: parsed +protocol: NECext +address: 00 F3 00 00 +command: 91 6E 00 00 +# +name: Timer +type: parsed +protocol: NECext +address: 00 F3 00 00 +command: 96 69 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9253 4427 684 486 656 486 656 486 682 461 681 1573 680 1575 678 464 677 491 651 1604 650 1604 650 1604 650 1604 650 491 651 491 651 1604 650 1604 651 491 651 491 651 1604 650 1604 650 491 651 491 651 491 652 1604 650 1604 651 1604 650 491 651 491 652 1604 650 1604 650 1604 651 491 651 39948 9250 2183 651 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9226 4450 657 484 657 484 658 484 657 485 657 1596 658 1597 681 487 653 488 653 1602 652 1602 652 1602 652 1602 652 490 652 490 652 1602 652 1602 652 490 652 490 652 490 652 1603 652 490 652 490 652 490 652 1602 652 1602 652 1602 653 1602 652 490 652 1602 652 1602 652 1602 653 489 653 39949 9250 2179 653 +#OSC +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9231 4449 657 484 658 483 659 483 659 483 659 1595 659 1595 659 485 681 461 680 1601 652 1602 653 1601 653 1601 653 488 654 488 654 1602 653 1601 653 488 654 488 654 1602 653 1602 652 1602 652 488 654 488 654 1602 653 1601 653 1602 652 488 654 488 654 488 654 1602 652 1602 653 488 654 39978 9229 2174 654 96468 9259 2146 679 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9384 4452 658 485 657 484 658 484 659 485 657 1597 658 1597 682 487 655 488 654 1603 652 1603 653 1603 653 1604 653 491 653 491 653 1603 653 1603 653 1603 653 491 653 1604 652 490 654 1603 652 490 653 490 653 1603 652 490 653 1603 652 490 653 1603 652 490 653 1603 652 1603 652 490 653 39953 9263 2181 652 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 83 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 87 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 8B 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2256 695 788 1354 789 1349 789 1340 792 702 762 697 763 692 789 661 788 720 786 693 786 689 785 685 784 681 784 676 784 1334 784 1330 783 102265 2255 695 787 1356 786 1352 785 1348 785 681 783 676 783 671 784 666 784 724 784 696 783 691 784 686 784 681 783 676 784 1335 783 1330 783 +#OSC +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2227 749 733 1382 761 1379 866 1292 841 566 898 591 868 588 866 582 760 1412 867 612 866 576 898 603 867 598 866 1256 759 695 867 1246 759 101611 2335 615 868 1245 899 1268 869 1266 867 566 898 591 760 694 761 689 760 1411 760 720 760 715 759 710 759 705 759 1363 760 696 758 1352 761 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2224 708 755 1419 757 1443 756 685 756 1470 757 709 758 710 784 657 836 708 756 763 785 1468 782 1444 755 737 756 712 754 1471 781 1419 780 101298 2250 656 778 1420 809 1391 753 686 755 1472 779 687 779 687 752 688 831 714 778 741 750 1502 776 1422 752 740 752 741 751 1448 751 1475 749 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2203 675 786 1388 867 1361 786 678 786 1444 758 708 759 707 760 707 786 735 757 760 786 735 757 736 781 711 757 734 759 733 785 734 758 101185 2198 708 757 1416 757 1442 756 685 755 1445 780 685 780 662 776 689 803 742 778 740 753 766 779 715 777 714 779 714 778 690 776 742 752 +#StrengthUp +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2224 685 782 1419 808 1418 779 687 757 1470 757 710 757 684 809 657 809 736 758 1469 758 736 756 1470 757 762 755 1446 782 1418 781 712 755 101352 2223 707 758 1417 834 1392 781 685 781 1446 780 687 754 687 754 713 804 741 779 1447 779 715 778 1447 779 740 752 1448 778 1422 779 690 775 +#StrengthDown +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2222 685 781 1419 808 1419 780 686 755 1447 781 684 783 686 780 686 807 1470 757 1470 756 1471 756 1497 756 1445 780 1447 780 1421 779 1421 754 101509 2250 684 777 1421 781 1444 754 687 754 1472 779 687 779 688 753 688 830 1448 777 1449 778 1448 752 1501 752 1450 773 1451 778 1422 778 1423 777 +# +name: Power +type: parsed +protocol: NECext +address: 41 59 00 00 +command: 05 FA 00 00 +# +name: Speed_up +type: parsed +protocol: NECext +address: 41 59 00 00 +command: 44 BB 00 00 +#OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1332 391 1303 359 485 1241 1304 391 1303 391 452 1242 452 1242 452 1243 451 1243 452 1243 452 1242 1303 7275 1303 391 1303 390 453 1240 1330 364 1328 366 477 1218 476 1218 476 1220 474 1220 474 1220 474 1220 1325 7252 1326 369 1325 369 475 1219 1325 369 1325 369 475 1219 475 1219 475 1219 475 1219 475 1219 475 1219 1326 7254 1326 369 1326 369 475 1219 1326 369 1326 369 475 1219 475 1220 475 1219 475 1219 475 1220 475 1220 1325 7256 1325 370 1325 370 474 1220 1325 370 1325 370 474 1220 475 1220 474 1220 474 1220 474 1221 474 1221 1324 7281 1301 370 1325 370 474 1220 1325 370 1324 370 474 1221 474 1221 473 1221 473 1221 473 1221 474 1220 1325 7256 1325 370 1324 371 473 1221 1324 370 1324 370 474 1221 473 1221 473 1220 474 1220 474 1220 474 1220 1324 7256 1324 371 1323 371 473 1221 1323 371 1323 371 473 1221 473 1221 473 1221 473 1221 473 1221 473 1221 1323 7256 1323 371 1323 371 473 1222 1322 372 1322 372 472 1222 472 1222 472 1222 472 1222 472 1222 472 1222 1322 7281 1297 372 1322 373 471 1246 1298 396 1298 397 447 1247 447 1247 447 1247 447 1247 447 1247 447 1247 1297 7281 1297 397 1297 397 447 1247 1297 397 1297 397 447 1247 447 1247 447 1247 447 1247 447 1248 446 1248 1296 7282 1296 398 1296 398 446 1248 1296 398 1296 398 447 1248 446 1248 446 1248 446 1247 447 1247 447 1248 1295 7281 1296 398 1296 398 446 1248 1296 399 1295 399 446 1248 446 1248 446 1248 446 1248 446 1248 446 1248 1296 7282 1296 399 1295 399 445 1248 1295 399 1295 399 445 1248 446 1249 444 1249 445 1249 445 1249 445 1249 1294 7282 1294 399 1295 400 444 1249 1295 399 1294 400 444 1250 444 1249 445 1250 444 1249 445 1250 444 1250 1294 7282 1294 400 1294 400 444 1250 1294 400 1294 401 443 1251 443 1250 444 1250 444 1250 444 1250 443 1251 1292 7284 1292 401 1293 402 442 1251 1292 402 1291 403 442 1276 418 1252 442 1276 418 1277 417 1277 417 1277 1242 7334 1268 427 1242 452 392 1302 1242 452 1242 452 392 1302 392 1302 392 1302 392 1302 391 1302 392 1302 1242 7335 1241 453 1240 453 391 1302 1241 453 1240 453 391 1303 390 1303 390 1303 390 1304 390 1303 391 1304 1240 7361 1216 479 1216 479 364 1330 1215 479 1215 480 363 1330 364 1330 364 1330 364 1330 364 1330 364 1330 1214 7362 1213 480 1214 480 363 1331 1213 481 1213 482 361 1332 362 1332 362 1331 363 1331 362 1332 362 1332 1212 7391 1185 508 1185 509 334 1359 1185 509 1184 509 334 1359 335 1359 334 1386 307 1386 307 1386 307 1386 1159 7445 1131 562 1131 616 196 1471 1104 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2265 691 792 690 792 684 793 1342 821 1310 821 640 822 634 822 660 792 718 791 690 791 685 791 680 791 675 791 670 790 1330 789 662 788 99517 2230 723 760 721 760 715 761 1374 761 1368 762 699 762 694 762 689 762 747 761 719 761 715 761 710 761 705 761 701 760 1360 760 691 760 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2312 617 867 641 840 577 899 1269 866 1265 865 622 839 593 864 612 838 670 839 1306 839 611 865 1271 864 627 839 1259 865 590 866 611 840 102129 2316 670 813 667 813 664 811 1323 810 1319 759 702 759 697 759 692 759 750 759 1386 758 717 759 1376 758 706 759 1365 759 696 759 691 760 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2232 725 759 722 759 717 759 1375 760 1370 760 702 759 697 759 692 788 1384 788 1356 788 1351 788 1347 787 1342 787 1338 786 670 785 1330 784 99591 2229 724 760 721 785 690 785 1349 784 1345 784 677 783 673 782 669 781 1391 781 1363 781 1359 780 1353 781 1349 780 1343 781 675 780 1334 780 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2230 724 760 720 761 715 761 1374 761 1370 760 701 760 696 760 691 786 1385 760 720 787 689 760 710 760 705 786 1339 785 670 785 665 784 98757 2224 729 754 726 755 721 754 1380 754 1375 754 706 754 701 754 696 754 1418 754 726 754 720 755 716 755 710 755 1369 755 700 755 696 755 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2256 698 786 695 786 689 787 1348 787 1342 787 673 788 668 787 663 787 722 786 696 784 1353 786 1375 759 706 759 702 783 672 783 1332 782 102265 2310 645 838 668 812 664 811 1323 810 1319 810 651 809 647 808 642 808 701 807 673 807 1332 807 1327 807 658 808 653 807 648 807 1307 807 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1356 337 1423 337 516 1157 1298 409 1296 416 448 1274 451 1276 452 1274 450 1274 449 1277 449 1275 1301 7091 1301 407 1300 405 450 1267 1302 407 1301 414 450 1274 451 1275 451 1275 451 1276 450 1275 451 1274 1302 7074 1303 406 1302 406 450 1268 1301 409 1299 416 449 1277 472 1254 473 1253 473 1254 472 1254 473 1252 1323 7069 1325 384 1300 408 470 1247 1324 387 1299 417 471 1253 472 1254 472 1254 472 1254 472 1254 472 1252 1324 7052 1324 384 1300 408 472 1246 1324 387 1299 417 472 1253 472 1254 472 1254 472 1254 473 1254 472 1252 1324 7064 1325 385 1323 385 471 1246 1324 387 1323 394 471 1254 471 1254 472 1254 472 1254 472 1254 472 1252 1324 7053 1324 385 1323 385 471 1247 1323 387 1322 394 471 1254 472 1254 472 1255 471 1255 471 1255 472 1253 1323 7070 1323 385 1323 385 471 1247 1323 388 1322 395 470 1254 472 1255 471 1255 471 1255 471 1255 471 1253 1323 7054 1323 386 1322 386 470 1248 1322 413 1297 396 470 1255 470 1256 470 1256 470 1256 470 1256 471 1254 1322 7071 1322 411 1297 387 469 1249 1321 414 1296 396 470 1280 445 1281 446 1281 445 1282 445 1282 445 1280 1296 7076 1296 412 1296 412 444 1274 1297 413 1297 421 445 1281 445 1282 444 1282 444 1282 445 1282 444 1280 1296 7098 1295 412 1296 412 445 1275 1296 414 1296 421 445 1282 444 1282 445 1282 444 1283 444 1282 445 1280 1296 7082 1296 412 1296 412 445 1275 1295 414 1296 421 444 1282 444 1282 444 1283 444 1283 444 1282 445 1280 1296 7098 1296 413 1295 413 444 1275 1295 415 1295 422 444 1283 443 1282 444 1283 443 1282 445 1282 444 1281 1295 7082 1295 413 1295 413 444 1275 1295 415 1294 423 443 1283 443 1283 443 1283 443 1283 443 1283 443 1281 1294 7093 1295 413 1295 414 443 1276 1294 415 1295 423 443 1283 443 1283 443 1283 443 1283 444 1283 444 1280 1295 7082 1295 414 1294 414 443 1276 1294 416 1294 423 443 1283 443 1283 443 1283 443 1283 444 1283 444 1281 1294 7098 1295 414 1294 414 443 1276 1294 416 1294 424 442 1284 443 1283 443 1283 443 1284 443 1283 443 1281 1294 7083 1294 414 1294 415 442 1277 1293 417 1293 425 441 1284 442 1284 442 1284 442 1284 442 1284 443 1282 1293 7099 1294 415 1293 416 441 1278 1292 418 1292 425 441 1285 441 1285 441 1285 442 1285 441 1285 441 1283 1292 7083 1293 417 1291 441 415 1279 1292 443 1267 451 415 1286 440 1286 441 1286 440 1285 441 1286 440 1284 1291 7096 1292 441 1267 442 415 1304 1266 444 1266 451 415 1311 415 1287 439 1287 440 1311 415 1286 440 1285 1290 7085 1291 442 1241 467 414 1304 1266 445 1241 476 414 1313 414 1312 415 1311 415 1312 415 1312 414 1310 1241 7151 1242 467 1241 467 415 1305 1241 469 1241 477 412 1314 414 1312 414 1312 414 1312 414 1312 414 1310 1241 7135 1241 467 1241 468 389 1330 1241 470 1240 477 389 1337 389 1337 414 1312 414 1313 413 1313 389 1335 1241 7151 1241 469 1239 470 386 1331 1240 496 1214 503 362 1338 388 1338 388 1338 388 1338 388 1338 388 1336 1240 7130 1241 494 1214 495 361 1357 1214 497 1213 504 362 1364 362 1364 362 1364 363 1364 362 1364 362 1362 1214 7153 1239 495 1213 496 360 1358 1213 498 1212 531 334 1365 362 1365 361 1365 361 1365 362 1364 362 1363 1213 7162 1213 496 1212 522 334 1359 1212 524 1186 532 333 1367 359 1366 360 1366 361 1365 361 1365 361 1364 1211 7180 1212 523 1185 550 306 1387 1185 552 1157 586 278 1395 332 1394 332 1394 332 1393 333 1393 334 1391 1184 7190 1185 576 1131 604 250 1415 1158 660 1049 2341 251 1448 278 1422 305 1448 278 1448 278 1447 1130 7229 1157 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1357 353 1327 382 448 1239 1333 409 1301 416 477 1246 452 1274 452 1274 453 1273 479 1246 1330 416 448 7967 1300 409 1299 410 446 1273 1299 412 1298 419 446 1280 446 1281 446 1281 446 1281 446 1279 1298 419 446 7958 1298 411 1297 410 446 1273 1298 412 1298 419 446 1280 446 1281 445 1281 446 1281 445 1279 1298 419 446 7967 1298 410 1298 411 445 1274 1297 413 1297 420 445 1281 445 1281 445 1281 445 1282 445 1279 1298 420 445 7957 1297 411 1297 411 445 1274 1297 413 1297 420 445 1281 445 1281 445 1282 444 1282 444 1280 1296 420 445 7962 1297 411 1297 411 445 1274 1297 413 1297 420 445 1282 444 1282 444 1282 444 1282 444 1280 1297 421 444 7957 1296 412 1297 411 444 1275 1296 414 1296 421 444 1282 444 1283 443 1283 443 1282 444 1281 1296 421 444 7968 1296 412 1296 412 444 1275 1296 414 1296 421 444 1283 443 1283 443 1283 443 1283 443 1281 1296 422 443 7958 1295 413 1295 413 443 1276 1295 415 1295 422 443 1283 443 1284 442 1284 442 1284 442 1282 1294 422 443 7970 1293 414 1294 414 442 1277 1294 416 1294 423 442 1284 442 1309 417 1309 417 1309 417 1307 1269 424 441 7978 1270 416 1292 414 442 1302 1269 440 1270 424 441 1309 417 1309 417 1310 417 1309 417 1307 1270 448 417 7994 1269 439 1269 439 416 1302 1269 441 1269 448 416 1310 416 1310 416 1310 416 1310 416 1308 1269 448 416 7984 1269 439 1268 440 416 1303 1268 442 1267 449 415 1310 416 1310 416 1310 416 1310 416 1309 1268 449 416 7994 1268 440 1268 440 415 1303 1268 442 1268 449 415 1311 415 1311 415 1311 415 1311 415 1309 1267 450 415 7985 1267 441 1267 441 414 1304 1266 444 1267 450 414 1311 415 1312 413 1312 414 1312 414 1310 1265 452 414 7991 1266 442 1242 466 413 1306 1241 468 1242 475 414 1337 388 1338 388 1338 388 1338 388 1336 1217 501 386 8013 1215 492 1216 492 387 1331 1216 494 1216 501 363 1363 362 1363 363 1363 363 1363 387 1338 1215 502 362 8047 1215 493 1215 492 363 1356 1215 495 1215 502 362 1364 361 1364 362 1364 362 1364 362 1363 1214 503 361 8063 1189 494 1214 494 361 1382 1189 496 1214 503 361 1390 335 1391 335 1391 335 1391 335 1389 1188 529 335 8074 1188 520 1188 520 334 1385 1187 522 1187 529 334 1392 333 1417 308 1418 308 1419 307 1417 1161 556 307 8092 1160 547 1161 547 307 1412 1160 550 1160 557 306 1419 306 1446 279 1421 305 1446 279 1445 1133 584 279 8125 1133 575 1133 576 277 1466 1106 603 1107 611 251 1474 251 1501 224 1502 224 1528 186 1511 1079 638 224 8228 1025 735 972 2640 786 +# +name: Speed_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1A 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1D 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 18 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0D 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 80 DE 00 00 +command: 00 FF 00 00 +# +name: Speed_up +type: parsed +protocol: NECext +address: 80 DE 00 00 +command: 08 F7 00 00 +# +name: Speed_dn +type: parsed +protocol: NECext +address: 80 DE 00 00 +command: 10 EF 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4640 4393 562 1442 562 1443 562 1443 537 1470 535 1468 562 1444 562 2398 562 1444 562 1446 560 2424 535 1470 535 2425 534 1472 533 1473 533 2427 533 1474 531 1473 4577 4456 531 1473 531 1474 532 1474 531 1474 532 1474 531 1474 531 2429 531 1474 531 1474 531 2429 531 1474 531 2429 531 1474 531 1474 531 2429 531 1474 531 14007 9125 2259 530 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4609 4425 563 1441 563 1442 563 1442 538 1469 537 1469 561 1444 562 2400 560 1470 535 1470 536 1470 536 2424 536 1470 535 1471 534 1472 533 2427 533 2427 533 1472 4580 4454 531 1472 533 1474 532 1474 531 1474 532 1474 532 1474 532 2428 532 1474 532 1474 532 1474 532 2428 532 1474 532 1474 532 1474 532 2428 532 2429 532 14008 9127 2258 528 50213 9131 2253 532 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4607 4424 563 1441 563 1443 563 1444 536 1469 537 1469 537 1496 534 2399 561 1470 535 2424 536 1470 536 1470 535 2424 535 1471 534 1472 534 1472 533 2427 533 1472 4579 4455 531 1472 532 1474 532 1474 532 1474 532 1474 532 1474 532 2429 531 1474 532 2428 532 1474 532 1474 532 2429 532 1474 532 1474 532 1474 532 2428 532 14007 9125 2258 530 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4604 4427 561 1442 562 1443 561 1444 537 1469 536 1469 562 1445 561 2399 560 1446 559 2424 534 1472 533 2426 533 1473 532 2428 531 1475 531 1474 531 1474 531 1473 4575 4457 530 1473 531 1475 531 1475 530 1475 531 1475 531 1475 530 2429 531 1475 530 2429 530 1475 530 2430 530 1475 530 2429 531 1475 531 1475 531 1475 530 14008 9122 2260 530 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4582 4424 562 1442 562 1471 534 1471 509 1496 510 1496 534 1472 534 2424 536 1470 536 1470 536 1470 536 1470 535 2424 535 2425 534 2426 534 1472 533 1473 533 1472 4581 4453 532 1472 532 1473 533 1473 533 1473 533 1473 533 1473 533 2428 532 1473 532 1473 532 1474 532 1474 532 2428 533 2428 532 2428 532 1474 532 1474 532 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1369 311 1327 312 498 1162 1286 355 1285 362 457 1221 458 1221 458 1221 458 1222 457 1221 458 1219 1312 6796 1310 330 1282 356 454 1219 1281 360 1280 367 452 1227 452 1227 452 1227 452 1227 452 1227 452 1225 1280 6815 1280 359 1279 359 451 1220 1280 361 1279 368 451 1228 451 1228 451 1228 451 1228 451 1228 451 1226 1279 6827 1279 382 1256 382 428 1245 1255 384 1256 391 427 1251 428 1252 426 1252 427 1252 427 1252 427 1250 1255 6838 1255 383 1255 383 426 1245 1255 385 1255 392 426 1252 427 1252 426 1252 426 1252 427 1252 426 1250 1255 6849 1255 383 1255 383 426 1245 1255 385 1254 392 426 1252 426 1252 426 1252 426 1252 426 1252 426 1250 1254 6835 1254 383 1254 383 426 1245 1254 385 1254 392 426 1252 426 1252 426 1252 426 1252 426 1252 426 1250 1254 6852 1254 383 1254 384 425 1245 1254 386 1253 392 426 1253 425 1252 426 1252 426 1253 425 1253 425 1251 1253 6835 1253 384 1253 384 425 1245 1253 386 1253 393 425 1252 425 1253 425 1253 425 1253 425 1253 425 1251 1253 6852 1252 384 1253 384 425 1246 1252 386 1253 393 425 1253 424 1253 425 1253 425 1254 424 1253 425 1252 1252 6835 1253 385 1252 385 424 1247 1252 387 1252 394 424 1254 424 1254 424 1254 424 1254 424 1254 424 1252 1251 6850 1251 386 1251 386 423 1248 1251 388 1250 395 423 1255 422 1256 422 1279 399 1280 398 1279 399 1277 1227 6862 1226 411 1226 411 398 1273 1226 413 1226 420 397 1280 398 1279 398 1280 397 1280 398 1280 398 1278 1226 6892 1226 411 1225 411 397 1273 1225 413 1225 420 397 1280 397 1280 398 1280 397 1280 397 1280 397 1279 1224 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1308 332 1305 332 479 1190 1309 333 1307 364 455 1199 479 1198 481 1198 480 1198 480 1197 1331 339 480 7657 1306 332 1305 332 479 1194 1304 335 1305 342 478 1201 477 1201 478 1201 477 1201 478 1199 1305 342 478 7647 1305 333 1304 333 478 1194 1304 335 1304 342 477 1201 477 1201 478 1201 477 1201 477 1199 1305 342 478 7660 1303 334 1304 333 478 1194 1304 336 1303 343 477 1202 477 1202 476 1202 477 1202 476 1200 1304 343 477 7647 1303 334 1304 334 476 1195 1303 336 1303 343 476 1202 476 1202 476 1202 476 1202 476 1200 1303 343 476 7659 1302 335 1302 335 476 1196 1302 337 1302 344 475 1203 475 1203 475 1203 475 1203 475 1201 1302 344 476 7646 1302 335 1300 338 474 1197 1300 338 1301 345 474 1204 474 1204 474 1204 449 1229 449 1227 1275 370 449 7690 1275 362 1275 361 449 1222 1275 364 1275 371 448 1230 448 1230 448 1231 447 1232 446 1229 1274 372 447 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1338 312 1380 310 446 1133 1313 356 1284 362 457 1221 457 1219 1285 363 456 1221 457 1222 482 1196 482 7655 1282 356 1281 357 453 1219 1279 360 1279 367 452 1226 452 1224 1280 367 452 1226 452 1226 452 1226 452 7671 1279 358 1279 358 452 1219 1279 360 1279 367 452 1227 451 1225 1279 367 451 1227 451 1227 451 1227 451 7682 1279 359 1278 359 451 1220 1278 361 1278 368 451 1227 450 1225 1278 368 451 1227 451 1228 450 1228 450 7691 1277 360 1277 360 450 1221 1277 362 1277 368 450 1228 449 1226 1277 369 449 1229 449 1252 426 1252 426 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1338 311 1326 312 497 1134 1364 311 1276 361 457 1221 457 1220 457 1218 1283 361 456 1221 456 1221 457 7676 1285 351 1285 351 457 1213 1286 353 1285 360 457 1220 457 1220 457 1219 1285 361 456 1221 456 1222 481 7639 1283 353 1284 353 455 1216 1283 356 1282 363 454 1223 455 1223 454 1221 1283 363 454 1223 454 1223 454 7676 1282 354 1282 354 454 1217 1282 356 1282 364 453 1223 454 1223 454 1221 1282 363 454 1224 453 1224 454 7681 1281 355 1281 355 453 1217 1281 357 1281 364 453 1225 452 1225 452 1222 1281 365 452 1225 452 1225 452 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3565 3379 984 2506 984 2505 985 797 900 2590 900 2590 926 2572 926 826 925 829 922 858 894 859 894 858 895 2604 894 859 894 859 894 2596 894 859 894 859 894 867 894 2596 894 2596 894 2596 894 2596 894 2596 894 867 894 40343 3530 3468 895 2596 894 2596 894 859 894 2596 894 2596 894 2604 894 859 894 859 894 859 894 859 894 859 894 2604 894 859 894 859 894 2596 894 859 894 859 894 867 894 2596 894 2596 894 2596 894 2596 895 2596 894 866 895 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3595 3377 930 2561 929 2561 929 853 900 2591 899 2590 900 2597 901 2590 925 2565 924 2567 922 2570 920 2596 894 866 895 858 895 858 895 2596 894 858 895 859 894 866 895 858 895 858 895 858 895 859 894 858 895 2604 894 40343 3533 3467 895 2595 895 2596 894 858 895 2596 894 2596 894 2604 895 2596 894 2596 894 2596 894 2596 894 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 859 894 859 894 859 894 859 894 2605 893 40319 3533 3442 920 2595 895 2596 894 858 895 2596 894 2596 894 2604 894 2596 894 2596 894 2596 894 2596 894 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 859 894 859 894 859 894 859 894 2604 894 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3594 3380 981 2505 930 2560 930 853 900 2590 900 2590 900 2598 900 2589 900 853 924 2568 921 2571 919 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 2596 894 859 894 859 894 859 894 2604 894 40335 3532 3467 895 2596 894 2596 895 859 894 2596 894 2596 894 2605 894 2596 894 859 894 2596 894 2597 894 2596 894 867 894 859 894 859 894 2596 894 859 894 859 894 867 894 859 894 2597 894 859 894 859 894 859 894 2605 893 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3534 3439 954 2505 985 2535 955 798 954 2535 899 2591 899 2599 899 853 925 2566 924 2567 922 2596 894 2596 894 867 894 859 894 860 893 2597 894 859 894 859 894 867 894 2597 893 859 894 859 894 860 893 859 894 2605 894 40336 3531 3469 894 2597 893 2597 893 859 894 2597 893 2597 893 2605 893 860 893 2597 893 2597 893 2597 894 2597 893 868 893 860 893 860 893 2597 893 860 893 860 893 868 893 2597 893 860 893 860 893 860 893 860 893 2605 893 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1A 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1370 314 1375 320 519 1167 1370 322 1339 350 465 1221 467 1222 467 1222 466 1221 467 1221 467 1221 1317 7067 1317 372 1341 349 491 1198 1340 352 1337 353 486 1202 486 1226 462 1226 462 1227 461 1227 462 1227 1311 7073 1312 378 1311 378 462 1227 1311 378 1312 378 462 1227 462 1227 461 1227 462 1227 461 1227 461 1227 1311 7074 1311 378 1311 378 462 1227 1311 379 1310 379 461 1228 461 1228 460 1228 460 1228 460 1229 459 1228 1310 7076 1309 381 1309 380 460 1229 1310 381 1309 381 458 1230 458 1230 459 1230 459 1230 459 1230 458 1230 1309 7077 1309 380 1310 380 460 1229 1310 380 1310 380 459 1229 460 1229 459 1229 460 1229 459 1229 459 1229 1310 7075 1310 379 1310 380 459 1229 1310 380 1310 379 460 1229 460 1229 459 1229 460 1228 460 1229 459 1229 1310 7074 1310 380 1310 379 460 1229 1310 379 1310 379 460 1229 459 1229 459 1229 460 1229 460 1229 460 1228 1311 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1394 321 1369 321 520 1141 1397 321 1368 321 519 1142 495 1194 495 1194 494 1195 494 1195 1366 351 491 7891 1341 350 1337 353 487 1201 1337 354 1336 354 487 1202 487 1202 487 1202 487 1202 487 1202 1336 354 487 7900 1336 354 1337 354 486 1202 1337 354 1336 354 487 1202 487 1202 487 1203 486 1202 487 1202 1336 354 487 7901 1335 354 1336 354 487 1203 1335 354 1336 355 486 1203 486 1203 486 1203 486 1203 486 1203 1335 355 486 7900 1335 355 1335 355 486 1203 1335 355 1335 355 486 1203 486 1204 485 1204 485 1204 485 1203 1335 356 485 7900 1334 356 1334 356 485 1204 1334 356 1334 356 485 1204 485 1204 484 1204 485 1204 485 1204 1333 357 484 7901 1333 357 1333 380 460 1228 1310 380 1310 380 460 1228 460 1228 460 1228 460 1228 460 1228 1310 380 460 7924 1309 380 1310 380 460 1229 1309 380 1310 380 460 1229 459 1229 460 1229 459 1230 459 1230 1309 381 458 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1343 372 1318 372 467 1194 1346 372 1318 372 467 1193 525 1164 1400 319 495 1166 523 1166 523 1167 521 7890 1344 347 1340 350 489 1200 1339 351 1339 351 488 1201 488 1201 1338 351 488 1201 488 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1339 352 1338 352 488 1201 488 1201 1339 352 487 1202 487 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1338 352 1338 352 487 1202 487 1202 1337 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1202 1338 353 1337 353 486 1202 487 1202 1338 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1203 1336 354 1336 354 485 1203 486 1204 1335 354 485 1203 485 1203 486 1203 486 7901 1336 378 1312 355 484 1205 1335 378 1312 378 461 1227 462 1227 1313 378 461 1228 461 1228 461 1228 461 7925 1312 378 1312 378 461 1228 1311 378 1312 378 461 1228 461 1228 1312 378 461 1228 461 1228 461 1228 461 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1370 319 1371 321 519 1167 1371 322 1366 324 488 1198 466 1222 466 1222 1317 375 464 1221 467 1220 468 7915 1317 372 1341 350 490 1197 1340 376 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1312 377 1312 377 462 1226 462 1226 462 1226 1312 378 462 1226 462 1226 462 7922 1312 377 1313 377 462 1226 1313 377 1312 377 462 1226 462 1226 462 1226 1313 377 462 1226 462 1226 462 7921 1312 377 1312 377 462 1226 1312 377 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1313 377 1312 354 485 1202 486 1202 486 1202 1337 352 487 1202 487 1201 487 7897 1337 352 1337 351 488 1200 1339 351 1339 352 487 1201 487 1201 488 1201 1338 352 487 1201 487 1201 488 7896 1338 352 1337 352 487 1201 1337 352 1337 352 487 1201 487 1201 487 1201 1338 352 487 1201 487 1201 487 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1369 349 1370 319 521 1167 1371 321 1340 349 466 1221 467 1222 467 1221 468 1221 1318 373 466 1220 468 7917 1344 347 1341 349 489 1198 1340 351 1338 352 487 1202 486 1202 486 1202 486 1202 1337 353 486 1202 486 7922 1312 377 1313 377 462 1227 1312 377 1313 378 461 1227 461 1227 462 1227 462 1227 1312 377 462 1227 461 7923 1312 378 1311 378 461 1227 1312 378 1311 377 462 1227 461 1227 461 1227 461 1227 1312 377 462 1227 462 7922 1312 378 1312 377 462 1227 1312 354 1336 354 485 1227 461 1227 461 1227 461 1203 1336 353 486 1203 486 7922 1312 353 1336 354 485 1203 1336 354 1336 354 485 1203 485 1227 461 1227 461 1227 1312 378 461 1227 461 7923 1312 378 1312 378 461 1227 1312 378 1312 378 461 1228 461 1228 461 1228 460 1228 1311 378 461 1227 461 7923 1311 378 1311 378 461 1227 1312 378 1312 378 461 1227 462 1227 461 1227 461 1227 1312 378 461 1228 461 6641 1312 378 1312 355 484 1228 1311 355 1335 378 461 1228 461 1228 460 1228 460 1228 1311 378 461 1228 460 7924 1311 379 1310 379 460 1228 1311 379 1311 379 460 1229 459 1229 459 1229 460 1229 1310 380 459 1229 459 7925 1310 380 1309 381 458 1230 1309 381 1309 381 458 1230 458 1231 457 1231 458 1231 1308 381 458 1231 457 7952 1283 407 1283 407 432 1256 1283 407 1283 407 432 1257 431 1257 432 1257 431 1257 1283 408 431 1257 431 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1372 307 1369 310 524 1149 1370 310 1366 334 500 1151 526 1152 1367 335 499 1155 522 1156 521 1179 498 7895 1339 338 1339 338 496 1181 1339 339 1338 339 496 1182 496 1182 1339 339 496 1182 496 1182 496 1182 496 7897 1338 339 1338 339 496 1182 1339 339 1338 339 496 1182 496 1182 1338 339 496 1182 496 1183 495 1183 495 7897 1338 339 1338 339 496 1182 1339 339 1338 340 495 1183 495 1183 1337 340 495 1183 495 1183 495 1183 495 7898 1337 340 1337 340 495 1183 1337 340 1337 340 495 1183 495 1183 1337 340 495 1183 495 1183 495 1183 495 7898 1337 340 1338 340 495 1183 1337 340 1337 340 494 1183 495 1184 1336 340 495 1184 494 1184 494 1183 495 7898 1337 340 1337 341 494 1183 1338 340 1337 340 495 1183 495 1183 1337 340 494 1184 494 1184 494 1183 495 7898 1337 340 1337 340 494 1184 1337 341 1336 341 494 1184 494 1184 1337 340 494 1184 494 1184 494 1184 494 7898 1337 341 1336 341 494 1184 1336 341 1336 341 494 1184 494 1184 1336 341 494 1184 494 1184 494 1184 494 7899 1336 341 1336 341 494 1184 1336 341 1336 341 494 1184 494 1184 1336 341 494 1184 494 1184 494 1184 494 7899 1335 341 1337 341 494 1184 1336 341 1336 342 493 1185 493 1185 1335 342 493 1185 493 1185 493 1185 493 7899 1336 342 1335 342 493 1185 1335 342 1335 342 493 1185 493 1185 1335 342 493 1185 493 1185 493 1185 493 7899 1336 342 1335 342 493 1185 1335 342 1335 342 493 1185 493 1185 1335 342 493 1185 493 1185 493 1185 493 7900 1335 342 1335 342 493 1185 1335 342 1335 342 493 1185 493 1185 1335 342 493 1186 492 1186 492 1185 492 7900 1335 342 1335 343 492 1186 1334 343 1334 343 492 1186 492 1186 1334 343 492 1186 492 1186 492 1186 492 7901 1334 343 1334 344 491 1186 1334 344 1333 344 491 1187 491 1187 1333 344 491 1186 491 1187 491 1187 491 7901 1334 344 1333 368 467 1211 1309 368 1309 368 466 1211 467 1211 1309 368 467 1211 467 1211 467 1211 467 7926 1309 368 1309 369 466 1212 1308 369 1308 369 465 1212 466 1212 1308 369 465 1212 466 1212 466 1212 466 7927 1308 369 1308 369 466 1212 1308 370 1307 370 464 1213 465 1213 1307 370 464 1213 465 1213 465 1213 465 7927 1307 370 1307 370 464 1213 1308 370 1307 371 464 1214 464 1214 1306 371 464 1214 464 1214 464 1214 463 7928 1306 371 1306 371 463 1215 1305 372 1305 372 463 1215 463 1214 1306 372 463 1214 463 1215 463 1215 463 7929 1305 396 1281 397 437 1240 1280 397 1280 397 437 1241 437 1217 1303 397 437 1240 438 1240 438 1240 438 7954 1280 397 1255 422 412 1266 1255 423 1254 422 412 1266 412 1266 1255 423 411 1266 412 1266 412 1266 412 7980 1255 422 1255 423 411 1266 1255 423 1254 423 411 1267 411 1266 1255 423 411 1266 412 1267 410 1267 411 7980 1254 424 1253 424 410 1267 1254 424 1253 425 409 1267 410 1268 1253 450 384 1268 410 1268 410 1268 410 7982 1252 450 1227 450 384 1294 1227 450 1227 450 384 1294 384 1294 1227 451 383 1294 384 1295 383 1294 383 8008 1227 451 1226 451 383 1295 1226 452 1225 452 382 1296 382 1296 1225 478 355 1321 356 1296 382 1296 381 8010 1225 478 1199 478 355 1322 1200 479 1198 505 192 1459 355 1323 1199 505 145 1506 354 1324 353 1324 275 8116 1191 3869 795 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9230 4449 644 522 617 523 617 524 615 526 613 529 611 530 610 530 610 531 609 1640 610 1640 610 1640 609 1640 609 1640 610 1640 609 1640 609 1640 609 531 609 531 609 1640 609 531 609 531 609 531 609 1640 609 531 609 1640 609 1641 608 532 608 1641 608 1641 608 1641 608 532 608 1641 608 40020 9177 2212 611 +# +name: Timer +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9215 4421 664 475 664 500 638 500 638 501 662 476 662 477 661 478 661 1588 659 1589 658 1614 633 1615 632 1615 606 1640 606 1641 629 1618 630 508 631 1616 631 1616 632 1616 632 507 632 507 633 482 657 481 658 481 658 480 659 480 659 480 659 1589 658 1589 658 1589 658 1589 658 1589 658 39821 9206 2163 659 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1290 398 1290 397 446 1243 1290 397 1291 397 446 1242 446 1242 446 1243 445 1244 444 1243 445 1243 1262 7180 1262 425 1263 426 445 1243 1262 426 1262 425 446 1243 445 1244 444 1243 445 1242 446 1242 446 1242 1264 7181 1262 425 1263 425 445 1244 1262 424 1264 424 446 1243 445 1243 445 1244 444 1243 445 1243 445 1244 1263 7179 1263 424 1264 424 445 1243 1263 425 1263 425 445 1244 444 1244 444 1243 444 1245 417 1270 418 1269 1264 7180 1262 425 1263 425 418 1270 1263 425 1263 424 419 1270 418 1270 418 1270 418 1270 418 1270 418 1270 1263 7179 1264 424 1264 424 419 1269 1264 424 1264 424 419 1270 418 1269 419 1270 418 1270 418 1270 418 1270 1263 7179 1264 424 1264 424 419 1269 1264 424 1264 424 419 1270 418 1270 418 1270 418 1269 419 1270 418 1269 1264 7180 1262 424 1264 424 419 1269 1264 425 1263 424 419 1270 418 1269 419 1270 418 1270 418 1270 418 1269 1264 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1262 424 1263 424 419 1268 1264 424 1264 424 419 1270 418 1270 418 1270 417 1270 418 1270 1263 424 418 8023 1263 424 1263 424 418 1270 1263 424 1264 424 418 1270 418 1269 419 1270 418 1269 419 1269 1264 424 418 8022 1264 424 1264 424 419 1269 1264 424 1264 424 418 1269 419 1269 419 1269 419 1269 418 1269 1264 424 418 8023 1263 423 1265 423 419 1271 1262 424 1264 423 419 1269 419 1268 420 1270 418 1269 419 1268 1265 424 418 8024 1263 423 1265 423 419 1269 1264 423 1265 424 418 1269 418 1270 418 1269 419 1269 419 1268 1265 424 418 8023 1263 424 1263 423 419 1269 1264 423 1265 423 419 1270 418 1269 419 1269 419 1269 419 1269 1264 423 419 8022 1264 424 1263 424 418 1269 1264 423 1265 424 418 1268 420 1269 419 1269 419 1269 419 1269 1264 424 419 8023 1263 423 1264 424 418 1269 1264 424 1264 424 418 1270 418 1269 419 1269 418 1269 419 1269 1264 424 418 8023 1264 424 1264 424 418 1269 1264 424 1264 423 420 1269 419 1270 418 1268 420 1269 419 1269 1264 423 419 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1264 424 1263 423 419 1268 1265 423 1264 423 419 1269 418 1270 1262 423 419 1270 417 1269 419 1268 420 8022 1262 423 1264 423 419 1268 1264 424 1263 423 419 1268 419 1269 1263 423 419 1269 419 1268 420 1269 418 8022 1263 423 1264 424 418 1268 1264 423 1264 424 419 1269 418 1269 1264 423 419 1269 418 1269 419 1269 419 8021 1264 423 1264 424 418 1268 1264 423 1264 423 419 1268 419 1269 1263 423 419 1268 419 1268 419 1269 419 8020 1264 423 1264 423 419 1268 1265 423 1264 423 419 1269 418 1269 1264 423 419 1270 417 1268 420 1269 418 8022 1263 423 1265 423 419 1267 1266 423 1264 423 419 1268 419 1269 1263 423 419 1269 418 1268 419 1268 420 8022 1263 423 1264 423 419 1268 1265 423 1264 423 419 1268 420 1269 1264 423 419 1268 420 1268 419 1268 420 8021 1264 422 1265 423 419 1269 1263 423 1264 423 419 1269 418 1268 1264 423 419 1269 419 1269 418 1268 419 8021 1264 424 1263 423 419 1269 1263 423 1264 423 419 1269 418 1268 1264 424 418 1270 417 1268 419 1268 419 8022 1262 423 1264 423 420 1269 1263 423 1264 424 418 1269 418 1268 1264 424 418 1269 419 1269 418 1269 419 8021 1263 424 1263 424 418 1269 1263 423 1264 423 419 1269 419 1269 1263 423 419 1269 419 1269 419 1269 419 8021 1264 423 1264 423 419 1269 1263 423 1264 423 419 1268 420 1269 1263 423 419 1268 419 1269 418 1269 419 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1262 423 1264 423 419 1268 1264 423 1264 423 419 1269 419 1268 419 1268 419 1268 1264 423 419 1268 419 8020 1264 423 1264 423 419 1268 1264 423 1264 423 419 1268 419 1269 419 1268 419 1268 1264 423 419 1269 418 8021 1264 423 1264 423 419 1268 1264 424 1263 423 419 1269 419 1268 419 1268 419 1268 1264 423 419 1268 420 8021 1264 423 1264 423 419 1268 1264 423 1264 424 418 1268 420 1268 420 1269 418 1269 1263 423 419 1268 419 8021 1264 423 1264 423 419 1269 1263 423 1264 424 418 1269 418 1269 419 1268 419 1268 1264 423 419 1268 419 8021 1264 423 1264 423 419 1268 1264 423 1264 424 418 1268 419 1268 419 1268 419 1268 1264 424 418 1269 419 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2199 716 759 763 701 742 732 737 727 742 732 737 727 1448 759 738 726 743 732 1444 752 771 724 745 729 1447 760 1417 758 739 746 750 725 1426 760 737 727 743 732 739 725 1452 755 743 732 50977 2206 711 753 1449 758 51061 2226 721 754 1451 724 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2214 833 744 1598 752 822 745 824 743 1584 746 813 743 811 745 829 717 1627 744 835 752 1584 746 824 743 1584 745 813 743 811 745 803 743 100100 2213 835 752 1589 751 824 742 826 751 1576 743 816 750 803 743 805 751 1619 752 827 750 1587 753 816 750 1576 743 816 751 803 743 806 750 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2218 859 723 1594 753 822 749 820 751 1577 749 811 750 804 746 803 747 1625 753 826 756 1583 753 816 755 1573 753 806 755 800 750 799 751 100206 2213 863 729 1588 748 826 756 813 748 1581 755 804 746 808 753 797 753 1618 750 830 752 1587 749 820 751 1577 749 810 751 830 720 829 721 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2221 740 729 736 723 743 747 719 750 716 753 1445 744 722 747 719 719 747 722 1450 749 717 752 741 728 1444 755 1445 754 712 747 746 744 1454 724 741 728 1444 755 1444 755 711 779 1446 722 51387 2222 741 728 1445 754 50964 2197 740 750 1448 751 50939 2221 741 728 1443 746 50962 2198 738 752 1447 721 50965 2217 718 751 1445 754 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2221 754 723 755 722 1415 728 1403 720 717 750 709 747 706 750 698 748 758 750 728 749 1414 719 1413 720 717 750 708 748 1395 727 1384 728 101577 2217 732 745 732 745 1418 725 1406 727 710 747 712 755 698 748 701 745 761 747 731 746 1417 726 1405 727 710 746 711 745 1398 725 1387 725 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2194 749 719 1455 753 1452 725 718 750 1480 749 720 728 742 726 717 783 792 718 777 723 1533 727 1477 752 743 757 767 722 746 723 1480 749 101228 2200 768 700 1448 750 1454 754 715 722 1482 747 722 746 723 745 723 777 772 728 767 754 1502 748 1482 726 769 731 766 723 745 755 1448 750 49842 2221 746 702 1447 782 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1224 443 1252 441 438 1266 1254 443 1254 450 438 1273 439 1273 438 1277 434 1271 1254 451 437 1274 438 7899 1250 441 1254 443 436 1267 1253 444 1253 450 438 1273 439 1272 439 1273 438 1271 1254 452 436 1275 436 7888 1253 440 1255 441 438 1267 1253 443 1254 450 438 1272 439 1274 437 1273 438 1271 1254 451 437 1274 438 7894 1255 442 1253 442 437 1266 1254 444 1253 451 437 1273 439 1274 437 1274 437 1273 1252 451 437 1275 437 7888 1253 442 1253 441 438 1268 1253 442 1255 449 439 1271 440 1276 435 1275 436 1272 1253 450 438 1271 441 7895 1254 440 1255 442 437 1267 1254 444 1253 450 438 1273 439 1275 437 1273 438 1272 1253 450 438 1275 437 7893 1254 442 1253 442 437 1267 1253 443 1254 451 437 1275 437 1273 438 1274 437 1273 1252 450 438 1272 440 7891 1253 442 1253 442 437 1266 1255 443 1254 451 437 1274 438 1274 437 1274 438 1272 1253 450 438 1274 438 7894 1254 441 1254 441 438 1267 1254 444 1253 450 438 1274 438 1274 437 1274 437 1272 1254 452 436 1273 439 7890 1254 441 1254 442 437 1266 1255 444 1253 450 438 1273 439 1274 437 1274 438 1273 1253 451 437 1274 438 7895 1253 441 1254 441 438 1267 1254 443 1254 451 437 1273 439 1276 435 1273 439 1271 1254 450 438 1274 438 7896 1254 441 1254 441 438 1267 1253 443 1254 451 437 1273 439 1274 437 1273 438 1272 1254 450 438 1274 438 7889 1253 441 1254 442 437 1267 1253 443 1254 450 438 1274 438 1274 437 1274 438 1272 1253 449 439 1273 439 7896 1254 442 1253 441 438 1268 1253 445 1252 451 437 1274 438 1274 437 1275 436 1271 1254 450 438 1274 437 7888 1254 441 1254 442 437 1268 1252 444 1253 450 438 1274 437 1275 437 1276 435 1273 1252 450 438 1274 438 7895 1254 442 1253 441 438 1267 1253 443 1254 451 437 1273 491 1221 491 1221 490 1220 1252 450 438 1274 491 7841 1253 441 1254 444 435 1267 1253 443 1254 450 491 1221 491 1219 492 1221 491 1218 1254 450 438 1273 492 7838 1253 441 1254 442 437 1267 1254 443 1254 451 437 1274 491 1221 490 1221 490 1220 1252 452 436 1274 491 7841 1254 441 1254 441 437 1268 1253 444 1253 450 438 1273 439 1272 439 1274 437 1271 1254 452 436 1275 437 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1280 415 1280 417 436 1267 1280 418 1279 424 1278 425 437 1275 490 1224 488 1223 489 1220 1279 422 1281 7000 1279 419 1276 416 489 1216 1279 419 1278 424 1279 423 492 1224 488 1222 490 1223 489 1220 1280 423 1279 6997 1278 417 1278 419 486 1215 1280 420 1277 423 1279 424 489 1226 485 1225 486 1227 435 1275 1252 450 1252 7029 1249 447 1225 469 406 1299 1226 471 1226 477 1225 479 405 1307 405 1307 405 1307 405 1304 1227 476 1226 7047 1228 469 1226 469 405 1300 1226 472 1226 475 1227 477 406 1307 405 1305 407 1306 406 1305 1226 475 1228 7052 1227 469 1226 468 405 1299 1228 471 1226 476 1226 476 407 1307 405 1306 406 1306 406 1303 1228 474 1228 7042 1227 468 1227 468 406 1299 1227 470 1227 476 1226 478 405 1309 403 1306 406 1307 405 1304 1226 476 1226 7057 1250 445 1250 444 433 1271 1251 446 1251 450 1252 452 435 1277 435 1277 435 1278 434 1275 1277 424 1278 6991 1278 417 1278 416 487 1218 1279 418 1279 423 1279 425 488 1223 489 1223 489 1223 489 1222 1279 424 1278 7004 1280 416 1279 415 489 1220 1276 417 1280 423 1279 425 488 1223 489 1223 489 1223 489 1221 1280 423 1279 6989 1280 415 1280 415 489 1217 1279 417 1280 423 1279 425 488 1223 489 1223 488 1223 489 1222 1279 423 1279 6999 1279 417 1278 416 488 1216 1280 418 1279 424 1278 424 489 1225 487 1223 489 1222 490 1222 1279 423 1279 6995 1278 416 1279 415 489 1216 1280 418 1279 423 1279 426 487 1224 488 1224 487 1223 488 1220 1281 423 1279 6999 1280 415 1280 416 488 1217 1279 417 1280 423 1279 425 488 1224 488 1226 486 1225 486 1220 1281 423 1279 6994 1279 417 1278 416 487 1218 1279 417 1280 422 1280 426 485 1226 486 1226 486 1226 485 1225 1278 423 1279 7001 1251 444 1251 442 435 1270 1252 445 1252 450 1252 452 434 1278 434 1277 435 1277 434 1276 1251 451 1251 7018 1250 445 1250 445 432 1272 1251 446 1251 451 1251 452 435 1278 434 1277 435 1277 435 1274 1253 450 1252 7031 1278 418 1277 416 436 1270 1277 419 1278 424 1278 426 435 1276 436 1275 437 1276 484 1224 1280 423 1279 6989 1279 417 1278 415 487 1219 1279 418 1279 424 1278 425 486 1225 487 1225 486 1225 487 1222 1280 423 1279 7013 1278 416 1279 415 488 1218 1279 418 1279 426 1276 425 487 1224 488 1224 487 1224 487 1222 1279 422 1280 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1254 441 1254 441 438 1272 1249 443 1254 448 1254 450 438 1274 438 1274 437 1276 1249 449 1253 451 437 7865 1253 443 1252 441 438 1268 1252 445 1252 451 1251 451 437 1274 437 1273 438 1275 1250 448 1254 450 438 7862 1253 441 1254 443 436 1268 1252 444 1253 449 1253 450 438 1274 437 1275 436 1272 1253 449 1253 451 437 7864 1252 444 1251 442 437 1266 1254 443 1253 448 1254 451 437 1273 438 1276 435 1272 1253 449 1253 452 436 7861 1254 441 1254 441 438 1268 1252 446 1250 448 1253 450 438 1272 439 1273 438 1273 1251 450 1252 452 436 7863 1253 441 1253 441 438 1267 1253 444 1252 449 1253 450 438 1273 438 1276 435 1270 1254 448 1254 451 437 7856 1253 441 1253 440 439 1266 1254 444 1252 450 1251 451 437 1275 436 1274 437 1272 1252 449 1252 451 437 7868 1252 441 1253 441 438 1266 1253 443 1253 450 1251 451 437 1273 438 1274 437 1270 1254 448 1253 451 437 7854 1254 441 1253 440 439 1267 1252 443 1254 448 1253 450 438 1274 437 1273 438 1272 1252 447 1254 450 438 7868 1253 442 1252 441 438 1266 1253 443 1253 449 1252 450 438 1277 434 1272 439 1273 1251 448 1253 451 437 7856 1252 441 1253 443 436 1267 1252 442 1254 448 1253 451 437 1274 490 1221 490 1219 1252 449 1252 451 437 7864 1252 442 1253 441 490 1215 1252 444 1252 449 1252 450 491 1221 490 1220 491 1218 1253 449 1252 451 490 7807 1254 442 1253 441 491 1215 1251 444 1252 448 1253 451 490 1220 491 1221 490 1218 1253 448 1253 450 491 7810 1253 441 1253 441 491 1213 1254 443 1253 448 1253 451 490 1219 492 1220 491 1219 1252 448 1253 450 491 7808 1254 441 1253 442 489 1214 1253 443 1253 448 1254 450 490 1221 490 1222 489 1219 1253 449 1253 452 488 7813 1279 416 1279 416 487 1218 1278 418 1279 422 1280 425 486 1225 487 1225 486 1223 1279 423 1279 426 435 7896 1279 416 1279 416 437 1268 1279 418 1279 423 1279 425 437 1275 436 1275 436 1273 1279 423 1279 425 436 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1275 405 1279 401 436 1243 1279 401 1273 407 440 1238 436 1244 1278 429 408 1244 440 1239 435 1244 440 8113 1272 434 1250 430 407 1246 1276 430 1254 426 411 1242 432 1247 1275 432 405 1247 437 1242 432 1247 437 8115 1280 426 1248 432 405 1248 1274 433 1251 428 409 1244 440 1239 1272 434 413 1239 435 1245 439 1239 435 8118 1277 429 1245 435 412 1240 1271 436 1248 431 406 1247 437 1242 1280 427 410 1242 432 1247 437 1242 432 8121 1274 406 1278 428 409 1244 1278 402 1272 408 439 1240 434 1245 1277 404 433 1246 438 1240 434 1245 439 8114 1281 399 1275 405 432 1247 1275 406 1278 401 436 1244 430 1249 1273 407 440 1240 434 1245 439 1239 435 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1277 429 1245 435 412 1267 1245 435 1249 431 406 1273 1249 431 406 1247 438 1241 433 1246 439 1267 407 8120 1275 430 1255 426 411 1242 1280 426 1248 432 405 1274 1248 432 405 1248 436 1243 431 1248 436 1242 432 8123 1272 407 1278 429 408 1245 1277 430 1244 436 411 1268 1254 426 411 1243 431 1274 410 1268 406 1274 410 8117 1278 401 1273 433 404 1275 1247 433 1252 429 408 1271 1251 429 408 1271 413 1239 435 1271 413 1265 409 8119 1277 402 1272 408 439 1240 1272 434 1251 430 407 1272 1250 430 407 1246 439 1241 433 1245 439 1240 434 8120 1275 431 1254 426 411 1243 1279 427 1247 433 404 1249 1273 407 440 1265 409 1244 430 1275 409 1269 405 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1317 325 1360 371 472 1214 1291 397 1320 325 527 1224 474 1223 476 1225 473 1222 476 1223 474 1221 1317 7168 1289 356 1329 358 485 1240 1318 372 1316 325 530 1222 475 1224 472 1224 474 1223 475 1225 473 1222 1316 7209 1315 369 1316 368 449 1241 1289 399 1289 409 470 1252 446 1225 474 1250 448 1223 474 1222 475 1222 1316 7181 1315 368 1316 325 491 1242 1316 372 1315 379 475 1223 474 1250 449 1224 474 1222 476 1222 473 1222 1316 7176 1315 367 1316 370 474 1214 1315 324 1338 380 498 1224 474 1222 474 1222 475 1248 449 1222 502 1194 1289 7192 1315 368 1316 324 492 1240 1317 325 1363 377 475 1223 474 1249 449 1224 472 1223 474 1223 473 1223 1316 7204 1314 369 1315 370 473 1239 1289 371 1315 381 473 1224 473 1223 475 1224 473 1226 472 1223 473 1220 1316 7178 1313 366 1319 369 473 1215 1288 397 1316 380 472 1223 474 1225 472 1224 471 1250 448 1224 447 1248 1288 7203 1314 325 1356 324 521 1215 1286 424 1288 379 475 1223 472 1223 473 1224 475 1221 448 1248 476 1245 1289 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1314 348 1360 324 466 1269 1262 368 1345 324 530 1224 475 1222 474 1226 474 1223 474 1220 1316 378 450 8036 1315 394 1290 323 493 1240 1317 372 1316 377 475 1224 476 1222 475 1221 476 1222 473 1221 1290 324 557 8047 1314 369 1316 394 446 1242 1288 373 1314 379 448 1252 471 1226 471 1224 445 1252 472 1222 1287 431 447 8022 1315 369 1314 323 464 1274 1283 399 1315 388 385 1305 473 1223 500 1198 473 1223 474 1221 1315 380 474 8016 1313 324 1362 368 472 1215 1315 324 1364 378 474 1223 473 1223 474 1222 473 1224 473 1220 1315 378 477 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1350 359 1323 359 482 1175 1351 337 1346 345 506 1185 510 1183 1350 371 481 1187 508 1187 508 1189 506 7967 1343 362 1318 364 477 1210 1316 368 1316 376 476 1220 476 1218 1315 377 475 1220 475 1220 475 1220 475 8037 1315 366 1315 366 475 1211 1315 369 1315 377 475 1220 475 1219 1314 377 475 1220 475 1220 475 1221 474 8008 1314 367 1314 367 474 1212 1314 370 1314 378 474 1221 475 1219 1314 378 474 1221 475 1221 474 1221 474 8009 1314 366 1315 367 474 1212 1314 370 1314 378 474 1221 474 1219 1314 378 474 1221 474 1221 475 1221 474 7998 1314 366 1315 367 474 1212 1314 370 1314 378 474 1221 475 1219 1314 378 474 1221 475 1221 474 1221 475 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1347 336 1347 359 482 1175 1351 361 1322 370 481 1184 511 1185 510 1183 1349 370 481 1188 507 1189 506 7963 1319 362 1317 364 477 1210 1316 368 1316 376 476 1220 475 1220 475 1218 1315 376 476 1219 476 1220 475 8005 1315 366 1315 365 476 1211 1315 368 1316 377 475 1220 475 1219 476 1218 1315 377 475 1220 475 1220 475 7979 1315 366 1315 366 475 1211 1315 369 1315 377 475 1220 475 1220 475 1218 1315 377 475 1220 475 1220 475 7984 1314 366 1315 366 475 1211 1315 369 1315 377 475 1221 474 1221 475 1218 1315 377 475 1221 474 1221 475 7970 1314 367 1314 366 475 1212 1314 369 1315 378 474 1221 474 1221 474 1219 1314 378 474 1221 474 1221 475 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1395 358 1369 358 499 1215 1399 331 1341 384 472 1241 474 1240 475 1240 1345 383 473 1240 474 1240 474 8239 1366 387 1339 387 469 1246 1339 388 1339 387 469 1246 469 1246 469 1246 1339 388 469 1246 469 1246 469 8242 1339 387 1339 387 469 1246 1339 387 1339 387 469 1246 469 1246 469 1246 1339 387 469 1246 469 1246 468 8243 1338 388 1338 388 469 1246 1339 388 1338 388 468 1246 468 1246 469 1247 1338 388 468 1246 468 1247 468 8243 1338 388 1338 388 468 1246 1338 388 1338 388 468 1246 469 1246 468 1246 1339 387 469 1246 468 1246 468 8218 1363 363 1363 363 493 1222 1363 363 1363 363 493 1222 493 1222 492 1222 1363 363 493 1221 493 1222 493 8217 1363 363 1363 363 493 1222 1363 363 1363 363 493 1246 468 1223 492 1223 1361 375 481 1247 467 1247 467 8243 1337 388 1338 388 468 1247 1337 389 1337 388 468 1247 468 1247 468 1247 1337 389 467 1248 466 1248 466 8243 1337 389 1337 389 467 1248 1336 389 1337 390 466 1248 466 1248 466 1248 1336 390 466 1248 466 1248 466 8244 1336 390 1311 415 465 1249 1336 390 1336 391 465 1250 465 1249 465 1250 1310 415 465 1250 464 1250 439 8270 1310 416 1310 416 440 1275 1310 417 1309 417 438 1300 414 1301 413 1301 1284 442 414 1301 413 1301 413 8297 1284 442 1284 442 413 1301 1284 443 1283 443 413 1302 412 1302 412 1302 1282 443 413 1302 412 1302 412 8298 1282 443 1283 443 413 1302 1283 443 1283 444 412 1303 411 1303 411 1303 1282 444 412 1303 411 1303 411 8299 1280 445 1281 470 385 1329 1255 470 1256 471 384 1330 384 1329 385 1329 1255 471 385 1330 384 1330 384 8326 1253 472 1254 472 384 1331 1253 473 1253 499 356 1357 357 1358 356 1358 1226 499 356 1358 356 1359 355 8355 1224 502 1224 501 355 1385 1199 527 1199 527 328 1386 328 1386 328 1386 1199 527 328 1387 327 1387 327 8409 1171 554 1172 555 300 1414 1172 555 1171 555 300 1416 299 1416 298 1415 1171 556 298 1442 272 1442 272 8438 1143 583 1143 583 271 1471 1115 611 1115 664 179 1536 178 1563 122 1619 1006 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1333 408 1299 408 449 1268 1302 410 1300 417 449 1274 451 1272 1299 417 449 1276 449 1276 449 1276 449 7978 1324 384 1324 385 472 1247 1322 389 1321 395 471 1256 470 1254 1320 396 470 1280 446 1280 446 1280 446 7977 1296 411 1297 388 469 1273 1296 389 1321 421 445 1280 446 1278 1296 420 446 1280 445 1280 446 1280 445 7982 1296 412 1296 412 445 1273 1296 414 1296 421 445 1280 445 1278 1296 421 445 1281 445 1280 445 1280 445 7976 1295 412 1296 412 445 1273 1296 414 1296 421 445 1280 445 1278 1296 421 445 1281 444 1280 445 1280 445 7987 1295 412 1296 412 445 1273 1296 414 1295 421 445 1280 445 1278 1295 421 445 1280 445 1280 445 1280 445 7952 1319 412 1296 412 445 1273 1296 414 1296 422 444 1280 445 1278 1296 422 444 1281 444 1280 445 1280 445 7980 1296 412 1296 412 445 1273 1296 414 1296 421 445 1281 444 1279 1295 422 444 1281 444 1280 445 1280 445 7952 1318 413 1295 412 445 1273 1296 414 1296 421 445 1280 445 1278 1296 421 445 1280 445 1281 444 1280 445 7956 1319 412 1296 412 445 1273 1295 415 1295 422 444 1280 445 1278 1295 422 444 1280 445 1280 445 1280 445 7957 1319 412 1295 412 445 1273 1295 415 1294 422 444 1280 445 1279 1294 422 444 1280 445 1281 444 1281 444 7956 1318 413 1294 413 444 1273 1295 415 1294 422 444 1281 444 1279 1295 422 444 1281 444 1281 444 1281 444 7974 1295 413 1295 413 444 1274 1294 415 1295 422 444 1281 444 1279 1294 422 444 1281 444 1281 444 1281 444 7979 1294 413 1295 413 444 1274 1294 416 1294 423 443 1281 444 1279 1294 422 444 1281 443 1281 444 1281 444 7973 1294 414 1294 413 444 1274 1294 416 1293 423 443 1281 443 1279 1294 423 443 1282 443 1282 443 1282 443 7986 1293 414 1293 414 443 1274 1294 416 1293 423 443 1282 443 1280 1293 423 443 1282 443 1282 443 1282 443 7975 1293 415 1293 415 442 1275 1293 417 1292 424 442 1282 442 1280 1293 424 442 1283 441 1283 442 1283 442 7980 1292 415 1292 415 442 1276 1292 417 1292 425 441 1283 441 1281 1292 425 441 1283 441 1284 441 1284 441 7975 1292 416 1291 416 441 1277 1291 419 1290 426 440 1284 440 1282 1290 427 439 1285 439 1285 440 1284 440 7981 1291 442 1265 442 415 1302 1265 444 1265 451 415 1310 414 1308 1265 452 414 1310 414 1310 414 1310 415 7977 1289 442 1241 467 414 1303 1240 469 1240 476 414 1310 414 1308 1240 476 414 1311 413 1311 412 1312 414 8013 1240 467 1240 467 413 1305 1239 470 1239 477 412 1312 389 1333 1240 477 413 1311 412 1312 389 1336 388 8027 1239 468 1239 468 388 1329 1239 470 1239 478 387 1337 387 1334 1239 478 388 1336 388 1337 387 1337 388 8032 1239 469 1238 494 362 1331 1237 496 1213 504 362 1362 386 1337 1212 504 362 1362 362 1363 362 1363 361 8054 1212 495 1212 495 361 1356 1212 497 1212 505 360 1364 360 1362 1211 505 360 1364 360 1364 360 1364 360 8060 1212 522 1185 522 334 1384 1185 524 1185 532 333 1391 333 1389 1184 532 333 1391 333 1391 333 1392 333 8089 1184 524 1183 550 306 1411 1158 552 1157 559 305 1420 304 1418 1156 586 278 1446 278 1446 278 1446 278 8143 1130 603 1104 605 249 1493 1077 712 997 4114 1050 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1351 383 1324 383 474 1217 1352 386 1324 392 448 1249 477 1249 477 1246 1353 390 475 1249 475 1250 474 7942 1323 386 1322 386 471 1247 1321 388 1322 396 470 1254 471 1255 470 1253 1320 396 471 1254 471 1255 470 7933 1320 387 1321 387 470 1248 1320 389 1321 396 471 1255 470 1255 470 1253 1321 397 469 1255 470 1255 470 7943 1319 388 1320 388 469 1248 1320 390 1319 397 469 1255 469 1256 468 1254 1319 397 469 1255 469 1256 468 7931 1318 388 1319 388 469 1249 1318 391 1318 398 468 1257 468 1256 468 1255 1318 398 468 1257 468 1256 468 7949 1318 389 1318 390 467 1250 1317 392 1317 422 444 1281 443 1281 443 1279 1294 422 444 1281 443 1281 443 7956 1294 413 1294 413 444 1274 1293 415 1294 423 443 1281 443 1281 443 1279 1294 423 443 1281 443 1281 443 7966 1293 414 1293 414 443 1274 1293 416 1293 423 443 1282 442 1282 443 1280 1293 423 443 1282 442 1282 442 7956 1292 414 1293 414 443 1275 1292 416 1293 424 442 1282 442 1282 442 1280 1293 424 442 1282 442 1282 442 7967 1292 415 1292 415 442 1276 1291 417 1292 424 442 1283 441 1283 441 1281 1292 425 441 1283 441 1283 441 7963 1291 416 1291 416 440 1277 1290 418 1291 425 441 1284 440 1284 440 1282 1290 427 439 1284 440 1285 439 7969 1289 417 1290 418 438 1302 1265 444 1265 451 415 1309 415 1310 414 1308 1264 451 415 1310 414 1310 414 7983 1264 443 1263 443 414 1303 1264 445 1264 452 414 1310 413 1311 413 1309 1239 477 413 1311 413 1311 413 7995 1262 444 1239 468 412 1306 1238 470 1239 478 412 1312 411 1312 412 1310 1237 479 387 1362 362 1362 387 8010 1212 494 1213 495 361 1355 1213 497 1212 504 361 1363 361 1363 361 1361 1211 504 361 1363 360 1364 360 8054 1210 496 1211 521 334 1383 1185 524 1184 532 333 1390 334 1391 333 1389 1183 532 333 1392 332 1391 332 8089 1157 524 1183 549 306 1411 1157 552 1157 585 279 1445 279 1445 278 1444 1130 586 278 1472 251 1447 277 8155 1103 630 1076 657 186 1530 1050 660 1048 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1275 398 1297 369 561 1107 1301 370 1298 397 533 1108 563 1107 563 1106 1274 421 534 1110 560 1111 558 7970 1268 424 1243 425 530 1140 1242 427 1242 428 528 1141 528 1142 527 1142 1241 428 528 1142 528 1142 527 8000 1240 428 1241 428 528 1142 1241 428 1241 428 528 1142 528 1142 527 1142 1241 428 528 1142 527 1142 528 8000 1240 429 1240 429 527 1142 1240 429 1240 429 528 1142 527 1143 527 1143 1240 430 526 1143 526 1144 525 8029 1211 461 1208 485 446 1225 1156 565 1103 620 229 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1283 419 1288 442 417 1258 1288 416 1281 448 411 1264 443 1258 439 1264 443 1258 439 1263 444 1259 1286 7832 1283 420 1287 443 416 1260 1286 417 1280 450 409 1265 442 1260 437 1265 443 1259 438 1265 442 1260 1286 7834 1281 421 1286 444 415 1260 1286 444 1263 440 408 1266 441 1261 436 1266 441 1261 436 1266 441 1261 1285 7833 1282 421 1287 443 416 1259 1287 443 1254 449 410 1265 442 1259 438 1264 443 1259 438 1264 443 1259 1287 7833 1282 420 1287 442 417 1259 1287 442 1255 448 411 1264 443 1258 439 1263 444 1257 440 1262 435 1268 1288 7831 1284 418 1289 441 407 1268 1288 441 1256 446 413 1262 435 1267 440 1261 436 1266 441 1261 436 1266 1290 7829 1286 416 1281 449 410 1265 1281 422 1285 444 415 1259 438 1264 444 1258 439 1263 444 1258 439 1263 1283 7836 1290 413 1284 445 414 1261 1285 444 1253 450 409 1266 442 1260 437 1264 444 1259 438 1263 445 1258 1288 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1308 413 1310 409 426 1277 1313 410 1302 425 431 1279 433 1304 408 1304 408 1277 435 1277 435 1275 1304 7045 1312 408 1304 415 431 1272 1307 415 1308 419 427 1283 429 1308 404 1307 405 1281 431 1280 432 1278 1311 7015 1311 409 1303 415 431 1272 1307 414 1309 419 427 1283 430 1281 431 1306 406 1279 433 1279 433 1276 1303 7043 1304 417 1306 412 434 1269 1310 412 1311 417 429 1281 431 1279 433 1304 408 1277 435 1276 426 1283 1306 7018 1308 412 1311 408 427 1275 1304 417 1306 422 434 1275 427 1284 428 1308 404 1281 431 1280 432 1276 1314 7030 1307 413 1310 409 426 1276 1303 418 1305 423 433 1276 426 1284 428 1309 403 1281 431 1280 432 1277 1313 7009 1307 413 1310 409 426 1275 1304 418 1305 422 434 1275 427 1283 429 1307 405 1279 433 1278 434 1274 1305 7037 1310 410 1302 416 430 1272 1307 414 1309 418 428 1281 431 1305 407 1277 435 1275 427 1283 429 1279 1310 7010 1306 414 1309 409 426 1300 1279 417 1306 421 425 1309 403 1306 407 1278 434 1275 427 1284 428 1280 1309 7031 1306 414 1309 409 426 1300 1279 417 1306 421 425 1308 404 1306 406 1277 435 1274 428 1282 430 1277 1312 7049 1308 411 1312 406 429 1297 1282 413 1310 417 429 1304 408 1275 427 1282 430 1279 433 1276 436 1272 1307 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1318 409 1321 412 453 1248 1326 412 1318 412 442 1256 443 1255 1319 404 450 1249 450 1249 450 1249 450 8000 1319 402 1317 406 448 1249 1315 420 1371 353 501 1196 451 1247 1317 406 448 1249 450 1247 441 1254 445 8002 1306 408 1311 403 441 1254 1309 415 1315 401 443 1253 446 1250 1314 402 442 1254 445 1251 448 1248 440 7999 1310 405 1304 411 443 1252 1312 414 1305 412 442 1254 445 1252 1312 407 447 1249 450 1247 441 1255 444 7998 1310 406 1313 404 450 1246 1307 418 1363 354 449 1247 441 1255 1309 407 447 1249 450 1246 442 1254 445 7998 1311 403 1306 409 445 1250 1303 419 1311 404 450 1244 444 1251 1313 402 442 1253 446 1250 449 1248 440 8002 1306 436 1283 432 422 1245 1308 444 1285 430 414 1254 445 1252 1312 432 422 1245 443 1253 446 1250 449 7998 1311 407 1312 403 451 1242 1311 415 1304 411 443 1250 449 1247 1307 411 443 1249 450 1245 443 1252 447 7998 1311 408 1301 413 441 1279 1285 415 1304 411 443 1276 423 1273 1280 410 444 1275 424 1272 416 1280 419 7998 1311 408 1301 414 440 1251 1313 414 1305 409 445 1248 451 1245 1308 411 443 1249 450 1246 442 1254 445 8003 1306 414 1305 409 445 1248 1306 421 1309 406 448 1246 442 1253 1311 407 447 1246 442 1253 446 1250 449 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1323 406 1313 415 449 1249 1314 420 1309 415 449 1249 450 1248 450 1248 1316 412 442 1258 451 1248 451 7999 1319 408 1311 415 449 1249 1314 420 1320 405 449 1249 450 1249 450 1250 1313 413 451 1248 450 1249 449 8007 1321 406 1313 414 450 1248 1315 419 1311 415 449 1249 450 1249 449 1250 1314 413 441 1257 452 1248 450 8006 1312 415 1314 412 442 1257 1317 417 1312 411 443 1255 444 1256 443 1258 1316 410 444 1255 444 1256 442 8014 1314 411 1318 408 446 1252 1312 421 1319 405 449 1249 450 1248 450 1249 1314 411 443 1256 442 1256 442 8014 1314 410 1319 405 449 1248 1315 416 1313 408 446 1251 448 1250 449 1250 1314 410 444 1253 445 1252 446 8006 1312 412 1317 407 447 1251 1313 420 1309 415 449 1248 440 1257 441 1256 1318 406 448 1251 447 1251 447 8005 1313 412 1317 407 447 1250 1313 418 1311 412 442 1255 443 1254 444 1254 1320 405 449 1249 450 1249 449 8003 1315 409 1310 413 441 1256 1318 414 1315 407 447 1251 447 1250 448 1250 1313 410 444 1255 443 1254 444 8008 1320 406 1313 409 445 1253 1310 422 1318 405 449 1249 449 1249 449 1249 1314 412 442 1256 442 1257 452 8003 1315 413 1316 407 447 1250 1313 421 1319 406 448 1249 449 1250 448 1279 1295 406 448 1246 452 1247 451 8002 1326 407 1322 415 449 1251 1323 424 1326 414 450 1250 448 1256 453 1249 1325 409 445 1253 445 1255 454 8005 1323 413 1327 406 448 1254 1330 419 1321 412 452 1248 450 1250 448 1254 1330 404 450 1250 448 1254 455 8002 1316 408 1311 412 452 1244 1319 413 1316 406 448 1248 450 1248 450 1249 1314 407 447 1249 449 1249 449 8005 1313 411 1308 411 443 1252 1311 417 1312 406 448 1249 449 1248 450 1248 1315 434 420 1251 447 1252 446 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 01 00 00 00 +# +name: Mode +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 09 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 86 00 00 00 +# +name: Mode +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 01 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 0C 00 00 00 +# +name: Mode +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 07 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9248 4429 664 479 662 504 663 478 664 478 663 1588 687 1611 663 478 662 480 661 1615 659 1616 659 1616 658 1616 659 483 658 483 658 1616 658 1616 658 1616 658 1617 657 1616 658 483 658 1617 657 483 658 483 658 1616 658 483 658 483 658 483 658 1617 657 483 657 1617 657 1617 657 484 657 39720 9237 2194 658 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9281 4426 667 503 640 503 666 477 666 477 666 1609 666 1610 665 478 664 480 662 1614 661 1615 661 1615 661 1615 661 482 661 482 661 1615 661 1615 661 482 661 1615 661 1616 660 482 661 483 660 482 661 482 660 1616 660 1616 660 483 660 483 660 1616 659 1616 660 1616 660 1616 659 483 660 39712 9244 2192 659 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9278 4401 665 478 663 479 687 479 664 477 664 1586 688 1610 664 478 662 479 661 1614 660 1615 659 1615 660 1615 660 482 659 482 659 1615 660 1615 659 1615 659 1615 659 1615 659 482 659 482 659 482 659 482 659 1615 659 482 659 482 659 482 659 1615 659 1615 659 1615 659 1615 659 482 659 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9261 4448 641 499 642 499 642 498 643 498 643 1632 643 1632 667 475 666 475 666 1609 665 1610 664 1611 663 1612 662 479 662 479 662 1613 661 1614 660 479 662 479 662 1613 661 1612 662 1637 637 504 637 504 637 1637 637 1637 637 1613 661 504 637 504 637 504 636 1637 637 1637 637 504 636 39707 9242 2184 662 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9265 4406 688 455 686 479 662 479 662 479 662 1612 661 1613 660 481 659 482 658 1617 657 1618 656 1618 656 1618 656 485 656 485 655 1618 656 1618 656 1618 656 485 655 1618 655 485 656 1618 655 485 656 485 655 1618 655 485 655 1619 655 485 655 1619 654 486 654 1619 655 1619 655 486 654 39717 9230 2198 654 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1261 422 1260 422 514 1169 1260 422 1260 422 418 1265 418 1266 417 1266 514 1169 514 1169 514 1168 1262 7159 1261 422 1261 422 418 1265 1261 422 1260 422 418 1265 418 1266 417 1266 417 1266 418 1264 419 1265 1261 7159 1260 422 1260 422 418 1265 1261 421 1262 422 418 1266 514 1168 516 1169 515 1167 516 1168 515 1168 1261 7160 1261 422 1261 421 515 1168 1262 422 1261 421 515 1169 515 1167 516 1167 516 1167 516 1168 515 1168 1261 7160 1260 422 1260 422 514 1168 1262 421 1261 421 419 1264 515 1169 515 1169 514 1169 515 1169 515 1168 1262 7160 1260 421 1261 421 418 1266 1260 422 1261 422 417 1265 418 1265 418 1266 417 1265 418 1265 418 1266 1260 7159 1261 422 1260 422 418 1266 1260 421 1261 422 417 1265 418 1266 417 1266 417 1265 418 1266 417 1265 1261 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1341 341 1341 341 498 1186 1340 341 1341 342 497 1186 521 1160 499 1186 1340 342 522 1162 521 1162 521 7900 1340 342 1340 343 521 1163 1339 343 1339 343 521 1162 521 1162 521 1162 1340 343 521 1163 521 1163 520 7902 1338 344 1338 343 496 1187 1340 343 1339 343 521 1164 520 1164 519 1163 1339 344 519 1163 496 1187 521 7901 1339 343 1339 344 495 1187 1340 342 1341 343 496 1187 497 1186 497 1187 1340 343 496 1188 495 1187 496 7927 1337 343 1339 343 496 1187 1339 343 1339 343 496 1188 520 1163 520 1163 1340 343 496 1187 520 1163 520 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1342 341 1341 340 500 1183 1343 341 1341 341 499 1184 499 1184 499 1184 499 1183 1343 340 499 1185 499 7923 1341 340 1342 341 498 1184 1342 341 1341 341 498 1185 498 1186 497 1184 500 1186 1341 342 497 1185 498 7923 1341 342 1340 342 497 1185 1342 341 1341 342 521 1162 498 1186 497 1185 498 1186 1340 342 521 1162 521 7899 1341 341 1341 341 498 1185 1341 342 1340 341 498 1186 497 1185 499 1185 498 1185 1341 341 498 1185 499 7923 1341 342 1341 341 499 1184 1342 342 1341 342 497 1184 499 1185 498 1184 499 1185 1341 342 497 1185 499 7922 1342 341 1341 341 499 1185 1341 341 1341 341 499 1183 500 1185 498 1185 498 1184 1342 342 497 1185 499 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1286 396 1287 396 444 1240 1286 396 1286 397 443 1239 444 1240 1285 396 444 1239 444 1240 443 1241 442 7977 1286 396 1286 396 500 1183 1287 396 1286 395 501 1183 501 1184 1286 395 500 1184 499 1183 500 1183 501 7922 1340 341 1341 341 499 1184 1342 341 1341 340 500 1184 499 1183 1343 340 500 1184 499 1183 500 1184 499 7922 1342 340 1342 340 500 1184 1342 340 1342 340 500 1185 498 1184 1342 340 500 1184 499 1184 499 1186 498 7922 1341 341 1341 341 499 1184 1342 341 1341 340 500 1183 500 1184 1342 341 499 1184 500 1184 499 1184 499 +# +name: Power +type: parsed +protocol: NECext +address: 82 21 00 00 +command: 1F E0 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 82 21 00 00 +command: 1B E4 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1278 421 1279 422 508 1210 1250 422 1278 421 509 1182 509 1183 508 1183 508 1183 508 1182 509 1184 1276 7327 1278 421 1279 421 509 1184 1276 422 1277 422 508 1183 508 1183 508 1182 509 1184 507 1184 507 1182 1278 7329 1351 345 1355 346 508 1183 1353 346 1353 346 509 1182 509 1182 509 1184 507 1183 508 1182 508 1183 1353 7251 1354 345 1354 346 508 1184 1352 346 1353 346 508 1184 507 1182 509 1185 506 1182 509 1183 508 1186 1350 7253 1352 345 1354 346 508 1183 1353 346 1353 345 509 1184 507 1183 508 1211 479 1184 507 1183 508 1185 1351 7254 1351 347 1352 348 506 1183 1353 347 1352 346 508 1183 508 1184 507 1187 504 1183 508 1185 506 1184 1352 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1278 421 1279 421 509 1183 1277 421 1278 422 508 1183 508 1182 509 1182 509 1182 509 1184 1276 421 509 8086 1279 421 1278 421 509 1183 1277 422 1277 422 508 1182 509 1183 508 1182 509 1184 507 1183 1352 347 508 8088 1353 346 1353 346 508 1184 1351 346 1353 347 507 1184 507 1184 507 1183 508 1187 504 1183 1353 346 508 8088 1352 345 1354 346 508 1183 1353 347 1352 346 508 1184 507 1185 506 1185 506 1184 507 1183 1349 350 508 8092 1345 352 1348 351 506 1185 1347 351 1348 352 505 1185 505 1185 506 1187 503 1185 505 1185 1347 354 504 8094 1250 446 1253 447 497 1193 1253 447 1252 446 497 1194 496 1195 495 1193 497 1195 495 1196 1252 446 495 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1253 446 1253 447 495 1194 1254 446 1253 447 422 1268 422 1268 1253 448 421 1270 421 1269 422 1269 422 8176 1251 448 1251 447 421 1269 1253 445 1254 447 421 1267 423 1269 1253 446 422 1270 421 1269 422 1269 422 8174 1253 447 1252 446 423 1269 1252 447 1252 445 424 1268 423 1269 1252 447 422 1268 422 1269 422 1268 423 8174 1252 446 1253 448 493 1195 1254 446 1253 447 495 1196 495 1195 1252 447 496 1193 497 1194 498 1194 496 8098 1253 445 1254 446 499 1194 1252 446 1253 446 498 1192 498 1191 1254 447 503 1187 504 1187 504 1186 505 8091 1346 352 1347 353 505 1186 1346 352 1347 352 505 1185 506 1187 1345 351 507 1185 505 1185 505 1184 506 +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 88 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 8C 00 00 00 +# +name: Speed_dn +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 87 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1255 435 1257 435 415 1278 1257 440 1256 435 415 1278 415 1278 415 1279 414 1279 414 1279 414 1278 1257 7176 1257 436 1256 435 415 1279 1256 438 1258 437 413 1279 414 1278 415 1278 415 1279 414 1279 414 1278 1257 7174 1256 436 1256 437 413 1278 1257 439 1257 435 415 1278 415 1278 415 1277 416 1279 414 1278 415 1278 1256 7175 1257 435 1257 435 415 1279 1256 440 1256 435 415 1278 415 1278 415 1278 415 1278 415 1280 413 1278 1257 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1284 409 1283 408 440 1253 1284 411 1285 407 441 1254 439 1253 440 1253 440 1251 442 1252 1285 409 439 7991 1285 409 1283 408 440 1253 1284 411 1285 407 441 1255 438 1253 440 1252 441 1251 442 1254 1283 407 441 7994 1283 410 1282 409 439 1254 1283 413 1283 409 439 1253 440 1253 440 1253 440 1253 440 1253 1284 407 441 7996 1284 409 1283 408 440 1253 1284 412 1284 408 440 1253 440 1253 440 1251 442 1253 440 1253 1284 409 439 7996 1284 407 1285 408 440 1254 1283 411 1285 409 439 1254 439 1253 440 1252 441 1253 440 1253 1284 408 440 7996 1284 408 1284 409 439 1255 1282 411 1285 408 440 1254 439 1252 441 1252 441 1254 439 1253 1284 407 441 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1251 439 1253 437 414 1282 1252 441 1255 436 415 1279 414 1280 413 1280 413 1279 1255 437 414 1279 414 8020 1253 438 1255 438 413 1281 1253 441 1255 438 413 1280 413 1279 414 1279 414 1280 1254 437 414 1280 413 8024 1253 438 1254 437 414 1281 1253 440 1256 437 414 1279 414 1279 414 1278 415 1280 1254 437 414 1280 413 8023 1254 440 1252 436 415 1279 1255 441 1255 436 415 1279 414 1279 414 1279 414 1279 1255 437 414 1279 414 8024 1253 437 1256 437 414 1280 1254 441 1255 437 414 1278 415 1280 413 1280 413 1280 1254 437 414 1279 414 8023 1254 438 1255 436 415 1280 1255 441 1255 436 415 1279 414 1279 414 1279 414 1280 1254 436 415 1280 413 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1252 437 1255 438 519 1174 1255 441 1255 437 518 1175 519 1173 520 1175 1253 437 518 1175 519 1175 518 7917 1253 438 1254 438 413 1281 1253 440 1256 437 414 1279 465 1228 465 1228 1254 436 415 1279 465 1227 466 7969 1253 438 1254 438 413 1280 1254 440 1256 436 415 1280 413 1278 466 1229 1254 437 414 1279 414 1278 415 8024 1252 437 1255 436 415 1279 1255 440 1256 437 414 1279 414 1279 414 1279 1255 437 414 1279 414 1278 415 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1253 434 1258 435 492 1200 1258 438 1258 434 498 1194 494 1202 1257 433 494 1200 493 1200 493 1200 493 7940 1259 434 1258 433 493 1200 1259 438 1258 433 493 1199 494 1200 1259 434 492 1200 493 1200 493 1199 494 7939 1258 435 1257 434 497 1196 1258 437 1259 433 498 1196 492 1201 1258 434 497 1197 491 1200 493 1201 492 7941 1258 434 1258 435 495 1197 1258 437 1259 433 497 1197 497 1196 1259 434 496 1197 496 1197 496 1196 497 7939 1258 435 1257 434 495 1198 1258 438 1258 434 495 1198 495 1199 1257 435 494 1199 494 1197 496 1197 496 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 121 20917 311 387 1345 372 1316 372 450 1212 1319 400 1292 396 450 1210 479 1212 476 1238 450 1238 451 1238 451 1238 1345 7078 1316 372 1315 372 474 1216 1314 377 1314 374 472 1217 472 1217 472 1217 472 1217 472 1217 472 1217 1314 7106 1314 375 1313 374 472 1218 1313 378 1314 375 471 1218 471 1218 471 1218 471 1218 471 1218 471 1218 1313 7108 1312 376 1312 375 471 1219 1312 380 1312 376 470 1218 471 1218 471 1219 470 1218 471 1219 470 1219 1312 7110 1311 376 1312 376 470 1219 1312 381 1311 377 469 1219 470 1219 469 1220 469 1220 469 1220 469 1221 1310 7133 1286 402 1286 401 445 1245 1285 406 1286 401 445 1244 444 1244 445 1244 445 1244 445 1244 445 1245 1286 7134 1285 402 1286 402 444 1245 1286 406 1286 402 444 1245 444 1245 444 1245 444 1245 443 1245 444 1245 1285 7136 1284 403 1285 403 443 1245 1285 407 1285 403 443 1246 443 1246 443 1246 443 1246 443 1246 443 1246 1285 7135 1284 404 1284 404 442 1247 1284 408 1284 404 442 1247 442 1247 442 1247 442 1247 442 1248 441 1248 1283 7138 1282 405 1283 405 441 1249 1282 410 1281 407 440 1250 439 1249 440 1273 415 1274 390 1299 415 1274 1256 7166 1256 432 1256 431 415 1275 1255 436 1232 456 390 1299 415 1275 390 1299 390 1299 390 1299 390 1300 1231 7189 1231 457 1232 457 389 1301 1230 461 1231 458 388 1301 388 1301 388 1301 388 1326 363 1326 363 1327 1204 7217 1204 484 1205 484 362 1328 1203 488 1204 485 361 1328 361 1328 361 1328 361 1329 360 1353 335 1330 1202 7246 1177 511 1177 512 334 1356 1175 542 1150 539 307 1383 306 1383 306 1382 307 1409 280 1409 280 1410 1122 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1293 396 1293 395 450 1238 1294 399 1293 395 450 1238 451 1238 1294 395 450 1237 452 1237 452 1237 452 7964 1294 395 1318 370 475 1215 1316 377 1315 397 448 1241 448 1242 1290 398 447 1242 447 1242 447 1242 447 7970 1290 398 1292 372 473 1217 1316 377 1315 397 448 1217 473 1217 1316 372 473 1216 473 1217 473 1216 473 7944 1316 373 1316 372 473 1217 1316 376 1316 372 473 1216 473 1217 1316 372 473 1216 473 1216 473 1216 473 7942 1318 372 1317 371 474 1216 1317 375 1317 371 473 1216 473 1216 1318 371 474 1216 473 1216 473 1216 473 7943 1318 371 1318 371 474 1216 1318 375 1317 371 474 1216 474 1216 1318 371 474 1216 473 1216 473 1216 474 7943 1317 372 1317 372 473 1217 1317 376 1316 372 473 1217 472 1217 1316 372 473 1217 472 1217 472 1217 472 7944 1314 375 1314 398 447 1243 1290 403 1289 398 447 1243 446 1244 1289 399 446 1243 447 1243 446 1243 446 7971 1289 400 1289 399 446 1244 1289 403 1289 399 446 1243 446 1244 1289 399 446 1244 446 1244 445 1244 445 7972 1288 400 1289 399 446 1244 1290 403 1289 399 446 1244 445 1244 1289 399 446 1244 446 1244 445 1244 445 7946 1288 401 1288 400 469 1222 1311 380 1312 399 445 1244 445 1245 1289 399 445 1244 445 1245 445 1244 446 7971 1289 400 1289 400 444 1246 1289 403 1289 400 443 1246 444 1245 1272 416 445 1244 420 1269 444 1245 420 7996 1288 401 1288 400 420 1270 1264 429 1263 425 419 1270 419 1270 1264 425 419 1270 419 1270 419 1270 419 7995 1262 426 1263 425 419 1270 1263 430 1262 425 419 1270 419 1271 1262 426 418 1271 418 1271 418 1271 418 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1321 393 1295 393 451 1211 1322 396 1295 393 451 1210 507 1182 507 1181 1351 364 479 1208 480 1208 480 7933 1322 366 1320 368 475 1213 1319 373 1318 370 474 1214 474 1215 474 1215 1317 371 473 1215 473 1215 473 7939 1315 372 1316 372 472 1216 1316 376 1315 373 471 1217 472 1218 470 1218 1313 398 446 1242 446 1242 446 7967 1262 425 1263 425 444 1245 1263 429 1287 400 445 1244 445 1243 446 1244 1288 400 445 1243 446 1243 446 7966 1288 400 1288 400 445 1244 1287 404 1288 400 445 1244 444 1244 444 1245 1286 401 444 1244 444 1245 444 7968 1261 426 1286 401 419 1270 1285 406 1285 402 444 1244 445 1244 445 1245 1287 400 444 1244 445 1244 444 7968 1288 400 1288 400 444 1244 1288 404 1287 399 445 1244 444 1244 445 1244 1263 425 444 1244 444 1244 444 7967 1261 427 1261 426 418 1272 1260 456 1235 452 392 1296 392 1296 393 1297 1235 453 392 1296 393 1296 393 7996 1261 427 1261 427 418 1270 1262 430 1285 402 444 1245 443 1245 444 1245 1286 402 444 1245 444 1245 444 7969 1286 402 1286 402 444 1245 1286 406 1285 402 444 1245 444 1245 444 1245 1286 402 444 1245 444 1245 444 7968 1284 403 1285 403 443 1246 1284 407 1284 403 443 1246 442 1246 443 1246 1284 403 443 1246 443 1246 442 7971 1284 405 1283 405 441 1247 1283 408 1283 405 441 1248 441 1248 440 1249 1281 406 440 1249 440 1249 440 7974 1281 408 1280 431 390 1299 1232 460 1256 432 389 1299 390 1299 389 1300 1231 456 390 1299 390 1299 390 8023 1231 457 1231 457 389 1300 1231 461 1230 458 388 1300 389 1301 388 1301 1230 458 388 1302 387 1301 388 8027 1228 484 1204 484 362 1327 1204 488 1203 484 362 1328 361 1328 361 1329 1202 486 360 1354 335 1329 360 8080 1176 512 1176 512 334 1356 1175 542 1149 539 306 1382 307 1384 305 1383 1149 566 279 1436 252 1410 279 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1373 341 1295 393 452 1211 1322 397 1295 393 451 1210 479 1210 479 1211 478 1237 1296 393 451 1236 453 7962 1321 367 1320 368 476 1214 1318 374 1318 370 474 1215 474 1215 474 1215 474 1216 1316 371 473 1215 474 7941 1316 372 1316 372 472 1217 1316 376 1316 372 473 1216 473 1216 473 1217 472 1217 1315 373 472 1217 472 7941 1315 374 1314 374 471 1218 1314 401 1291 398 447 1242 447 1242 447 1242 447 1243 1290 398 446 1242 447 7968 1290 399 1289 398 447 1243 1290 402 1290 398 447 1242 447 1242 447 1242 447 1243 1289 398 446 1243 446 7968 1289 399 1289 398 447 1243 1290 402 1290 398 446 1243 446 1243 446 1243 446 1243 1290 398 446 1243 446 7967 1289 399 1289 399 446 1244 1289 403 1289 398 446 1243 446 1243 446 1243 446 1244 1289 399 446 1243 446 7969 1288 400 1288 399 445 1244 1288 403 1288 400 445 1244 445 1244 445 1244 445 1244 1288 400 445 1244 420 7995 1261 427 1261 428 416 1296 1236 456 1236 452 392 1296 393 1296 393 1296 393 1296 1236 452 393 1296 393 7997 1260 428 1260 428 416 1273 1260 432 1259 428 417 1296 392 1272 417 1296 393 1296 1237 452 393 1296 393 8021 1236 452 1236 452 392 1297 1236 456 1236 452 393 1297 392 1297 392 1297 392 1298 1235 453 391 1298 391 +# +name: Power +type: parsed +protocol: NEC +address: 03 00 00 00 +command: D8 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 4F 00 00 00 +# +name: Speed_dn +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 47 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 03 00 00 00 +command: C3 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 9B 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9018 4461 617 1623 617 502 618 502 617 502 617 503 616 501 619 503 616 501 618 502 617 1622 617 1623 617 1621 618 1621 618 1622 617 1623 617 1622 618 1623 617 1623 617 501 619 503 617 531 588 502 618 502 617 502 617 503 617 501 618 1623 617 1623 617 1622 618 1623 617 1622 618 1623 617 1623 617 1621 619 503 617 503 617 501 619 503 616 501 618 503 617 503 616 504 616 1621 619 1623 617 1623 617 1624 615 1623 617 1623 616 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9021 4460 676 1564 676 445 675 444 676 445 675 445 675 445 674 445 675 446 674 445 675 1566 674 1565 674 1565 674 1566 673 1565 674 1565 674 1565 674 1566 674 1565 674 1565 674 445 674 446 674 445 674 446 673 445 674 446 674 446 673 446 674 1566 673 1565 674 1567 672 1568 672 1567 672 1566 674 1567 673 1567 673 447 673 447 673 448 672 446 621 499 621 499 621 500 672 449 618 1619 620 1621 619 1619 621 1620 620 1620 620 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8995 4486 588 1653 587 532 588 530 590 531 589 534 585 532 587 531 589 532 587 532 588 1652 587 1652 588 1652 588 1652 588 1653 587 1652 588 1652 588 1652 588 1651 589 531 589 532 588 1652 588 532 588 531 589 531 589 531 588 531 589 1652 588 1651 589 532 588 1651 589 1651 589 1652 588 1651 589 1652 587 533 586 531 588 1653 587 531 588 531 589 532 588 531 589 532 587 1651 588 1653 586 530 589 1651 588 1651 588 1652 587 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8993 4485 589 1651 589 529 590 529 591 530 590 530 589 530 590 531 589 530 589 531 589 1649 590 1650 589 1649 590 1650 589 1651 588 1649 590 1650 589 1654 585 1650 589 1651 588 1650 590 533 586 531 588 532 588 530 590 530 590 532 587 531 588 530 589 1650 589 1650 589 1652 587 1651 588 1651 588 1650 589 1650 589 1652 587 530 590 530 589 530 589 531 588 531 588 531 588 530 589 531 588 1651 588 1650 590 1651 589 1651 589 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2280 776 785 1565 783 796 782 790 783 1549 783 810 752 805 752 800 752 858 752 830 752 826 776 797 775 793 774 789 773 810 747 805 747 102605 2223 832 752 1595 753 825 752 820 752 1581 752 811 751 806 751 802 750 860 750 833 775 804 773 799 773 795 773 790 773 785 772 780 773 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4612 4435 543 1461 544 1460 545 1461 544 1460 543 1462 518 1487 518 2441 516 1513 491 1513 492 2465 492 1513 516 2441 516 1488 516 1489 515 2443 514 1492 514 1485 4580 4467 513 1492 513 1492 513 1492 513 1491 514 1492 513 1492 513 2445 513 1492 513 1492 513 2445 513 1492 513 2445 513 1492 513 1492 513 2445 513 1493 513 14064 9205 2275 513 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4611 4435 544 1460 545 1460 546 1459 546 1460 519 1487 517 1487 518 2440 518 1488 517 1488 516 1512 517 2440 517 1488 516 1488 516 1489 515 2444 513 2445 514 1485 4581 4467 513 1492 513 1492 513 1492 513 1492 513 1492 513 1492 513 2444 514 1492 513 1491 514 1492 513 2445 513 1492 513 1492 513 1492 513 2444 513 2446 513 14066 9203 2275 513 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4611 4434 545 1460 545 1459 546 1457 548 1458 521 1485 520 1485 545 2413 544 1462 542 2439 518 1487 517 1489 515 2444 512 1493 512 1493 512 1493 512 2446 512 1486 4581 4470 512 1493 511 1493 512 1493 512 1493 512 1493 512 1493 512 2446 511 1493 511 2446 512 1493 512 1493 512 2446 512 1493 512 1494 511 1493 512 2447 512 14071 9203 2279 511 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4608 4436 545 1458 547 1458 547 1458 547 1458 521 1484 521 1485 520 2439 544 1461 544 2440 518 1487 517 2441 516 1489 515 2444 513 1493 512 1492 513 1494 512 1486 4577 4471 512 1493 512 1493 512 1493 512 1492 513 1493 512 1493 512 2446 512 1493 512 2446 512 1493 512 2446 512 1493 512 2446 512 1493 512 1493 512 1494 512 14051 9198 2279 512 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4605 4437 544 1460 545 1459 546 1458 546 1459 520 1485 520 1486 545 2414 543 1487 517 1488 515 1489 515 1491 513 2446 512 2446 511 2446 512 1493 512 1494 511 1487 4573 4472 511 1493 511 1493 511 1493 511 1493 512 1494 510 1494 511 2446 511 1493 511 1494 511 1494 510 1493 511 2447 511 2446 511 2446 511 1494 511 1495 511 14064 9191 2281 510 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4617 4436 544 1459 545 1458 545 1458 546 1458 520 1485 519 1485 545 2413 544 1462 542 1486 517 2440 515 2442 513 1492 512 1492 513 2444 512 1492 512 1493 513 1485 4582 4469 512 1493 512 1492 512 1492 512 1493 511 1493 512 1492 512 2445 511 1493 511 1493 511 2445 512 2446 511 1493 511 1493 511 2445 511 1493 511 1494 511 14048 9216 2280 512 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9220 4459 632 565 628 539 628 539 627 540 626 542 624 545 622 548 619 571 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 571 596 571 596 572 595 1652 596 571 596 571 596 571 596 572 595 1652 596 1652 596 1653 595 571 596 1652 596 39508 9197 2229 595 96159 9231 2229 596 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9244 4432 633 564 629 538 630 538 629 539 627 540 626 542 624 570 598 547 620 1651 597 1651 597 1651 597 1651 597 1651 597 1651 597 1651 598 1651 597 1651 597 1651 597 1651 597 570 597 570 597 570 597 570 597 570 597 570 597 570 597 570 598 1651 597 1651 597 1651 597 1651 597 1652 596 39509 9207 2227 597 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9223 4461 632 565 628 540 627 540 626 541 625 542 624 545 622 547 621 571 597 1629 620 1652 597 1653 595 1653 596 1653 596 1653 596 1653 596 1653 596 572 596 572 595 1653 596 1653 595 572 595 572 596 572 595 572 595 1653 596 1653 596 572 595 572 595 1653 596 1653 595 1653 596 1653 595 39519 9204 2229 596 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9227 4459 634 564 629 539 629 538 629 539 627 541 625 543 624 570 598 571 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 571 597 571 596 1652 597 571 596 571 597 571 596 571 597 571 596 1652 597 1652 597 571 597 1652 597 1652 597 1653 596 1653 596 39523 9207 2229 596 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9223 4459 633 564 629 538 629 538 628 539 627 541 624 544 622 570 597 570 597 1651 597 1651 597 1651 597 1651 598 1651 597 1651 597 1651 597 1651 597 570 597 1651 597 1651 597 1651 597 1651 597 570 598 1651 597 570 597 1651 597 570 597 570 597 570 597 570 597 1652 596 570 597 1652 596 39519 9202 2226 597 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9241 4434 633 565 602 565 628 539 628 539 627 540 626 542 624 545 622 547 620 1628 620 1629 619 1629 619 1652 596 1653 595 1629 619 1652 596 1653 595 571 596 1653 595 571 596 571 596 572 595 571 596 1653 595 572 595 1653 595 571 596 1653 595 1653 595 1653 595 1653 596 572 595 1653 595 39519 9226 2229 597 +# +name: Power +type: raw +frequency: 40000 +duty_cycle: 0.4 +data: 20592 6864 2288 9152 11440 2288 2288 6864 2288 2288 2288 13728 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1328 375 1304 375 460 1196 1327 379 1299 379 430 1221 459 1221 458 1222 483 1197 483 1198 481 1222 1299 7068 1297 382 1297 382 453 1227 1296 382 1297 382 453 1227 453 1228 452 1228 452 1227 453 1228 452 1228 1296 7322 1295 383 1296 383 452 1228 1296 383 1296 383 452 1228 452 1228 452 1228 452 1228 452 1228 452 1228 1296 7070 1295 383 1296 384 451 1228 1296 383 1296 383 452 1228 452 1229 451 1228 451 1229 451 1228 451 1229 1295 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1327 373 1306 377 459 1194 1329 373 1305 377 1276 402 433 1219 486 1194 486 1195 484 1219 1302 379 1299 7067 1299 380 1299 381 455 1224 1299 381 1298 381 1298 381 455 1225 455 1225 455 1225 455 1225 1298 381 1298 7344 1298 381 1299 381 455 1225 1298 381 1298 381 1298 381 455 1225 455 1225 455 1225 455 1226 1298 381 1298 7069 1298 382 1298 382 454 1225 1298 382 1298 382 1298 382 454 1226 454 1226 454 1226 454 1226 1297 382 1297 +# +name: Speed_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1330 377 1302 377 459 1193 1330 376 1303 376 1277 401 435 1218 487 1194 486 1194 1329 377 1299 380 456 7911 1299 380 1299 380 456 1225 1299 380 1299 380 1299 380 456 1224 456 1225 455 1225 1298 381 1299 380 456 8149 1299 381 1298 381 455 1225 1299 380 1299 381 1299 381 455 1225 455 1225 455 1226 1298 381 1299 381 455 7913 1298 381 1299 381 455 1226 1298 381 1299 381 1298 382 454 1225 455 1226 454 1226 1298 382 1298 381 455 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1329 373 1306 377 459 1193 1330 377 1302 377 1277 402 434 1219 485 1195 1329 377 1300 379 456 1224 455 7911 1299 381 1298 381 455 1224 1299 380 1299 381 1298 381 455 1225 455 1225 1299 380 1299 381 455 1225 455 8165 1298 381 1299 381 455 1225 1299 381 1299 381 1298 381 455 1225 455 1226 1298 381 1299 381 455 1225 455 7913 1298 381 1298 381 455 1226 1297 381 1298 382 1297 382 454 1226 454 1226 1297 382 1298 382 454 1226 454 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1330 376 1303 376 460 1192 1331 376 1302 376 459 1191 463 1217 462 1218 1331 376 459 1195 484 1219 460 7907 1300 379 1300 380 456 1223 1300 380 1300 380 456 1224 456 1224 456 1224 1299 380 456 1224 456 1224 456 8166 1299 380 1299 380 456 1224 1299 380 1299 380 456 1224 456 1224 455 1224 1299 380 456 1224 455 1224 456 7909 1299 380 1299 380 455 1224 1299 380 1299 380 456 1224 455 1225 455 1225 1298 381 455 1225 454 1225 455 diff --git a/applications/main/infrared/resources/infrared/assets/projectors.ir b/applications/main/infrared/resources/infrared/assets/projectors.ir new file mode 100644 index 0000000000..2fef79ee43 --- /dev/null +++ b/applications/main/infrared/resources/infrared/assets/projectors.ir @@ -0,0 +1,1497 @@ +Filetype: IR library file +Version: 1 +# Last Updated 1st Oct, 2023 +# Last Checked 1st Oct, 2023 +# +# TEMP FIX FOR POWER +# +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 310 27591 171 27662 241 27731 307 27575 107 27749 306 27551 130 55520 243 27614 217 55584 129 27743 119 27756 115 27747 163 27712 308 27502 243 27650 217 27732 175 27693 167 27698 166 27689 171 27622 215 27712 133 27658 216 27716 129 27732 162 27698 305 27571 131 27753 310 27570 170 27707 162 27707 175 10960 9194 4518 618 542 618 543 725 434 672 1623 671 1647 646 514 592 568 592 568 592 1702 592 568 592 567 593 1702 592 568 618 1676 618 1676 618 1676 618 543 617 543 617 543 617 1677 617 544 616 544 616 544 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1679 615 1678 616 1678 616 40239 9196 2250 617 +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 310 27591 171 27662 241 27731 307 27575 107 27749 306 27551 130 55520 243 27614 217 55584 129 27743 119 27756 115 27747 163 27712 308 27502 243 27650 217 27732 175 27693 167 27698 166 27689 171 27622 215 27712 133 27658 216 27716 129 27732 162 27698 305 27571 131 27753 310 27570 170 27707 162 27707 175 10960 9194 4518 618 542 618 543 725 434 672 1623 671 1647 646 514 592 568 592 568 592 1702 592 568 592 567 593 1702 592 568 618 1676 618 1676 618 1676 618 543 617 543 617 543 617 1677 617 544 616 544 616 544 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1679 615 1678 616 1678 616 40239 9196 2250 617 +# +name: Vol_up +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 48 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 49 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 14 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 0B 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 0B 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 48 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 44 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 83 7C 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 82 7D 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 08 13 00 00 +command: 87 78 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9055 4338 672 1551 669 1553 618 1603 619 481 617 482 616 481 617 507 591 1605 645 479 619 1577 645 1578 644 1578 644 479 619 480 618 1581 641 480 617 1605 617 1606 616 1606 615 483 615 1608 614 484 614 484 614 484 614 484 614 484 614 484 614 1609 614 484 614 1609 614 1609 613 1609 613 40058 9000 2068 614 95467 9022 2068 614 +# +name: Power +type: parsed +protocol: NECext +address: 08 13 00 00 +command: 87 78 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9055 4338 672 1551 669 1553 618 1603 619 481 617 482 616 481 617 507 591 1605 645 479 619 1577 645 1578 644 1578 644 479 619 480 618 1581 641 480 617 1605 617 1606 616 1606 615 483 615 1608 614 484 614 484 614 484 614 484 614 484 614 484 614 1609 614 484 614 1609 614 1609 613 1609 613 40058 9000 2068 614 95467 9022 2068 614 +# +name: Mute +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 29 D6 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 08 F7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 04 FB 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 93 6C 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9032 4462 598 501 627 1604 627 530 598 531 677 423 706 422 706 421 707 451 677 1554 677 451 598 1633 598 1634 597 1634 598 1634 598 1634 625 1606 681 1550 626 502 598 530 599 529 600 1632 600 528 600 528 601 528 601 528 601 1631 600 1631 625 1607 625 504 625 1607 624 1608 624 1608 623 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 02 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1D 00 00 00 +# +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9096 4436 620 505 647 478 648 501 623 1599 647 1624 623 502 623 503 621 504 619 1628 618 507 617 507 617 1630 617 508 616 1630 617 1630 617 1631 616 508 616 508 617 508 616 1631 616 508 617 508 617 508 616 508 616 1630 616 1630 616 1631 616 508 616 1630 617 1630 617 1630 617 1631 617 509 616 508 616 509 616 509 616 509 616 509 615 509 616 508 617 1631 616 1631 615 1631 616 1631 616 1631 616 1631 616 1631 615 1631 616 14435 9093 2186 615 96359 9095 2184 617 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1D 00 00 00 +# +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9096 4436 620 505 647 478 648 501 623 1599 647 1624 623 502 623 503 621 504 619 1628 618 507 617 507 617 1630 617 508 616 1630 617 1630 617 1631 616 508 616 508 617 508 616 1631 616 508 617 508 617 508 616 508 616 1630 616 1630 616 1631 616 508 616 1630 617 1630 617 1630 617 1631 617 509 616 508 616 509 616 509 616 509 616 509 615 509 616 508 617 1631 616 1631 615 1631 616 1631 616 1631 616 1631 616 1631 615 1631 616 14435 9093 2186 615 96359 9095 2184 617 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9091 4465 594 530 595 530 594 530 594 1651 595 1652 595 529 621 504 620 504 619 1628 618 507 617 508 616 1631 616 509 615 1631 616 1631 616 1632 615 509 616 509 616 509 615 1631 616 509 616 508 616 1631 616 509 616 1631 615 1631 616 1631 617 508 616 1631 616 1631 616 508 616 1631 617 508 617 509 616 509 616 509 616 509 616 509 616 509 616 509 616 1631 616 1631 616 1631 616 1631 616 1631 615 1631 615 1631 615 1631 616 14435 9090 2190 615 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9092 4439 620 506 619 506 618 530 593 1627 620 1630 643 504 620 505 618 506 617 1630 617 508 616 508 616 1632 616 508 617 1631 616 1631 616 1631 616 1631 616 509 616 508 616 1631 616 509 616 509 615 1632 616 509 616 508 616 1631 616 1631 616 508 616 1631 615 1631 616 509 615 1632 615 509 616 509 616 509 616 509 616 509 616 510 615 509 616 509 616 1631 616 1631 615 1631 616 1631 615 1631 615 1631 615 1631 615 1631 615 14434 9088 2191 615 96339 9115 2189 616 96343 9117 2189 616 96343 9114 2189 616 +# AV-Mute +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9092 4439 620 506 618 506 618 530 594 1627 619 1629 643 505 619 505 619 506 617 1629 617 508 616 508 616 1631 616 508 616 1630 616 1630 616 1630 617 1630 616 1630 616 1631 616 508 616 508 616 508 616 1631 616 508 617 508 616 508 616 508 616 1630 616 1631 615 1631 616 508 616 1631 616 508 617 508 616 509 615 509 616 508 616 509 615 509 616 508 616 1631 615 1631 615 1631 616 1631 615 1631 615 1631 615 1631 615 1631 616 14433 9088 2191 615 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9014 4332 661 1570 661 471 660 473 658 474 657 476 655 498 633 498 634 502 633 499 633 1599 632 1599 632 1599 632 1599 632 1599 632 1600 631 1603 632 500 632 501 631 501 631 501 631 501 631 501 631 1601 631 504 631 1601 631 1601 631 1601 631 1601 631 1601 630 1601 630 501 631 1601 631 38177 8983 2149 630 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9014 4332 661 1570 661 471 660 473 658 474 657 476 655 498 633 498 634 502 633 499 633 1599 632 1599 632 1599 632 1599 632 1599 632 1600 631 1603 632 500 632 501 631 501 631 501 631 501 631 501 631 1601 631 504 631 1601 631 1601 631 1601 631 1601 631 1601 630 1601 630 501 631 1601 631 38177 8983 2149 630 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 4C 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9042 4306 690 1541 665 468 664 468 664 469 663 470 662 471 660 495 636 499 636 497 634 1597 634 1598 633 1598 633 1599 633 1599 632 1599 633 1603 632 1599 633 499 633 499 633 500 632 499 633 500 632 1600 632 503 633 500 632 1600 632 1600 632 1600 633 1600 632 1600 632 500 632 1600 632 37912 8986 2145 633 +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3522 1701 472 426 444 1269 472 426 444 426 443 427 443 427 443 426 444 427 443 426 444 427 442 428 441 429 440 431 438 1304 437 433 437 433 438 433 437 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1305 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 459 411 459 411 459 411 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 459 411 459 411 1330 411 1330 411 460 410 1330 411 1330 411 1331 410 1330 411 74392 3516 1736 436 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 435 435 1305 436 435 435 435 435 1306 435 435 435 435 435 435 435 436 434 436 434 436 434 435 435 436 434 436 434 436 434 1330 411 1331 410 1330 411 1330 411 1330 411 459 411 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 435 436 434 436 1306 435 435 435 435 435 1306 435 435 435 435 435 435 435 435 435 435 435 436 434 436 434 435 435 436 434 435 435 1306 435 1330 411 1307 434 1331 410 1308 433 436 434 436 434 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 435 435 434 436 434 436 434 436 434 436 434 436 1306 435 435 435 435 435 435 435 1306 435 435 435 436 434 1306 435 435 435 436 434 436 434 435 435 436 434 436 434 460 410 460 410 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 434 436 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 435 435 435 435 434 436 1306 435 434 436 435 435 435 435 1306 435 436 434 435 435 1306 435 435 435 436 434 436 434 436 434 436 434 460 410 437 433 459 411 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3514 1736 437 434 436 1304 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 435 435 434 436 434 436 435 435 434 436 1305 436 435 435 435 435 435 435 1306 435 435 435 435 435 1306 435 435 435 436 434 435 435 459 411 436 434 435 435 459 411 459 411 459 411 459 411 1330 411 1306 435 1330 411 1330 411 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 529 7218 126 6585 219 703 180 5362 427 18618 177 +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3522 1701 472 426 444 1269 472 426 444 426 443 427 443 427 443 426 444 427 443 426 444 427 442 428 441 429 440 431 438 1304 437 433 437 433 438 433 437 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1305 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 459 411 459 411 459 411 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 459 411 459 411 1330 411 1330 411 460 410 1330 411 1330 411 1331 410 1330 411 74392 3516 1736 436 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 435 435 1305 436 435 435 435 435 1306 435 435 435 435 435 435 435 436 434 436 434 436 434 435 435 436 434 436 434 436 434 1330 411 1331 410 1330 411 1330 411 1330 411 459 411 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 435 436 434 436 1306 435 435 435 435 435 1306 435 435 435 435 435 435 435 435 435 435 435 436 434 436 434 435 435 436 434 435 435 1306 435 1330 411 1307 434 1331 410 1308 433 436 434 436 434 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 435 435 434 436 434 436 434 436 434 436 434 436 1306 435 435 435 435 435 435 435 1306 435 435 435 436 434 1306 435 435 435 436 434 436 434 435 435 436 434 436 434 460 410 460 410 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 434 436 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 435 435 435 435 434 436 1306 435 434 436 435 435 435 435 1306 435 436 434 435 435 1306 435 435 435 436 434 436 434 436 434 436 434 460 410 437 433 459 411 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3514 1736 437 434 436 1304 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 435 435 434 436 434 436 435 435 434 436 1305 436 435 435 435 435 435 435 1306 435 435 435 435 435 1306 435 435 435 436 434 435 435 459 411 436 434 435 435 459 411 459 411 459 411 459 411 1330 411 1306 435 1330 411 1330 411 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 529 7218 126 6585 219 703 180 5362 427 18618 177 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9069 4362 622 486 621 487 621 491 622 1608 623 1603 622 487 621 487 621 491 622 1604 621 487 622 491 622 1604 621 491 622 1608 622 1609 621 1604 622 486 622 487 621 491 621 1605 621 487 621 491 622 1604 622 491 621 1609 621 1609 621 1604 622 491 621 1609 622 1604 621 491 621 1604 622 487 621 487 622 486 622 487 621 488 621 487 621 488 620 491 621 1609 622 1609 620 1609 621 1609 621 1609 621 1609 621 1609 621 1618 621 14330 9047 2137 620 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9047 4362 621 486 622 463 645 490 622 1609 622 1604 622 487 620 487 621 491 622 1604 622 484 625 490 621 1605 649 463 621 1609 620 1611 621 1608 622 1605 621 486 622 491 622 1604 621 487 621 492 620 1604 621 488 621 492 620 1609 622 1604 621 492 622 1609 620 1605 621 491 622 1603 622 488 621 488 620 488 620 488 621 488 620 487 622 485 621 492 596 1635 621 1609 622 1585 643 1611 620 1608 621 1610 619 1611 620 1619 619 14332 9074 2109 647 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9073 4336 648 461 647 484 624 489 623 1607 623 1603 622 486 622 486 622 491 622 1604 621 487 621 491 622 1604 622 491 621 1609 621 1609 621 1609 621 1608 622 1609 621 1604 621 486 622 486 622 491 622 1604 622 486 622 487 621 487 621 491 622 1608 622 1609 621 1604 622 491 621 1604 621 487 621 486 622 487 621 487 621 487 621 487 621 487 621 491 622 1608 622 1608 622 1609 621 1608 622 1608 622 1608 622 1609 621 1617 622 14330 9047 2137 620 +# ON +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 4F B0 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 80 19 00 00 +command: 10 EF 00 00 +# ON +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 4F B0 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 80 19 00 00 +command: 10 EF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 80 19 00 00 +command: 1C E3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 80 19 00 00 +command: 46 B9 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 51 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 0A F5 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 4E B1 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 51 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 0A F5 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 4E B1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 0E F1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 0D F2 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 4F B0 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 4F B0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 14 EB 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 08 16 00 00 +command: 87 78 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 08 16 00 00 +command: 87 78 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 08 16 00 00 +command: C8 37 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 28 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 29 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 84 F4 00 00 +command: 0B F4 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 00 FF 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 84 F4 00 00 +command: 0B F4 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 00 FF 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 1D E2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 0B F4 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 90 6F 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 90 6F 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 99 66 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 98 67 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 1C E3 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 1C E3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4F B0 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4B B4 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 02 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 2E 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 02 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 2E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 52 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 51 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 56 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 5A 00 00 00 +# +name: Power +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 15 00 00 00 +# +name: Power +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 82 7D 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 83 7C 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 14 EB 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 91 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 90 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 91 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 90 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 31 00 00 00 +command: D0 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 89 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 00 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 30 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 31 00 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 32 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 30 00 00 00 +command: 00 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 0D 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9032 4479 597 560 572 558 564 566 566 1666 589 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 1669 596 560 562 1671 594 1666 588 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 566 566 563 569 1664 591 1669 596 1664 590 565 567 1667 598 1661 593 1666 588 1671 594 562 570 560 562 568 564 565 567 563 569 560 562 568 564 565 567 1666 588 1671 594 1665 589 1670 595 1665 590 1669 596 1664 590 1668 597 13983 9029 2222 599 96237 9030 2221 589 96244 9034 2217 594 96244 9033 2218 592 96249 9038 2213 597 96239 9037 2214 596 96238 9028 2223 598 96221 9032 2215 595 +# +name: Power +type: parsed +protocol: NECext +address: 30 00 00 00 +command: 00 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 0D 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9032 4479 597 560 572 558 564 566 566 1666 589 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 1669 596 560 562 1671 594 1666 588 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 566 566 563 569 1664 591 1669 596 1664 590 565 567 1667 598 1661 593 1666 588 1671 594 562 570 560 562 568 564 565 567 563 569 560 562 568 564 565 567 1666 588 1671 594 1665 589 1670 595 1665 590 1669 596 1664 590 1668 597 13983 9029 2222 599 96237 9030 2221 589 96244 9034 2217 594 96244 9033 2218 592 96249 9038 2213 597 96239 9037 2214 596 96238 9028 2223 598 96221 9032 2215 595 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4482 593 563 569 561 571 559 563 1698 566 1694 570 559 563 568 564 566 566 1695 569 560 572 559 563 1671 593 563 569 1692 562 1671 593 1693 571 558 564 567 565 565 567 1693 571 532 590 567 565 1695 569 560 562 1698 566 1694 570 1663 591 539 593 1693 571 1688 566 564 568 1691 563 567 565 565 567 563 569 561 571 559 563 567 565 565 567 563 569 1690 564 1695 569 1691 563 1696 568 1691 563 1697 567 1692 562 1697 567 13988 9030 2223 597 96250 9035 2219 591 96245 9032 2221 589 96240 9038 2215 595 96235 9033 2220 590 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9028 4482 593 563 569 561 571 558 564 1696 568 1690 564 566 566 563 569 561 571 1688 566 563 569 561 571 1688 566 563 569 1690 564 1695 569 1689 565 1668 596 560 562 568 564 1695 569 560 562 568 564 1695 569 560 562 568 564 1695 569 1690 564 566 566 1692 572 1687 567 563 569 1690 564 566 566 564 568 562 570 559 563 567 565 565 567 562 570 560 562 1696 568 1665 589 1670 594 1665 589 1670 594 1664 590 1669 647 1612 590 13987 9031 2220 590 96223 9033 2217 593 96223 9034 2218 592 96225 9032 2219 591 96221 9087 2164 595 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9031 4479 596 560 572 558 564 566 566 1693 571 1688 566 563 569 561 571 559 563 1696 568 561 571 559 563 1697 567 562 570 1689 565 1694 570 1688 566 1693 571 1661 593 1693 571 558 564 566 566 564 568 1691 563 541 591 564 568 562 570 560 562 1697 567 1692 562 1696 568 562 570 1689 565 564 568 561 571 559 563 567 565 564 568 562 570 560 562 567 565 1694 570 1689 565 1694 570 1688 566 1693 571 1688 566 1693 571 1662 592 13987 9031 2220 590 96231 9034 2217 593 96234 9030 2222 588 96247 9037 2215 595 +# +name: Vol_up +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 14 00 00 00 +# OFF +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 4E B1 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 1D 00 00 00 +# OFF +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 4E B1 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 1D 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 15 00 00 00 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9075 4307 677 433 675 456 651 461 651 1579 650 1576 649 459 649 460 648 465 648 1578 647 461 622 491 622 1604 647 465 647 1583 622 1608 647 1579 647 461 647 466 622 1604 647 465 647 1579 647 461 645 463 648 465 648 1583 646 1580 646 466 647 1579 622 491 647 1583 622 1608 647 1579 647 461 647 461 622 486 622 486 647 461 647 462 646 462 622 491 646 1584 622 1608 647 1584 621 1608 647 1583 646 1584 647 1584 646 1592 622 14330 9047 2137 621 +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: E6 00 00 00 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9075 4307 677 433 675 456 651 461 651 1579 650 1576 649 459 649 460 648 465 648 1578 647 461 622 491 622 1604 647 465 647 1583 622 1608 647 1579 647 461 647 466 622 1604 647 465 647 1579 647 461 645 463 648 465 648 1583 646 1580 646 466 647 1579 622 491 647 1583 622 1608 647 1579 647 461 647 461 622 486 622 486 647 461 647 462 646 462 622 491 646 1584 622 1608 647 1584 621 1608 647 1583 646 1584 647 1584 646 1592 622 14330 9047 2137 621 +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: E6 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 07 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 0B 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 0F 00 00 00 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3523 1701 472 426 444 1269 472 426 444 426 442 429 443 427 443 426 444 426 444 426 443 427 442 429 440 430 439 432 438 1304 437 433 437 432 438 432 438 433 437 433 437 433 437 433 437 433 437 433 437 1304 437 433 437 433 437 433 437 1304 437 433 437 433 437 1304 437 433 437 434 436 433 437 434 436 434 436 434 436 433 437 433 437 434 436 1304 437 1305 436 1305 436 1305 436 1305 436 1305 436 434 436 434 436 1305 436 1305 436 1305 436 434 436 1305 436 1305 436 1306 435 1306 435 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 1304 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 436 434 435 435 1307 434 1331 410 1307 434 1307 434 1330 411 1307 434 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 434 436 433 437 433 437 1304 437 434 436 434 436 434 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 435 435 434 436 1305 436 434 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1306 435 1307 434 1307 434 1307 434 1331 410 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 1304 437 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 437 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3523 1701 472 426 444 1269 472 426 444 426 442 429 443 427 443 426 444 426 444 426 443 427 442 429 440 430 439 432 438 1304 437 433 437 432 438 432 438 433 437 433 437 433 437 433 437 433 437 433 437 1304 437 433 437 433 437 433 437 1304 437 433 437 433 437 1304 437 433 437 434 436 433 437 434 436 434 436 434 436 433 437 433 437 434 436 1304 437 1305 436 1305 436 1305 436 1305 436 1305 436 434 436 434 436 1305 436 1305 436 1305 436 434 436 1305 436 1305 436 1306 435 1306 435 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 1304 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 436 434 435 435 1307 434 1331 410 1307 434 1307 434 1330 411 1307 434 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 434 436 433 437 433 437 1304 437 434 436 434 436 434 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 435 435 434 436 1305 436 434 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1306 435 1307 434 1307 434 1307 434 1331 410 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 1304 437 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 437 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9093 4441 620 507 618 530 594 531 593 1652 595 1653 620 505 620 505 619 506 617 1630 616 508 616 508 616 1632 615 509 615 1631 616 1632 615 1632 615 510 615 509 615 1632 615 509 615 1632 615 510 615 510 614 509 615 1632 614 1633 614 509 615 1633 614 509 615 1632 615 1632 614 1633 614 510 614 510 615 510 615 510 614 510 614 510 615 510 615 510 614 1632 615 1632 614 1632 615 1632 615 1632 615 1632 615 1632 615 1633 614 14439 9088 2192 614 96349 9112 2190 616 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9093 4441 620 507 618 530 594 531 593 1652 595 1653 620 505 620 505 619 506 617 1630 616 508 616 508 616 1632 615 509 615 1631 616 1632 615 1632 615 510 615 509 615 1632 615 509 615 1632 615 510 615 510 614 509 615 1632 614 1633 614 509 615 1633 614 509 615 1632 615 1632 614 1633 614 510 614 510 615 510 615 510 614 510 614 510 615 510 615 510 614 1632 615 1632 614 1632 615 1632 615 1632 615 1632 615 1632 615 1633 614 14439 9088 2192 614 96349 9112 2190 616 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 219 27658 217 27663 216 27658 216 27634 216 27642 215 27646 217 27662 217 27637 216 27649 216 27649 218 27656 217 27658 215 27640 214 27636 217 27649 216 27644 218 27635 217 27630 215 27645 216 27631 215 27632 216 27650 216 27628 217 27630 214 27627 217 27623 215 27632 215 27641 216 27634 214 27633 215 27648 215 27648 217 27651 215 27635 216 27629 216 27630 216 2021 9254 4461 618 542 618 542 618 542 618 1675 619 1676 618 541 619 541 619 542 618 1677 617 543 617 543 617 1678 616 568 592 1702 592 1702 618 1676 618 542 618 542 618 543 617 1677 617 543 617 544 616 1678 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1678 616 544 616 1678 616 40239 9200 2247 617 99930 110 27739 119 27738 123 27750 126 27738 175 27617 214 27716 203 27604 213 27639 217 27631 214 27722 136 27753 119 27736 175 27618 246 27683 177 27619 245 27685 171 55486 244 27693 158 27635 241 27695 170 27693 129 27717 340 27530 113 27757 106 27751 124 27728 172 27707 126 27666 215 27708 123 27733 123 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 18 E9 00 00 +command: 49 B6 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 14 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 14 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 48 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 40 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 18 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 0C F3 00 00 +# +name: Power +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 0C F3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 0D F2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 1F E0 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 81 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 81 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 8F 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 8C 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9066 4428 608 507 609 1622 609 507 609 507 609 1623 608 1623 609 507 609 506 610 1623 609 507 609 1622 610 1623 608 507 609 506 610 1622 609 1623 609 506 610 1622 610 506 610 1623 637 478 690 425 638 478 637 1594 637 1594 664 451 636 1594 610 506 610 1621 611 1621 610 1621 610 505 611 40183 9065 2156 637 95953 9037 2185 608 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: A8 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: A8 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 88 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 9C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8C 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 17 E8 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 17 E8 00 00 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9064 4354 666 1559 666 1562 662 1586 638 475 636 477 635 477 635 478 635 1590 635 1591 634 478 635 1591 634 478 634 478 635 478 634 1591 635 478 634 1591 634 478 635 478 634 478 635 1591 634 478 634 1591 635 478 634 478 634 1591 634 1591 635 1591 634 478 635 1591 634 478 634 1591 635 40957 9035 2144 634 95483 9047 2155 632 95484 9048 2153 633 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 50 AF 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4385 638 1587 664 1562 663 1587 637 476 635 478 634 478 635 478 635 1591 634 1591 634 478 635 1591 635 478 634 478 635 478 635 1591 635 478 634 478 634 1591 634 478 635 479 634 1591 635 478 634 1591 635 478 634 1592 634 478 634 1591 635 1591 635 478 634 1592 634 478 634 1591 634 40958 9033 2144 635 +# +name: Power +type: parsed +protocol: NECext +address: FF FF 00 00 +command: E8 17 00 00 +# +name: Power +type: parsed +protocol: NECext +address: FF FF 00 00 +command: E8 17 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: FF FF 00 00 +command: BD 42 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: FF FF 00 00 +command: F2 0D 00 00 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 05 00 00 00 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 70 01 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 71 01 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 81 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 17 E8 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 81 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 17 E8 00 00 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9010 4413 532 1617 532 1617 533 489 533 489 533 489 558 464 558 465 557 1593 557 465 557 466 556 1594 555 467 555 1595 529 1621 554 1595 581 1569 581 441 581 1569 581 441 581 441 581 441 581 441 581 441 581 1569 581 1569 581 441 581 1569 580 1569 580 1570 580 1595 554 1595 555 468 554 42156 8983 2135 556 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9032 4390 556 1592 559 1591 559 463 559 463 558 464 557 465 556 465 557 1593 583 440 581 441 580 1569 581 441 581 1569 580 1569 581 1569 581 1570 580 1596 554 1596 554 468 554 468 554 468 554 442 580 442 580 1596 554 469 553 469 553 1596 554 1596 553 1597 550 1598 553 1598 552 469 551 42155 9008 2107 531 95218 9006 2108 582 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9011 4388 557 1617 532 1617 532 489 533 489 558 464 558 440 582 440 582 1593 556 466 556 466 556 1594 556 467 555 1595 555 1595 529 1620 554 1596 554 467 554 468 555 1595 579 443 581 1569 581 441 581 441 580 442 581 1569 581 1569 581 441 581 1569 580 441 581 1569 581 1569 581 1570 579 42152 8957 2159 556 +# +name: Power +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 02 FD 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 02 FD 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 08 F7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0B F4 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 81 03 00 00 +command: F0 0F 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 51 AE 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 52 AD 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 06 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 09 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1A 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 00 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 00 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9035 4437 563 548 563 548 563 522 594 1645 591 1639 592 518 593 548 563 552 563 1640 592 548 563 553 562 1668 564 524 592 1642 594 1674 562 1673 563 1639 593 548 563 552 564 1669 562 548 563 520 615 529 586 1645 587 529 587 1650 586 1646 586 529 586 1650 586 1649 587 1646 586 524 587 524 587 524 587 524 587 525 643 467 644 440 671 467 644 472 643 1592 644 1593 643 1593 642 1594 641 1594 587 1649 585 1651 563 1682 562 14430 9008 2205 562 +# +name: Vol_up +type: parsed +protocol: NECext +address: 84 F4 00 00 +command: 2C D3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 84 F4 00 00 +command: 2F D0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0F F0 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 12 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 06 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 00 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 04 B1 00 00 +command: 58 A7 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 04 B1 00 00 +command: 58 A7 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9107 4376 681 1573 681 472 655 472 654 474 652 475 652 476 651 476 652 476 651 476 651 1604 651 1604 651 1604 651 1603 652 1604 651 1604 651 1604 651 1604 650 476 651 477 650 477 650 476 651 477 651 1604 650 476 651 477 650 1604 651 1604 650 1604 651 1604 650 1604 651 477 650 1604 650 39498 9079 2178 651 +# +name: Power +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 12 ED 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 12 ED 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 44 BB 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 46 B9 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 11 EE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 81 03 00 00 +command: F0 0F 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 01 FE 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 01 FE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 10 EF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 0C F3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 6A 95 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 11 00 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 51 AE 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 00 30 00 00 +command: A8 57 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 00 30 00 00 +command: A9 56 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 5E A1 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 5B A4 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 93 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 93 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 15 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 15 00 00 00 +# +name: Play +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 18 E7 00 00 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8981 4411 532 1616 557 1592 557 465 556 465 556 466 555 467 554 468 554 1595 554 467 554 468 554 1595 554 467 580 1569 581 1568 581 1568 581 1569 580 441 581 1569 580 442 580 1570 579 1594 555 467 555 443 579 442 580 1594 554 467 555 1594 555 467 555 468 554 1595 554 1595 528 1620 529 42169 9008 2106 531 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8981 4411 532 1616 557 1592 557 465 556 465 556 466 555 467 554 468 554 1595 554 467 554 468 554 1595 554 467 580 1569 581 1568 581 1568 581 1569 580 441 581 1569 580 442 580 1570 579 1594 555 467 555 443 579 442 580 1594 554 467 555 1594 555 467 555 468 554 1595 554 1595 528 1620 529 42169 9008 2106 531 +# +name: Play +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 41 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 41 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 03 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 03 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 02 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 17 E8 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 17 E8 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 07 F8 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0A F5 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 363 1732 365 685 363 1734 363 1735 362 686 362 1735 362 687 361 685 362 1735 363 686 267 1830 362 1734 363 686 268 1830 361 687 267 42739 267 1829 268 780 268 1830 267 1829 268 781 267 781 267 1832 265 1830 267 780 268 1830 267 781 267 780 268 1830 267 781 267 1830 267 42723 267 1830 267 781 362 1735 267 1829 268 781 267 1830 267 782 266 780 268 1830 267 781 267 1831 266 1830 267 781 267 1829 268 782 266 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 270 1825 272 780 268 1827 349 1747 350 699 349 698 351 698 350 1748 350 698 350 1748 349 699 349 697 351 699 349 1746 351 698 350 44740 349 1745 352 698 350 1747 350 1747 350 698 350 1747 355 1742 355 692 356 1742 355 694 355 1743 354 1742 355 1742 355 694 354 1742 355 40555 355 1742 355 693 355 1742 355 1743 354 693 355 694 354 693 355 1743 354 693 355 1742 355 693 355 692 357 693 355 1741 356 694 354 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 353 1742 355 693 355 1742 355 1742 355 693 355 1744 353 694 354 1743 354 693 355 1743 354 694 354 693 355 694 354 1742 355 695 353 43687 354 1743 354 696 352 1744 353 1744 353 694 354 695 353 1742 355 694 354 1743 354 694 354 1743 354 1742 355 1742 355 694 354 1744 353 41606 351 1745 352 696 352 1746 351 1746 351 697 351 1747 350 696 352 1745 352 698 350 1746 351 698 350 696 352 698 350 1746 351 699 349 +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 02 00 00 00 +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 02 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: D1 00 00 00 From 33ed5ea1f20209754217cd4915b14c80f30187f3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:10:09 +0400 Subject: [PATCH 029/111] upd tv ir from pr manually --- .../infrared/resources/infrared/assets/tv.ir | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index e7a7525feb..d2164b16d2 100755 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 1st Oct, 2023 -# Last Checked 1st Oct, 2023 +# Last Updated 29th Oct, 2023 +# Last Checked 29th Oct, 2023 # name: Power type: parsed @@ -2393,3 +2393,69 @@ type: parsed protocol: NECext address: 69 69 00 00 command: 0E F1 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 0B F4 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 1E E1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 5F A0 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 5C A3 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 01 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 18 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 1C 00 00 00 From a7e6eb7fe48f11f3ad88895ecc1e6297f336d176 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:41:41 +0400 Subject: [PATCH 030/111] clock lol --- applications/main/clock_app/application.fam | 1 + .../resources/ibtnfuzzer/example_uids_cyfral.txt | 8 ++++++++ .../resources/ibtnfuzzer/example_uids_ds1990.txt | 11 +++++++++++ .../resources/ibtnfuzzer/example_uids_metakom.txt | 9 +++++++++ .../resources/music_player/Marble_Machine.fmf | 6 ++++++ .../resources/rfidfuzzer/example_uids_em4100.txt | 8 ++++++++ .../resources/rfidfuzzer/example_uids_h10301.txt | 8 ++++++++ .../resources/rfidfuzzer/example_uids_hidprox.txt | 8 ++++++++ .../resources/rfidfuzzer/example_uids_pac.txt | 8 ++++++++ .../resources/subplaylist/example_playlist.txt | 5 +++++ .../main/clock_app/resources/swd_scripts/100us.swd | 1 + .../clock_app/resources/swd_scripts/call_test_1.swd | 6 ++++++ .../clock_app/resources/swd_scripts/call_test_2.swd | 7 +++++++ .../resources/swd_scripts/dump_0x00000000_1k.swd | 6 ++++++ .../resources/swd_scripts/dump_0x00000000_4b.swd | 6 ++++++ .../clock_app/resources/swd_scripts/dump_STM32.swd | 6 ++++++ .../clock_app/resources/swd_scripts/goto_test.swd | 7 +++++++ .../main/clock_app/resources/swd_scripts/halt.swd | 11 +++++++++++ .../main/clock_app/resources/swd_scripts/reset.swd | 8 ++++++++ .../clock_app/resources/swd_scripts/test_write.swd | 3 +++ 20 files changed, 133 insertions(+) create mode 100644 applications/main/clock_app/resources/ibtnfuzzer/example_uids_cyfral.txt create mode 100644 applications/main/clock_app/resources/ibtnfuzzer/example_uids_ds1990.txt create mode 100644 applications/main/clock_app/resources/ibtnfuzzer/example_uids_metakom.txt create mode 100644 applications/main/clock_app/resources/music_player/Marble_Machine.fmf create mode 100644 applications/main/clock_app/resources/rfidfuzzer/example_uids_em4100.txt create mode 100644 applications/main/clock_app/resources/rfidfuzzer/example_uids_h10301.txt create mode 100644 applications/main/clock_app/resources/rfidfuzzer/example_uids_hidprox.txt create mode 100644 applications/main/clock_app/resources/rfidfuzzer/example_uids_pac.txt create mode 100644 applications/main/clock_app/resources/subplaylist/example_playlist.txt create mode 100644 applications/main/clock_app/resources/swd_scripts/100us.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/call_test_1.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/call_test_2.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/dump_0x00000000_1k.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/dump_0x00000000_4b.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/dump_STM32.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/goto_test.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/halt.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/reset.swd create mode 100644 applications/main/clock_app/resources/swd_scripts/test_write.swd diff --git a/applications/main/clock_app/application.fam b/applications/main/clock_app/application.fam index 47f152b1d0..7dc7fb12a9 100644 --- a/applications/main/clock_app/application.fam +++ b/applications/main/clock_app/application.fam @@ -6,6 +6,7 @@ App( icon="A_Clock_14", stack_size=2 * 1024, order=81, + resources="resources", fap_icon="icon.png", fap_category="Tools", ) diff --git a/applications/main/clock_app/resources/ibtnfuzzer/example_uids_cyfral.txt b/applications/main/clock_app/resources/ibtnfuzzer/example_uids_cyfral.txt new file mode 100644 index 0000000000..497d2211a3 --- /dev/null +++ b/applications/main/clock_app/resources/ibtnfuzzer/example_uids_cyfral.txt @@ -0,0 +1,8 @@ +# Example file, P.S. keep empty line at the end! +0000 +F000 +FE00 +CAFE +00CA +FF00 +FFFF diff --git a/applications/main/clock_app/resources/ibtnfuzzer/example_uids_ds1990.txt b/applications/main/clock_app/resources/ibtnfuzzer/example_uids_ds1990.txt new file mode 100644 index 0000000000..6828bb4238 --- /dev/null +++ b/applications/main/clock_app/resources/ibtnfuzzer/example_uids_ds1990.txt @@ -0,0 +1,11 @@ +# Example file, P.S. keep empty line at the end! +0000000000000000 +FE00000000000000 +CAFE000000000000 +00CAFE0000000000 +0000CAFE00000000 +000000CAFE000000 +00000000CA000000 +0000000000A00000 +00000000000123FF +FFFFFFFFFFFFFFFF diff --git a/applications/main/clock_app/resources/ibtnfuzzer/example_uids_metakom.txt b/applications/main/clock_app/resources/ibtnfuzzer/example_uids_metakom.txt new file mode 100644 index 0000000000..911ea73b2c --- /dev/null +++ b/applications/main/clock_app/resources/ibtnfuzzer/example_uids_metakom.txt @@ -0,0 +1,9 @@ +# Example file, P.S. keep empty line at the end! +00000000 +F0000000 +E0000000 +FE000000 +CAFE0000 +00CAFE00 +0000CA00 +FFFFFFFF diff --git a/applications/main/clock_app/resources/music_player/Marble_Machine.fmf b/applications/main/clock_app/resources/music_player/Marble_Machine.fmf new file mode 100644 index 0000000000..7403c9a0f1 --- /dev/null +++ b/applications/main/clock_app/resources/music_player/Marble_Machine.fmf @@ -0,0 +1,6 @@ +Filetype: Flipper Music Format +Version: 0 +BPM: 130 +Duration: 8 +Octave: 5 +Notes: E6, P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6, 4P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6 diff --git a/applications/main/clock_app/resources/rfidfuzzer/example_uids_em4100.txt b/applications/main/clock_app/resources/rfidfuzzer/example_uids_em4100.txt new file mode 100644 index 0000000000..46ce16ba8c --- /dev/null +++ b/applications/main/clock_app/resources/rfidfuzzer/example_uids_em4100.txt @@ -0,0 +1,8 @@ +# Example file, P.S. keep empty line at the end! +0000000000 +FE00000000 +CAFE000000 +00CAFE0000 +0000CAFE00 +000000CAFE +00000000CA diff --git a/applications/main/clock_app/resources/rfidfuzzer/example_uids_h10301.txt b/applications/main/clock_app/resources/rfidfuzzer/example_uids_h10301.txt new file mode 100644 index 0000000000..95ea9ac280 --- /dev/null +++ b/applications/main/clock_app/resources/rfidfuzzer/example_uids_h10301.txt @@ -0,0 +1,8 @@ +# Example file, P.S. keep empty line at the end! +000000 +F00000 +E00000 +FE0000 +CAFE00 +00CAFE +0000CA diff --git a/applications/main/clock_app/resources/rfidfuzzer/example_uids_hidprox.txt b/applications/main/clock_app/resources/rfidfuzzer/example_uids_hidprox.txt new file mode 100644 index 0000000000..88683caf1a --- /dev/null +++ b/applications/main/clock_app/resources/rfidfuzzer/example_uids_hidprox.txt @@ -0,0 +1,8 @@ +# Example file, P.S. keep empty line at the end! +000000000000 +00FE00000000 +00CAFE000000 +0000CAFE0000 +000000CAFE00 +00000000CAFE +0000000000CA diff --git a/applications/main/clock_app/resources/rfidfuzzer/example_uids_pac.txt b/applications/main/clock_app/resources/rfidfuzzer/example_uids_pac.txt new file mode 100644 index 0000000000..56ed2069be --- /dev/null +++ b/applications/main/clock_app/resources/rfidfuzzer/example_uids_pac.txt @@ -0,0 +1,8 @@ +# Example file, P.S. keep empty line at the end! +00000000 +F0000000 +FE000000 +CAFE0000 +00CAFE00 +0000CAFE +000000CA diff --git a/applications/main/clock_app/resources/subplaylist/example_playlist.txt b/applications/main/clock_app/resources/subplaylist/example_playlist.txt new file mode 100644 index 0000000000..efa883cb08 --- /dev/null +++ b/applications/main/clock_app/resources/subplaylist/example_playlist.txt @@ -0,0 +1,5 @@ +# Example file, it will not work, you need to add paths to your files! +sub: /ext/subghz/Vehicles/Tesla/Tesla_charge_AM270.sub +sub: /ext/subghz/Vehicles/Tesla/Tesla_charge_AM650.sub +sub: /ext/subghz/Test1.sub +sub: /ext/subghz/Test2.sub \ No newline at end of file diff --git a/applications/main/clock_app/resources/swd_scripts/100us.swd b/applications/main/clock_app/resources/swd_scripts/100us.swd new file mode 100644 index 0000000000..3ad89a0ab1 --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/100us.swd @@ -0,0 +1 @@ +swd_clock_delay 100 diff --git a/applications/main/clock_app/resources/swd_scripts/call_test_1.swd b/applications/main/clock_app/resources/swd_scripts/call_test_1.swd new file mode 100644 index 0000000000..03f5575f41 --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/call_test_1.swd @@ -0,0 +1,6 @@ + +message 0 "gonna call call_test_2" dialog + +call call_test_2 + +message 0 "back now" dialog diff --git a/applications/main/clock_app/resources/swd_scripts/call_test_2.swd b/applications/main/clock_app/resources/swd_scripts/call_test_2.swd new file mode 100644 index 0000000000..f358b6ece3 --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/call_test_2.swd @@ -0,0 +1,7 @@ + +# first do a beeeeeep +beep 1 + +message 0 "Seems to work" dialog + +beep 0 diff --git a/applications/main/clock_app/resources/swd_scripts/dump_0x00000000_1k.swd b/applications/main/clock_app/resources/swd_scripts/dump_0x00000000_1k.swd new file mode 100644 index 0000000000..a8870fe30e --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/dump_0x00000000_1k.swd @@ -0,0 +1,6 @@ +ap_select 0 +max_tries 50 +block_size 4 +mem_dump /ext/swd_scripts/flash.bin 0x00000000 0x100000 2 +beep 1 +message 5 "Reading sucessful" diff --git a/applications/main/clock_app/resources/swd_scripts/dump_0x00000000_4b.swd b/applications/main/clock_app/resources/swd_scripts/dump_0x00000000_4b.swd new file mode 100644 index 0000000000..a8870fe30e --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/dump_0x00000000_4b.swd @@ -0,0 +1,6 @@ +ap_select 0 +max_tries 50 +block_size 4 +mem_dump /ext/swd_scripts/flash.bin 0x00000000 0x100000 2 +beep 1 +message 5 "Reading sucessful" diff --git a/applications/main/clock_app/resources/swd_scripts/dump_STM32.swd b/applications/main/clock_app/resources/swd_scripts/dump_STM32.swd new file mode 100644 index 0000000000..e675537c98 --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/dump_STM32.swd @@ -0,0 +1,6 @@ +ap_select 0 +max_tries 50 +block_size 1024 +mem_dump /ext/swd_scripts/flash.bin 0x08000000 0x100000 2 +beep 1 +message 0 "Reading finished" dialog diff --git a/applications/main/clock_app/resources/swd_scripts/goto_test.swd b/applications/main/clock_app/resources/swd_scripts/goto_test.swd new file mode 100644 index 0000000000..680285653b --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/goto_test.swd @@ -0,0 +1,7 @@ +beep 1 +goto l2 +.label l1 +beep 0 +.label l2 +beep 1 +goto l1 diff --git a/applications/main/clock_app/resources/swd_scripts/halt.swd b/applications/main/clock_app/resources/swd_scripts/halt.swd new file mode 100644 index 0000000000..6aad4c194b --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/halt.swd @@ -0,0 +1,11 @@ + +# make sure errors do not cause a script abort +errors ignore + +message 0 "HAMMER TIME! Trying to halt CPU" +ap_select 0 + +# loop writing the halt bits +.label l1 +mem_write 0xE000EDF0 0xA05F0003 +goto l1 diff --git a/applications/main/clock_app/resources/swd_scripts/reset.swd b/applications/main/clock_app/resources/swd_scripts/reset.swd new file mode 100644 index 0000000000..1872757fb4 --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/reset.swd @@ -0,0 +1,8 @@ +errors ignore +status 0 +message 0 "HAMMER TIME! Try to halt the CPU" +.label l1 +ap_select 0 +mem_write 0xE000EDF0 0xA05F0001 +mem_write 0xE000ED0C 0x05FA0004 +goto l1 diff --git a/applications/main/clock_app/resources/swd_scripts/test_write.swd b/applications/main/clock_app/resources/swd_scripts/test_write.swd new file mode 100644 index 0000000000..df69461fde --- /dev/null +++ b/applications/main/clock_app/resources/swd_scripts/test_write.swd @@ -0,0 +1,3 @@ +mem_write 0x20002000 0xdeadbeef +mem_write 0xE000EDF0 0xA05F0001 +mem_write 0xE000EDF0 0xA05F0007 From 16ffa2bf75e6acbecc0b0d48f792a630525c0db2 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 9 Nov 2023 10:18:36 +0400 Subject: [PATCH 031/111] [FL-3657] Fix NFC unit tests (#3192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/nfc/helpers/nfc_data_generator.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 2783051622..011f9f6db7 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -28,6 +28,7 @@ static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, static void nfc_generate_mf_ul_uid(uint8_t* uid) { uid[0] = NXP_MANUFACTURER_ID; furi_hal_random_fill_buf(&uid[1], 6); + uid[3] |= 0x01; // To avoid forbidden 0x88 value // I'm not sure how this is generated, but the upper nybble always seems to be 8 uid[6] &= 0x0F; uid[6] |= 0x80; @@ -322,6 +323,7 @@ static void nfc_generate_ntag_i2c_plus_2k(NfcDevice* nfc_device) { static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { uid[0] = NXP_MANUFACTURER_ID; furi_hal_random_fill_buf(&uid[1], length - 1); + uid[3] |= 0x01; // To avoid forbidden 0x88 value } static void From 49dcf81743859c17ee332f882e838500206f8039 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:22:34 +0300 Subject: [PATCH 032/111] [FL-3618] Infrared remote button index support (#3180) * Do not load all signals at once (Draft) * Minor cleanup * Refactor remote renaming * Improve function signatures * Rename infrared_remote functions * Optimise signal loading * Implement adding signals to remote * Add read_name() method * Deprecate a function * Partially implement deleting signals (draft) * Use m-array instead of m-list for signal name directory * Use plain C strings instead of furi_string * Implement deleting signals * Implement deleting signals via generalised callback * Implement renaming signals * Rename some types * Some more renaming * Remove unused type * Implement inserting signals (internal use) * Improve InfraredMoveView * Send an event to move a signal * Remove unused type * Implement moving signals * Implement creating new remotes with one signal * Un-deprecate and rename a function * Add InfraredRemote API docs * Add InfraredSignal API docs * Better error messages * Show progress pop-up when moving buttons in a remote * Copy labels to the InfraredMoveView to avoid pointer invalidation * Improve file selection scene * Show progress pop-up when renaming buttons in a remote * Refactor a scene * Show progress when deleting a button from remote * Use a random name for temp files * Add docs to infrared_brute_force.h * Rename Infrared type to InfraredApp * Add docs to infrared_app_i.h * Deliver event data via a callback * Bundle event data together with event type * Change DataExchange behaviour * Adapt RPC debug app to new API * Remove rogue output * Add Doxygen comments to rpc_app.h * Simplify rpc_app.c code * Remove superflous parameter * Do not allocate protobuf messages on the stack * Fix GetError response * Support for button indices * Comment out shallow submodules * Fix F18 api * Fix logical error and add more debug output * fbt: testing unshallow for protobuf * github: lint&checks: unshallow prior to checks * Fix a TODO * github: do not unshallow the unshallowed * fbt: assets: only attempt to unshallow if cannot describe * Do not use the name when loading a signal by index (duh) * Simplify loading infrared signals by name * Sync with protobuf release * Infrared: use compact furi_crash macros Co-authored-by: hedger Co-authored-by: hedger Co-authored-by: Aleksandr Kutuzov --- .../workflows/lint_and_submodule_check.yml | 2 +- .../debug/rpc_debug_app/rpc_debug_app.c | 39 +- ...pc_debug_app_scene_receive_data_exchange.c | 39 +- applications/main/ibutton/ibutton.c | 14 +- .../main/ibutton/ibutton_custom_event.h | 2 +- .../main/ibutton/scenes/ibutton_scene_rpc.c | 23 +- applications/main/infrared/infrared_app.c | 34 +- applications/main/infrared/infrared_app_i.h | 1 + .../main/infrared/infrared_brute_force.c | 2 +- .../main/infrared/infrared_custom_event.h | 5 +- applications/main/infrared/infrared_remote.c | 7 +- applications/main/infrared/infrared_signal.c | 36 +- applications/main/infrared/infrared_signal.h | 21 +- .../infrared/scenes/infrared_scene_remote.c | 6 +- .../main/infrared/scenes/infrared_scene_rpc.c | 45 +- applications/main/lfrfid/lfrfid.c | 12 +- .../main/lfrfid/scenes/lfrfid_scene_rpc.c | 8 +- .../main/nfc/helpers/nfc_custom_event.h | 2 +- .../protocol_support/nfc_protocol_support.c | 16 +- applications/main/nfc/nfc_app.c | 14 +- .../main/subghz/scenes/subghz_scene_rpc.c | 16 +- applications/main/subghz/subghz.c | 16 +- applications/services/rpc/rpc.c | 3 + applications/services/rpc/rpc_app.c | 465 +++++++++--------- applications/services/rpc/rpc_app.h | 202 +++++++- assets/protobuf | 2 +- scripts/fbt_tools/fbt_assets.py | 45 +- targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- 29 files changed, 670 insertions(+), 415 deletions(-) diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 51e2593ecc..3f4d8c79bd 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -23,7 +23,7 @@ jobs: - name: 'Check protobuf branch' run: | - git submodule update --init + git submodule update --init; SUB_PATH="assets/protobuf"; SUB_BRANCH="dev"; SUB_COMMITS_MIN=40; diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.c b/applications/debug/rpc_debug_app/rpc_debug_app.c index 314be5e722..2a0756383c 100644 --- a/applications/debug/rpc_debug_app/rpc_debug_app.c +++ b/applications/debug/rpc_debug_app/rpc_debug_app.c @@ -21,22 +21,51 @@ static void rpc_debug_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -static void rpc_debug_app_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void + rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) { + if(data == NULL || data_size == 0) { + strncpy(buf, "", buf_size); + return; + } + + const size_t byte_width = 3; + const size_t line_width = 7; + + data_size = MIN(data_size, buf_size / (byte_width + 1)); + + for(size_t i = 0; i < data_size; ++i) { + char* p = buf + (i * byte_width); + char sep = !((i + 1) % line_width) ? '\n' : ' '; + snprintf(p, byte_width + 1, "%02X%c", data[i], sep); + } + + buf[buf_size - 1] = '\0'; +} + +static void rpc_debug_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); RpcDebugApp* app = context; furi_assert(app->rpc); - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); rpc_system_app_set_callback(app->rpc, NULL, NULL); app->rpc = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); - rpc_system_app_confirm(app->rpc, RpcAppEventAppExit, true); + rpc_system_app_confirm(app->rpc, true); + } else if(event->type == RpcAppEventTypeDataExchange) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeBytes); + + rpc_debug_app_format_hex( + event->data.bytes.ptr, event->data.bytes.size, app->text_store, TEXT_STORE_SIZE); + + view_dispatcher_send_custom_event( + app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange); } else { - rpc_system_app_confirm(app->rpc, event, false); + rpc_system_app_confirm(app->rpc, false); } } diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c index 98bafff8a7..10d5e36f62 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c @@ -1,40 +1,5 @@ #include "../rpc_debug_app.h" -static void rpc_debug_app_scene_start_format_hex( - const uint8_t* data, - size_t data_size, - char* buf, - size_t buf_size) { - furi_assert(data); - furi_assert(buf); - - const size_t byte_width = 3; - const size_t line_width = 7; - - data_size = MIN(data_size, buf_size / (byte_width + 1)); - - for(size_t i = 0; i < data_size; ++i) { - char* p = buf + (i * byte_width); - char sep = !((i + 1) % line_width) ? '\n' : ' '; - snprintf(p, byte_width + 1, "%02X%c", data[i], sep); - } - - buf[buf_size - 1] = '\0'; -} - -static void rpc_debug_app_scene_receive_data_exchange_callback( - const uint8_t* data, - size_t data_size, - void* context) { - RpcDebugApp* app = context; - if(data) { - rpc_debug_app_scene_start_format_hex(data, data_size, app->text_store, TEXT_STORE_SIZE); - } else { - strncpy(app->text_store, "", TEXT_STORE_SIZE); - } - view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange); -} - void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { RpcDebugApp* app = context; strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); @@ -42,8 +7,6 @@ void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { text_box_set_text(app->text_box, app->text_store); text_box_set_font(app->text_box, TextBoxFontHex); - rpc_system_app_set_data_exchange_callback( - app->rpc, rpc_debug_app_scene_receive_data_exchange_callback, app); view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox); } @@ -53,6 +16,7 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana if(event.type == SceneManagerEventTypeCustom) { if(event.event == RpcDebugAppCustomEventRpcDataExchange) { + rpc_system_app_confirm(app->rpc, true); notification_message(app->notifications, &sequence_blink_cyan_100); notification_message(app->notifications, &sequence_display_backlight_on); text_box_set_text(app->text_box, app->text_store); @@ -66,5 +30,4 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) { RpcDebugApp* app = context; text_box_reset(app->text_box); - rpc_system_app_set_data_exchange_callback(app->rpc, NULL, NULL); } diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 760918097f..fb6d9dcb52 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -39,21 +39,23 @@ static void ibutton_make_app_folder(iButton* ibutton) { furi_record_close(RECORD_STORAGE); } -static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void ibutton_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); iButton* ibutton = context; - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event( ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose); rpc_system_app_set_callback(ibutton->rpc, NULL, NULL); ibutton->rpc = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit); - } else if(event == RpcAppEventLoadFile) { - view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad); + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(ibutton->file_path, event->data.string); + view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoadFile); } else { - rpc_system_app_confirm(ibutton->rpc, event, false); + rpc_system_app_confirm(ibutton->rpc, false); } } diff --git a/applications/main/ibutton/ibutton_custom_event.h b/applications/main/ibutton/ibutton_custom_event.h index 1ac50ee3cf..b0246d3109 100644 --- a/applications/main/ibutton/ibutton_custom_event.h +++ b/applications/main/ibutton/ibutton_custom_event.h @@ -15,7 +15,7 @@ typedef enum { iButtonCustomEventWorkerWriteNoDetect, iButtonCustomEventWorkerWriteCannotWrite, - iButtonCustomEventRpcLoad, + iButtonCustomEventRpcLoadFile, iButtonCustomEventRpcExit, iButtonCustomEventRpcSessionClose, } iButtonCustomEvent; diff --git a/applications/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index 9205eb4b46..7106fefaa2 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -23,28 +23,23 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; - if(event.event == iButtonCustomEventRpcLoad) { + if(event.event == iButtonCustomEventRpcLoadFile) { bool result = false; - const char* file_path = rpc_system_app_get_data(ibutton->rpc); - if(file_path && (furi_string_empty(ibutton->file_path))) { - furi_string_set(ibutton->file_path, file_path); + if(ibutton_load_key(ibutton)) { + popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - if(ibutton_load_key(ibutton)) { - popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); + ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); + ibutton_worker_emulate_start(ibutton->worker, ibutton->key); - ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); - ibutton_worker_emulate_start(ibutton->worker, ibutton->key); - - result = true; - } + result = true; } - rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result); + rpc_system_app_confirm(ibutton->rpc, result); } else if(event.event == iButtonCustomEventRpcExit) { - rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true); + rpc_system_app_confirm(ibutton->rpc, true); scene_manager_stop(ibutton->scene_manager); view_dispatcher_stop(ibutton->view_dispatcher); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 7abb4e4eb6..645659bbc5 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -44,30 +44,42 @@ static void infrared_tick_event_callback(void* context) { scene_manager_handle_tick_event(infrared->scene_manager); } -static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); InfraredApp* infrared = context; furi_assert(infrared->rpc_ctx); - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcSessionClose); rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL); infrared->rpc_ctx = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcExit); - } else if(event == RpcAppEventLoadFile) { + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(infrared->file_path, event->data.string); view_dispatcher_send_custom_event( - infrared->view_dispatcher, InfraredCustomEventTypeRpcLoad); - } else if(event == RpcAppEventButtonPress) { - view_dispatcher_send_custom_event( - infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPress); - } else if(event == RpcAppEventButtonRelease) { + infrared->view_dispatcher, InfraredCustomEventTypeRpcLoadFile); + } else if(event->type == RpcAppEventTypeButtonPress) { + furi_assert( + event->data.type == RpcAppSystemEventDataTypeString || + event->data.type == RpcAppSystemEventDataTypeInt32); + if(event->data.type == RpcAppSystemEventDataTypeString) { + furi_string_set(infrared->button_name, event->data.string); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressName); + } else { + infrared->app_state.current_button_index = event->data.i32; + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex); + } + } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease); } else { - rpc_system_app_confirm(infrared->rpc_ctx, event, false); + rpc_system_app_confirm(infrared->rpc_ctx, false); } } @@ -117,6 +129,7 @@ static InfraredApp* infrared_alloc() { InfraredApp* infrared = malloc(sizeof(InfraredApp)); infrared->file_path = furi_string_alloc(); + infrared->button_name = furi_string_alloc(); InfraredAppState* app_state = &infrared->app_state; app_state->is_learning_new_remote = false; @@ -247,6 +260,7 @@ static void infrared_free(InfraredApp* infrared) { infrared->gui = NULL; furi_string_free(infrared->file_path); + furi_string_free(infrared->button_name); free(infrared); } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index c9dfe3ab86..c35d3fa410 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -121,6 +121,7 @@ struct InfraredApp { InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ FuriString* file_path; /**< Full path to the currently loaded file. */ + FuriString* button_name; /** Name of the button requested in RPC mode. */ /** Arbitrary text storage for various inputs. */ char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; InfraredAppState app_state; /**< Application state. */ diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index bb7992ae61..373c7270f5 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -128,7 +128,7 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { furi_assert(brute_force->is_started); - const bool success = infrared_signal_search_and_read( + const bool success = infrared_signal_search_by_name_and_read( brute_force->current_signal, brute_force->ff, furi_string_get_cstr(brute_force->current_record_name)); diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 09440ddec1..30bb0f729c 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -15,9 +15,10 @@ enum InfraredCustomEventType { InfraredCustomEventTypeButtonSelected, InfraredCustomEventTypeBackPressed, - InfraredCustomEventTypeRpcLoad, + InfraredCustomEventTypeRpcLoadFile, InfraredCustomEventTypeRpcExit, - InfraredCustomEventTypeRpcButtonPress, + InfraredCustomEventTypeRpcButtonPressName, + InfraredCustomEventTypeRpcButtonPressIndex, InfraredCustomEventTypeRpcButtonRelease, InfraredCustomEventTypeRpcSessionClose, }; diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 5b241fe9f6..cab241c125 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -95,10 +95,9 @@ bool infrared_remote_load_signal( const char* path = furi_string_get_cstr(remote->path); if(!flipper_format_buffered_file_open_existing(ff, path)) break; - const char* name = infrared_remote_get_signal_name(remote, index); - - if(!infrared_signal_search_and_read(signal, ff, name)) { - FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", name, path); + if(!infrared_signal_search_by_index_and_read(signal, ff, index)) { + const char* signal_name = infrared_remote_get_signal_name(remote, index); + FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", signal_name, path); break; } diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index c73e4db98d..2b0d347911 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -266,19 +266,37 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name); } -bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name) { +bool infrared_signal_search_by_name_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const char* name) { bool success = false; FuriString* tmp = furi_string_alloc(); - do { - bool is_name_found = false; - while(!is_name_found && infrared_signal_read_name(ff, tmp)) { //-V560 - is_name_found = furi_string_equal(tmp, name); + while(infrared_signal_read_name(ff, tmp)) { + if(furi_string_equal(tmp, name)) { + success = infrared_signal_read_body(signal, ff); + break; } - if(!is_name_found) break; //-V547 - if(!infrared_signal_read_body(signal, ff)) break; //-V779 - success = true; - } while(false); + } + + furi_string_free(tmp); + return success; +} + +bool infrared_signal_search_by_index_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + size_t index) { + bool success = false; + FuriString* tmp = furi_string_alloc(); + + for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) { + if(i == index) { + success = infrared_signal_read_body(signal, ff); + break; + } + } furi_string_free(tmp); return success; diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index fcbb3c8739..cfa4cfa94e 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -162,7 +162,26 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); * @param[in] name pointer to a zero-terminated string containing the requested signal name. * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). */ -bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name); +bool infrared_signal_search_by_name_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const char* name); + +/** + * @brief Read a signal with a particular index from a FlipperFormat file into an InfraredSignal instance. + * + * This function will look for a signal with the given index and if found, attempt to read it. + * Same considerations apply as to infrared_signal_read(). + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[in] index the requested signal index. + * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + */ +bool infrared_signal_search_by_index_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + size_t index); /** * @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file. diff --git a/applications/main/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c index 5974acbfec..6c1d1ec4e3 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote.c +++ b/applications/main/infrared/scenes/infrared_scene_remote.c @@ -1,7 +1,7 @@ #include "../infrared_app_i.h" typedef enum { - ButtonIndexPlus = -2, + ButtonIndexLearn = -2, ButtonIndexEdit = -1, ButtonIndexNA = 0, } ButtonIndex; @@ -44,7 +44,7 @@ void infrared_scene_remote_on_enter(void* context) { button_menu_add_item( button_menu, "+", - ButtonIndexPlus, + ButtonIndexLearn, infrared_scene_remote_button_menu_callback, ButtonMenuItemTypeControl, context); @@ -95,7 +95,7 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { if(is_transmitter_idle) { scene_manager_set_scene_state( scene_manager, InfraredSceneRemote, (unsigned)button_index); - if(button_index == ButtonIndexPlus) { + if(button_index == ButtonIndexLearn) { infrared->app_state.is_learning_new_remote = false; scene_manager_next_scene(scene_manager, InfraredSceneLearn); consumed = true; diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index fa5a599afa..f3408fba4d 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -1,6 +1,8 @@ #include "../infrared_app_i.h" #include +#define TAG "InfraredApp" + typedef enum { InfraredRpcStateIdle, InfraredRpcStateLoaded, @@ -38,11 +40,9 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { view_dispatcher_stop(infrared->view_dispatcher); } else if(event.event == InfraredCustomEventTypePopupClosed) { view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypeRpcLoad) { + } else if(event.event == InfraredCustomEventTypeRpcLoadFile) { bool result = false; - const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); - if(arg && (state == InfraredRpcStateIdle)) { - furi_string_set(infrared->file_path, arg); + if(state == InfraredRpcStateIdle) { result = infrared_remote_load( infrared->remote, furi_string_get_cstr(infrared->file_path)); if(result) { @@ -56,20 +56,35 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventLoadFile, result); - } else if(event.event == InfraredCustomEventTypeRpcButtonPress) { + rpc_system_app_confirm(infrared->rpc_ctx, result); + } else if( + event.event == InfraredCustomEventTypeRpcButtonPressName || + event.event == InfraredCustomEventTypeRpcButtonPressIndex) { bool result = false; - const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); - if(arg && (state == InfraredRpcStateLoaded)) { - size_t button_index = 0; - if(infrared_remote_get_signal_index(infrared->remote, arg, &button_index)) { - infrared_tx_start_button_index(infrared, button_index); - result = true; + if(state == InfraredRpcStateLoaded) { + if(event.event == InfraredCustomEventTypeRpcButtonPressName) { + const char* button_name = furi_string_get_cstr(infrared->button_name); + size_t index; + const bool index_found = + infrared_remote_get_signal_index(infrared->remote, button_name, &index); + infrared->app_state.current_button_index = + index_found ? (signed)index : InfraredButtonIndexNone; + FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); + } else { + FURI_LOG_D( + TAG, + "Sending signal with index \"%ld\"", + infrared->app_state.current_button_index); + } + if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { + infrared_tx_start_button_index( + infrared, infrared->app_state.current_button_index); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); + result = true; } } - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result); + rpc_system_app_confirm(infrared->rpc_ctx, result); } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) { bool result = false; if(state == InfraredRpcStateSending) { @@ -78,11 +93,11 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); } - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result); + rpc_system_app_confirm(infrared->rpc_ctx, result); } else if(event.event == InfraredCustomEventTypeRpcExit) { scene_manager_stop(infrared->scene_manager); view_dispatcher_stop(infrared->view_dispatcher); - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(infrared->rpc_ctx, true); } else if(event.event == InfraredCustomEventTypeRpcSessionClose) { scene_manager_stop(infrared->scene_manager); view_dispatcher_stop(infrared->view_dispatcher); diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index 2bef08df21..cd0613861f 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -13,21 +13,23 @@ static bool lfrfid_debug_back_event_callback(void* context) { return scene_manager_handle_back_event(app->scene_manager); } -static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { +static void rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); LfRfid* app = (LfRfid*)context; - if(rpc_event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcSessionClose); // Detach RPC rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL); app->rpc_ctx = NULL; - } else if(rpc_event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventExit); - } else if(rpc_event == RpcAppEventLoadFile) { + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(app->file_path, event->data.string); view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcLoadFile); } else { - rpc_system_app_confirm(app->rpc_ctx, rpc_event, false); + rpc_system_app_confirm(app->rpc_ctx, false); } } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c index 156dd97afa..906218d74f 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c @@ -24,17 +24,15 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == LfRfidEventExit) { - rpc_system_app_confirm(app->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(app->rpc_ctx, true); scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); } else if(event.event == LfRfidEventRpcSessionClose) { scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); } else if(event.event == LfRfidEventRpcLoadFile) { - const char* arg = rpc_system_app_get_data(app->rpc_ctx); bool result = false; - if(arg && (app->rpc_state == LfRfidRpcStateIdle)) { - furi_string_set(app->file_path, arg); + if(app->rpc_state == LfRfidRpcStateIdle) { if(lfrfid_load_key_data(app, app->file_path, false)) { lfrfid_worker_start_thread(app->lfworker); lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); @@ -48,7 +46,7 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { result = true; } } - rpc_system_app_confirm(app->rpc_ctx, RpcAppEventLoadFile, result); + rpc_system_app_confirm(app->rpc_ctx, result); } } return consumed; diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 2a3b13e1a4..16fbc47492 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -21,7 +21,7 @@ typedef enum { NfcCustomEventTextInputDone, NfcCustomEventDictAttackDone, - NfcCustomEventRpcLoad, + NfcCustomEventRpcLoadFile, NfcCustomEventRpcExit, NfcCustomEventRpcSessionClose, diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index bf6c0842fe..87fbe9f082 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -694,15 +694,17 @@ static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManag bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventRpcLoad && instance->rpc_state == NfcRpcStateIdle) { - furi_string_set(instance->file_path, rpc_system_app_get_data(instance->rpc_ctx)); - const bool load_success = nfc_load_file(instance, instance->file_path, false); - if(load_success) { - nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); + if(event.event == NfcCustomEventRpcLoadFile) { + bool success = false; + if(instance->rpc_state == NfcRpcStateIdle) { + if(nfc_load_file(instance, instance->file_path, false)) { + nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); + success = true; + } } - rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventLoadFile, load_success); + rpc_system_app_confirm(instance->rpc_ctx, success); } else if(event.event == NfcCustomEventRpcExit) { - rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(instance->rpc_ctx, true); scene_manager_stop(instance->scene_manager); view_dispatcher_stop(instance->view_dispatcher); } else if(event.event == NfcCustomEventRpcSessionClose) { diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 9e0de8891b..141a67e5cb 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -14,22 +14,24 @@ bool nfc_back_event_callback(void* context) { return scene_manager_handle_back_event(nfc->scene_manager); } -static void nfc_app_rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { +static void nfc_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); NfcApp* nfc = (NfcApp*)context; furi_assert(nfc->rpc_ctx); - if(rpc_event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose); rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); nfc->rpc_ctx = NULL; - } else if(rpc_event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcExit); - } else if(rpc_event == RpcAppEventLoadFile) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(nfc->file_path, event->data.string); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoadFile); } else { - rpc_system_app_confirm(nfc->rpc_ctx, rpc_event, false); + rpc_system_app_confirm(nfc->rpc_ctx, false); } } diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index d4bf3e808e..f8bf066d54 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -33,13 +33,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.event == SubGhzCustomEventSceneExit) { scene_manager_stop(subghz->scene_manager); view_dispatcher_stop(subghz->view_dispatcher); - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(subghz->rpc_ctx, true); } else if(event.event == SubGhzCustomEventSceneRpcSessionClose) { scene_manager_stop(subghz->scene_manager); view_dispatcher_stop(subghz->view_dispatcher); } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { bool result = false; - if((state == SubGhzRpcStateLoaded)) { + if(state == SubGhzRpcStateLoaded) { switch( subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { case SubGhzTxRxStartTxStateErrorOnlyRx: @@ -61,7 +61,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { break; } } - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); + rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { bool result = false; if(state == SubGhzRpcStateTx) { @@ -70,15 +70,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { result = true; } state = SubGhzRpcStateIdle; - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); + rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcLoad) { bool result = false; - const char* arg = rpc_system_app_get_data(subghz->rpc_ctx); - if(arg && (state == SubGhzRpcStateIdle)) { - if(subghz_key_load(subghz, arg, false)) { + if(state == SubGhzRpcStateIdle) { + if(subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false)) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateLoaded); - furi_string_set(subghz->file_path, arg); result = true; FuriString* file_name; file_name = furi_string_alloc(); @@ -97,7 +95,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { rpc_system_app_set_error_text(subghz->rpc_ctx, "Cannot parse file"); } } - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventLoadFile, result); + rpc_system_app_confirm(subghz->rpc_ctx, result); } } return consumed; diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index e8148798ef..69a72e95d6 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -20,29 +20,31 @@ void subghz_tick_event_callback(void* context) { scene_manager_handle_tick_event(subghz->scene_manager); } -static void subghz_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); SubGhz* subghz = context; furi_assert(subghz->rpc_ctx); - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcSessionClose); rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL); subghz->rpc_ctx = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); - } else if(event == RpcAppEventLoadFile) { + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(subghz->file_path, event->data.string); view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneRpcLoad); - } else if(event == RpcAppEventButtonPress) { + } else if(event->type == RpcAppEventTypeButtonPress) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPress); - } else if(event == RpcAppEventButtonRelease) { + } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease); } else { - rpc_system_app_confirm(subghz->rpc_ctx, event, false); + rpc_system_app_confirm(subghz->rpc_ctx, false); } } diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 826f222537..5880e7d9f9 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -477,12 +477,15 @@ void rpc_send_and_release(RpcSession* session, PB_Main* message) { } void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status) { + furi_assert(session); + PB_Main message = { .command_id = command_id, .command_status = status, .has_next = false, .which_content = PB_Main_empty_tag, }; + rpc_send_and_release(session, &message); pb_release(&PB_Main_msg, &message); } diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index e86eaa493d..9af652dae1 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -10,49 +10,85 @@ struct RpcAppSystem { RpcSession* session; - RpcAppSystemCallback app_callback; - void* app_context; + RpcAppSystemCallback callback; + void* callback_context; - RpcAppSystemDataExchangeCallback data_exchange_callback; - void* data_exchange_context; + uint32_t error_code; + char* error_text; - PB_Main* state_msg; - PB_Main* error_msg; - - uint32_t last_id; - char* last_data; + uint32_t last_command_id; + RpcAppSystemEventType last_event_type; }; #define RPC_SYSTEM_APP_TEMP_ARGS_SIZE 16 +static void rpc_system_app_send_state_response( + RpcAppSystem* rpc_app, + PB_App_AppState state, + const char* name) { + PB_Main* response = malloc(sizeof(PB_Main)); + + response->which_content = PB_Main_app_state_response_tag; + response->content.app_state_response.state = state; + + FURI_LOG_D(TAG, "%s", name); + rpc_send(rpc_app->session, response); + + free(response); +} + +static void rpc_system_app_send_error_response( + RpcAppSystem* rpc_app, + uint32_t command_id, + PB_CommandStatus status, + const char* name) { + // Not describing all possible errors as only APP_NOT_RUNNING is used + const char* status_str = status == PB_CommandStatus_ERROR_APP_NOT_RUNNING ? "APP_NOT_RUNNING" : + "UNKNOWN"; + FURI_LOG_E(TAG, "%s: %s, id %lu, status: %d", name, status_str, command_id, status); + rpc_send_and_release_empty(rpc_app->session, command_id, status); +} + +static void rpc_system_app_set_last_command( + RpcAppSystem* rpc_app, + uint32_t command_id, + const RpcAppSystemEvent* event) { + furi_assert(rpc_app->last_command_id == 0); + furi_assert(rpc_app->last_event_type == RpcAppEventTypeInvalid); + + rpc_app->last_command_id = command_id; + rpc_app->last_event_type = event->type; +} + static void rpc_system_app_start_process(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_start_request_tag); - RpcAppSystem* rpc_app = context; - RpcSession* session = rpc_app->session; - rpc_system_app_error_reset(rpc_app); - furi_assert(session); - char args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE]; - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); + furi_assert(rpc_app->last_command_id == 0); + furi_assert(rpc_app->last_event_type == RpcAppEventTypeInvalid); FURI_LOG_D(TAG, "StartProcess: id %lu", request->command_id); - PB_CommandStatus result; - Loader* loader = furi_record_open(RECORD_LOADER); const char* app_name = request->content.app_start_request.name; + + PB_CommandStatus result; + if(app_name) { + rpc_system_app_error_reset(rpc_app); + + char app_args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE]; const char* app_args = request->content.app_start_request.args; + if(app_args && strcmp(app_args, "RPC") == 0) { // If app is being started in RPC mode - pass RPC context via args string - snprintf(args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); - app_args = args_temp; + snprintf(app_args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); + app_args = app_args_temp; } - LoaderStatus status = loader_start(loader, app_name, app_args, NULL); + + const LoaderStatus status = loader_start(loader, app_name, app_args, NULL); if(status == LoaderStatusErrorAppStarted) { result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; } else if(status == LoaderStatusErrorInternal) { @@ -71,266 +107,271 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_record_close(RECORD_LOADER); FURI_LOG_D(TAG, "StartProcess: response id %lu, result %d", request->command_id, result); - rpc_send_and_release_empty(session, request->command_id, result); + rpc_send_and_release_empty(rpc_app->session, request->command_id, result); } static void rpc_system_app_lock_status_process(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_lock_status_request_tag); + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); + rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); FURI_LOG_D(TAG, "LockStatus"); - Loader* loader = furi_record_open(RECORD_LOADER); - - PB_Main response = { - .has_next = false, - .command_status = PB_CommandStatus_OK, - .command_id = request->command_id, - .which_content = PB_Main_app_lock_status_response_tag, - }; + PB_Main* response = malloc(sizeof(PB_Main)); - response.content.app_lock_status_response.locked = loader_is_locked(loader); + response->command_id = request->command_id; + response->which_content = PB_Main_app_lock_status_response_tag; + Loader* loader = furi_record_open(RECORD_LOADER); + response->content.app_lock_status_response.locked = loader_is_locked(loader); furi_record_close(RECORD_LOADER); FURI_LOG_D(TAG, "LockStatus: response"); - rpc_send_and_release(session, &response); - pb_release(&PB_Main_msg, &response); + rpc_send_and_release(rpc_app->session, response); + + free(response); } static void rpc_system_app_exit_request(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_exit_request_tag); - RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - PB_CommandStatus status; + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "ExitRequest: id %lu", request->command_id); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->app_callback(RpcAppEventAppExit, rpc_app->app_context); + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeAppExit, + .data = + { + .type = RpcAppSystemEventDataTypeNone, + {0}, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "ExitRequest: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ExitRequest"); } } static void rpc_system_app_load_file(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_load_file_request_tag); + RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus status; - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "LoadFile: id %lu", request->command_id); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->last_data = strdup(request->content.app_load_file_request.path); - rpc_app->app_callback(RpcAppEventLoadFile, rpc_app->app_context); + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeLoadFile, + .data = + { + .type = RpcAppSystemEventDataTypeString, + .string = request->content.app_load_file_request.path, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "LoadFile: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "LoadFile"); } } static void rpc_system_app_button_press(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_button_press_request_tag); + RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus status; - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "ButtonPress"); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->last_data = strdup(request->content.app_button_press_request.args); - rpc_app->app_callback(RpcAppEventButtonPress, rpc_app->app_context); + + RpcAppSystemEvent event; + event.type = RpcAppEventTypeButtonPress; + + if(strlen(request->content.app_button_press_request.args) != 0) { + event.data.type = RpcAppSystemEventDataTypeString; + event.data.string = request->content.app_button_press_request.args; + } else { + event.data.type = RpcAppSystemEventDataTypeInt32; + event.data.i32 = request->content.app_button_press_request.index; + } + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "ButtonPress: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ButtonPress"); } } static void rpc_system_app_button_release(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_button_release_request_tag); - furi_assert(context); RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus status; - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "ButtonRelease"); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->app_callback(RpcAppEventButtonRelease, rpc_app->app_context); + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeButtonRelease, + .data = + { + .type = RpcAppSystemEventDataTypeNone, + {0}, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "ButtonRelease: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ButtonRelease"); } } static void rpc_system_app_get_error_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_get_error_request_tag); - furi_assert(context); RpcAppSystem* rpc_app = context; - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); + + PB_Main* response = malloc(sizeof(PB_Main)); - rpc_app->error_msg->command_id = request->command_id; + response->command_id = request->command_id; + response->which_content = PB_Main_app_get_error_response_tag; + response->content.app_get_error_response.code = rpc_app->error_code; + response->content.app_get_error_response.text = rpc_app->error_text; FURI_LOG_D(TAG, "GetError"); - rpc_send(session, rpc_app->error_msg); + rpc_send(rpc_app->session, response); + + free(response); } static void rpc_system_app_data_exchange_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_data_exchange_request_tag); - furi_assert(context); RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus command_status; - pb_bytes_array_t* data = request->content.app_data_exchange_request.data; + if(rpc_app->callback) { + FURI_LOG_D(TAG, "DataExchange"); - if(rpc_app->data_exchange_callback) { - uint8_t* data_bytes = NULL; - size_t data_size = 0; - if(data) { - data_bytes = data->bytes; - data_size = data->size; - } - rpc_app->data_exchange_callback(data_bytes, data_size, rpc_app->data_exchange_context); - command_status = PB_CommandStatus_OK; + const pb_bytes_array_t* data = request->content.app_data_exchange_request.data; + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeDataExchange, + .data = + { + .type = RpcAppSystemEventDataTypeBytes, + .bytes = + { + .ptr = data ? data->bytes : NULL, + .size = data ? data->size : 0, + }, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); } else { - command_status = PB_CommandStatus_ERROR_APP_CMD_ERROR; + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "DataExchange"); } - - FURI_LOG_D(TAG, "DataExchange"); - rpc_send_and_release_empty(session, request->command_id, command_status); } void rpc_system_app_send_started(RpcAppSystem* rpc_app) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - - rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_STARTED; - - FURI_LOG_D(TAG, "SendStarted"); - rpc_send(session, rpc_app->state_msg); + rpc_system_app_send_state_response(rpc_app, PB_App_AppState_APP_STARTED, "SendStarted"); } void rpc_system_app_send_exited(RpcAppSystem* rpc_app) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - - rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_CLOSED; - - FURI_LOG_D(TAG, "SendExit"); - rpc_send(session, rpc_app->state_msg); -} - -const char* rpc_system_app_get_data(RpcAppSystem* rpc_app) { - furi_assert(rpc_app); - furi_assert(rpc_app->last_data); - return rpc_app->last_data; + rpc_system_app_send_state_response(rpc_app, PB_App_AppState_APP_CLOSED, "SendExit"); } -void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result) { +void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - furi_assert(rpc_app->last_id); - - PB_CommandStatus status = result ? PB_CommandStatus_OK : PB_CommandStatus_ERROR_APP_CMD_ERROR; - - uint32_t last_id = 0; - switch(event) { - case RpcAppEventAppExit: - case RpcAppEventLoadFile: - case RpcAppEventButtonPress: - case RpcAppEventButtonRelease: - last_id = rpc_app->last_id; - rpc_app->last_id = 0; - if(rpc_app->last_data) { - free(rpc_app->last_data); - rpc_app->last_data = NULL; - } - FURI_LOG_D(TAG, "AppConfirm: event %d last_id %lu status %d", event, last_id, status); - rpc_send_and_release_empty(session, last_id, status); - break; - default: - furi_crash("RPC App state programming Error"); - break; - } + furi_assert(rpc_app->last_command_id != 0); + /* Ensure that only commands of these types can be confirmed */ + furi_assert( + rpc_app->last_event_type == RpcAppEventTypeAppExit || + rpc_app->last_event_type == RpcAppEventTypeLoadFile || + rpc_app->last_event_type == RpcAppEventTypeButtonPress || + rpc_app->last_event_type == RpcAppEventTypeButtonRelease || + rpc_app->last_event_type == RpcAppEventTypeDataExchange); + + const uint32_t last_command_id = rpc_app->last_command_id; + const RpcAppSystemEventType last_event_type = rpc_app->last_event_type; + + rpc_app->last_command_id = 0; + rpc_app->last_event_type = RpcAppEventTypeInvalid; + + const PB_CommandStatus status = result ? PB_CommandStatus_OK : + PB_CommandStatus_ERROR_APP_CMD_ERROR; + FURI_LOG_D( + TAG, + "AppConfirm: event %d last_id %lu status %d", + last_event_type, + last_command_id, + status); + + rpc_send_and_release_empty(rpc_app->session, last_command_id, status); } void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx) { furi_assert(rpc_app); - rpc_app->app_callback = callback; - rpc_app->app_context = ctx; + rpc_app->callback = callback; + rpc_app->callback_context = ctx; } void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code) { furi_assert(rpc_app); - PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response; - content->code = error_code; + rpc_app->error_code = error_code; } void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text) { furi_assert(rpc_app); - PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response; - if(content->text) { - free(content->text); + if(rpc_app->error_text) { + free(rpc_app->error_text); } - content->text = error_text ? strdup(error_text) : NULL; + rpc_app->error_text = error_text ? strdup(error_text) : NULL; } void rpc_system_app_error_reset(RpcAppSystem* rpc_app) { @@ -340,29 +381,13 @@ void rpc_system_app_error_reset(RpcAppSystem* rpc_app) { rpc_system_app_set_error_text(rpc_app, NULL); } -void rpc_system_app_set_data_exchange_callback( - RpcAppSystem* rpc_app, - RpcAppSystemDataExchangeCallback callback, - void* ctx) { - furi_assert(rpc_app); - - rpc_app->data_exchange_callback = callback; - rpc_app->data_exchange_context = ctx; -} - void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - PB_Main message = { - .command_id = 0, - .command_status = PB_CommandStatus_OK, - .has_next = false, - .which_content = PB_Main_app_data_exchange_request_tag, - }; + PB_Main* request = malloc(sizeof(PB_Main)); - PB_App_DataExchangeRequest* content = &message.content.app_data_exchange_request; + request->which_content = PB_Main_app_data_exchange_request_tag; + PB_App_DataExchangeRequest* content = &request->content.app_data_exchange_request; if(data && data_size) { content->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data_size)); @@ -372,7 +397,9 @@ void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, si content->data = NULL; } - rpc_send_and_release(session, &message); + rpc_send_and_release(rpc_app->session, request); + + free(request); } void* rpc_system_app_alloc(RpcSession* session) { @@ -381,18 +408,6 @@ void* rpc_system_app_alloc(RpcSession* session) { RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem)); rpc_app->session = session; - // App exit message - rpc_app->state_msg = malloc(sizeof(PB_Main)); - rpc_app->state_msg->which_content = PB_Main_app_state_response_tag; - rpc_app->state_msg->command_status = PB_CommandStatus_OK; - - // App error message - rpc_app->error_msg = malloc(sizeof(PB_Main)); - rpc_app->error_msg->which_content = PB_Main_app_get_error_response_tag; - rpc_app->error_msg->command_status = PB_CommandStatus_OK; - rpc_app->error_msg->content.app_get_error_response.code = 0; - rpc_app->error_msg->content.app_get_error_response.text = NULL; - RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, @@ -429,24 +444,24 @@ void* rpc_system_app_alloc(RpcSession* session) { void rpc_system_app_free(void* context) { RpcAppSystem* rpc_app = context; furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - - if(rpc_app->app_callback) { - rpc_app->app_callback(RpcAppEventSessionClose, rpc_app->app_context); + furi_assert(rpc_app->session); + + if(rpc_app->callback) { + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeSessionClose, + .data = + { + .type = RpcAppSystemEventDataTypeNone, + {0}, + }, + }; + + rpc_app->callback(&event, rpc_app->callback_context); } - while(rpc_app->app_callback) { + while(rpc_app->callback) { furi_delay_tick(1); } - furi_assert(!rpc_app->data_exchange_callback); - - if(rpc_app->last_data) free(rpc_app->last_data); - - pb_release(&PB_Main_msg, rpc_app->error_msg); - - free(rpc_app->error_msg); - free(rpc_app->state_msg); free(rpc_app); } diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index d5c6fee962..4ee5a24d37 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -1,45 +1,213 @@ +/** + * @file rpc_app.h + * @brief Application RPC subsystem interface. + * + * The application RPC subsystem provides facilities for interacting with applications, + * such as starting/stopping, passing parameters, sending commands and exchanging arbitrary data. + * + * All commands are handled asynchronously via a user-settable callback. + * + * For a complete description of message types handled in this subsystem, + * see https://github.com/flipperdevices/flipperzero-protobuf/blob/dev/application.proto + */ #pragma once + #include "rpc.h" #ifdef __cplusplus extern "C" { #endif +/** + * @brief Enumeration of possible event data types. + */ typedef enum { - RpcAppEventSessionClose, - RpcAppEventAppExit, - RpcAppEventLoadFile, - RpcAppEventButtonPress, - RpcAppEventButtonRelease, + RpcAppSystemEventDataTypeNone, /**< No data is provided by the event. */ + RpcAppSystemEventDataTypeString, /**< Event data contains a zero-terminated string. */ + RpcAppSystemEventDataTypeInt32, /**< Event data contains a signed 32-bit integer. */ + RpcAppSystemEventDataTypeBytes, /**< Event data contains zero or more bytes. */ +} RpcAppSystemEventDataType; + +/** + * @brief Event data structure, containing the type and associated data. + * + * All below fields except for type are valid only if the respective type is set. + */ +typedef struct { + RpcAppSystemEventDataType + type; /**< Type of the data. The meaning of other fields depends on this one. */ + union { + const char* string; /**< Pointer to a zero-terminated character string. */ + int32_t i32; /**< Signed 32-bit integer value. */ + struct { + const uint8_t* ptr; /**< Pointer to the byte array data. */ + size_t size; /**< Size of the byte array, in bytes. */ + } bytes; /**< Byte array of arbitrary length. */ + }; +} RpcAppSystemEventData; + +/** + * @brief Enumeration of possible event types. + */ +typedef enum { + /** + * @brief Denotes an invalid state. + * + * An event of this type shall never be passed into the callback. + */ + RpcAppEventTypeInvalid, + /** + * @brief The client side has closed the session. + * + * After receiving this event, the RPC context is no more valid. + */ + RpcAppEventTypeSessionClose, + /** + * @brief The client has requested the application to exit. + * + * The application must exit after receiving this command. + */ + RpcAppEventTypeAppExit, + /** + * @brief The client has requested the application to load a file. + * + * This command's meaning is application-specific, i.e. the application might or + * might not require additional commands after loading a file to do anything useful. + */ + RpcAppEventTypeLoadFile, + /** + * @brief The client has informed the application that a button has been pressed. + * + * This command's meaning is application-specific, e.g. to select a part of the + * previously loaded file or to invoke a particular function within the application. + */ + RpcAppEventTypeButtonPress, + /** + * @brief The client has informed the application that a button has been released. + * + * This command's meaning is application-specific, e.g. to cease + * all activities to be conducted while a button is being pressed. + */ + RpcAppEventTypeButtonRelease, + /** + * @brief The client has sent a byte array of arbitrary size. + * + * This command's purpose is bi-directional exchange of arbitrary raw data. + * Useful for implementing higher-level protocols while using the RPC as a transport layer. + */ + RpcAppEventTypeDataExchange, +} RpcAppSystemEventType; + +/** + * @brief RPC application subsystem event structure. + */ +typedef struct { + RpcAppSystemEventType type; /**< Type of the event. */ + RpcAppSystemEventData data; /**< Data associated with the event. */ } RpcAppSystemEvent; -typedef void (*RpcAppSystemCallback)(RpcAppSystemEvent event, void* context); -typedef void ( - *RpcAppSystemDataExchangeCallback)(const uint8_t* data, size_t data_size, void* context); +/** + * @brief Callback function type. + * + * A function of this type must be passed to rpc_system_app_set_callback() by the user code. + * + * @warning The event pointer is valid ONLY inside the callback function. + * + * @param[in] event pointer to the event object. Valid only inside the callback function. + * @param[in,out] context pointer to the user-defined context object. + */ +typedef void (*RpcAppSystemCallback)(const RpcAppSystemEvent* event, void* context); +/** + * @brief RPC application subsystem opaque type declaration. + */ typedef struct RpcAppSystem RpcAppSystem; -void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx); +/** + * @brief Set the callback function for use by an RpcAppSystem instance. + * + * @param[in,out] rpc_app pointer to the instance to be configured. + * @param[in] callback pointer to the function to be called upon message reception. + * @param[in,out] context pointer to the user-defined context object. Will be passed to the callback. + */ +void rpc_system_app_set_callback( + RpcAppSystem* rpc_app, + RpcAppSystemCallback callback, + void* context); +/** + * @brief Send a notification that an RpcAppSystem instance has been started and is ready. + * + * Call this function once right after acquiring an RPC context and setting the callback. + * + * @param[in,out] rpc_app pointer to the instance to be used. + */ void rpc_system_app_send_started(RpcAppSystem* rpc_app); +/** + * @brief Send a notification that the application using an RpcAppSystem instance is about to exit. + * + * Call this function when the application is about to exit (usually in the *_free() function). + * + * @param[in,out] rpc_app pointer to the instance to be used. + */ void rpc_system_app_send_exited(RpcAppSystem* rpc_app); -const char* rpc_system_app_get_data(RpcAppSystem* rpc_app); - -void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result); +/** + * @brief Send a confirmation that the application using an RpcAppSystem instance has handled the event. + * + * An explicit confirmation is required for the following event types: + * - RpcAppEventTypeAppExit + * - RpcAppEventTypeLoadFile + * - RpcAppEventTypeButtonPress + * - RpcAppEventTypeButtonRelease + * - RpcAppEventTypeDataExchange + * + * Not confirming these events will result in a client-side timeout. + * + * @param[in,out] rpc_app pointer to the instance to be used. + * @param[in] result whether the command was successfully handled or not (true for success). + */ +void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result); +/** + * @brief Set the error code stored in an RpcAppSystem instance. + * + * The error code can be retrieved by the client at any time by using the GetError request. + * The error code value has no meaning within the subsystem, i.e. it is only passed through to the client. + * + * @param[in,out] rpc_app pointer to the instance to be modified. + * @param[in] error_code arbitrary error code to be set. + */ void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code); +/** + * @brief Set the error text stored in an RpcAppSystem instance. + * + * The error text can be retrieved by the client at any time by using the GetError request. + * The text has no meaning within the subsystem, i.e. it is only passed through to the client. + * + * @param[in,out] rpc_app pointer to the instance to be modified. + * @param[in] error_text Pointer to a zero-terminated string containing the error text. + */ void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text); +/** + * @brief Reset the error code and text stored in an RpcAppSystem instance. + * + * Resets the error code to 0 and error text to "" (empty string). + * + * @param[in,out] rpc_app pointer to the instance to be reset. + */ void rpc_system_app_error_reset(RpcAppSystem* rpc_app); -void rpc_system_app_set_data_exchange_callback( - RpcAppSystem* rpc_app, - RpcAppSystemDataExchangeCallback callback, - void* ctx); - +/** + * @brief Send a byte array of arbitrary data to the client using an RpcAppSystem instance. + * + * @param[in,out] rpc_app pointer to the instance to be used. + * @param[in] data pointer to the data buffer to be sent. + * @param[in] data_size size of the data buffer, in bytes. + */ void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size); #ifdef __cplusplus diff --git a/assets/protobuf b/assets/protobuf index 327163d586..23ad19a756 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 327163d5867c7aa3051334c93ced718d15bfe4da +Subproject commit 23ad19a756649ed9f6677b598e5361c5cce6847b diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index 5c32ae1a98..492a66b665 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -80,22 +80,35 @@ def __invoke_git(args, source_dir): def _proto_ver_generator(target, source, env): target_file = target[0] src_dir = source[0].dir.abspath - try: - __invoke_git( - ["fetch", "--tags"], - source_dir=src_dir, - ) - except (subprocess.CalledProcessError, EnvironmentError): - # Not great, not terrible - print(fg.boldred("Git: fetch failed")) - - try: - git_describe = __invoke_git( - ["describe", "--tags", "--abbrev=0"], - source_dir=src_dir, - ) - except (subprocess.CalledProcessError, EnvironmentError): - raise StopError("Git: describe failed") + + def fetch(unshallow=False): + git_args = ["fetch", "--tags"] + if unshallow: + git_args.append("--unshallow") + + try: + __invoke_git(git_args, source_dir=src_dir) + except (subprocess.CalledProcessError, EnvironmentError): + # Not great, not terrible + print(fg.boldred("Git: fetch failed")) + + def describe(): + try: + return __invoke_git( + ["describe", "--tags", "--abbrev=0"], + source_dir=src_dir, + ) + except (subprocess.CalledProcessError, EnvironmentError): + return None + + fetch() + git_describe = describe() + if not git_describe: + fetch(unshallow=True) + git_describe = describe() + + if not git_describe: + raise StopError("Failed to process git tags for protobuf versioning") git_major, git_minor = git_describe.split(".") version_file_data = ( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 5f8e836c00..0eadd799d4 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1950,14 +1950,12 @@ Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCal Function,+,rpc_session_set_context,void,"RpcSession*, void*" Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" -Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, _Bool" Function,+,rpc_system_app_error_reset,void,RpcAppSystem* Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" -Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* Function,+,rpc_system_app_send_exited,void,RpcAppSystem* Function,+,rpc_system_app_send_started,void,RpcAppSystem* Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*" -Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*" Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 57adf96aec..9834531953 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2493,14 +2493,12 @@ Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCal Function,+,rpc_session_set_context,void,"RpcSession*, void*" Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" -Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, _Bool" Function,+,rpc_system_app_error_reset,void,RpcAppSystem* Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" -Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* Function,+,rpc_system_app_send_exited,void,RpcAppSystem* Function,+,rpc_system_app_send_started,void,RpcAppSystem* Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*" -Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*" Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* From a56a52b4bbe9fe9dd5d1a265936c960f973b8dbf Mon Sep 17 00:00:00 2001 From: ushastoe Date: Fri, 10 Nov 2023 21:00:13 +0300 Subject: [PATCH 033/111] Fix naming in iButton --- applications/main/ibutton/scenes/ibutton_scene_emulate.c | 4 ++-- applications/main/ibutton/scenes/ibutton_scene_info.c | 4 ++-- applications/main/ibutton/scenes/ibutton_scene_write.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index 713b8331c3..04487f700a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -28,10 +28,10 @@ void ibutton_scene_emulate_on_enter(void* context) { ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); 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); diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index cf44d6a865..dcdee9557a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -11,12 +11,12 @@ void ibutton_scene_info_on_enter(void* context) { furi_string_printf( tmp, - "\e#%s [%s]\e#", + "\e#%s\n[%s]\e#", ibutton->key_name, ibutton_protocols_get_name(ibutton->protocols, protocol_id)); widget_add_text_box_element( - widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); + widget, 0, 2, 128, 40, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); furi_string_reset(tmp); ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 541aa1c52b..f87932ddff 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -30,7 +30,7 @@ void ibutton_scene_write_on_enter(void* context) { ibutton_protocols_get_name(ibutton->protocols, protocol_id)); 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); @@ -46,7 +46,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); From 725811d7b37f6480238b2480e1b8a3a2721e2954 Mon Sep 17 00:00:00 2001 From: ushastoe Date: Fri, 10 Nov 2023 23:29:11 +0300 Subject: [PATCH 034/111] fix [] --- applications/main/ibutton/scenes/ibutton_scene_emulate.c | 6 +++--- applications/main/ibutton/scenes/ibutton_scene_write.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index 04487f700a..3cfddb8d04 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -23,9 +23,9 @@ 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, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index f87932ddff..0e489fead7 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -25,9 +25,9 @@ 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, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); From 3e9ecd2f4f7cf08fe1a9da2c86f2af27eda31e93 Mon Sep 17 00:00:00 2001 From: ushastoe Date: Mon, 13 Nov 2023 11:26:36 +0300 Subject: [PATCH 035/111] applications_user/Flipper-Zero-Privet-Mir-main/ --- applications_user/.gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 applications_user/.gitignore diff --git a/applications_user/.gitignore b/applications_user/.gitignore deleted file mode 100644 index 72e8ffc0db..0000000000 --- a/applications_user/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* From bee1a6c18a4d3f81ae13cf332fa6299d272abe99 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 13 Nov 2023 12:27:21 +0400 Subject: [PATCH 036/111] remove rtos --- furi/core/common_defines.h | 2 -- furi/furi.h | 3 --- targets/f7/src/main.c | 1 - 3 files changed, 6 deletions(-) diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 5bd218d357..2b30c3b06d 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -2,8 +2,6 @@ #include "core_defines.h" #include -#include -#include #ifdef __cplusplus extern "C" { diff --git a/furi/furi.h b/furi/furi.h index b1299c9a95..422509055f 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -21,9 +21,6 @@ #include -// FreeRTOS timer, REMOVE AFTER REFACTORING -#include - // Workaround for math.h leaking through HAL in older versions #include diff --git a/targets/f7/src/main.c b/targets/f7/src/main.c index 2c353f52b2..ca705fe5e4 100644 --- a/targets/f7/src/main.c +++ b/targets/f7/src/main.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #define TAG "Main" From 42101c6594ba9446449951643f9c6a2c0e83bf9e Mon Sep 17 00:00:00 2001 From: ushastoe Date: Mon, 13 Nov 2023 11:27:27 +0300 Subject: [PATCH 037/111] Revert " applications_user/Flipper-Zero-Privet-Mir-main/" This reverts commit 3e9ecd2f4f7cf08fe1a9da2c86f2af27eda31e93. --- applications_user/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 applications_user/.gitignore diff --git a/applications_user/.gitignore b/applications_user/.gitignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/applications_user/.gitignore @@ -0,0 +1 @@ +* From ef09dcf8f84d2afd9b1910dd7fdb1fb179b93e0d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 13 Nov 2023 12:42:05 +0400 Subject: [PATCH 038/111] subghz things and remove rtos finally --- applications/main/archive/archive.c | 5 +- applications/main/subghz/views/receiver.c | 6 +- lib/subghz/protocols/alutech_at_4n.h | 18 --- lib/subghz/protocols/came_atomo.h | 16 --- lib/subghz/protocols/faac_slh.h | 22 --- lib/subghz/protocols/keeloq.h | 42 ------ lib/subghz/protocols/nice_flor_s.h | 20 --- lib/subghz/protocols/public_api.h | 156 +++++++++++++++++++++- lib/subghz/protocols/somfy_keytis.h | 18 --- lib/subghz/protocols/somfy_telis.h | 18 --- lib/subghz/protocols/star_line.h | 20 --- targets/f7/api_symbols.csv | 113 ++-------------- 12 files changed, 168 insertions(+), 286 deletions(-) diff --git a/applications/main/archive/archive.c b/applications/main/archive/archive.c index 4a5a8b39e2..066fcab059 100644 --- a/applications/main/archive/archive.c +++ b/applications/main/archive/archive.c @@ -95,18 +95,17 @@ void archive_free(ArchiveApp* archive) { } void archive_show_loading_popup(ArchiveApp* context, bool show) { - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); ViewStack* view_stack = context->view_stack; Loading* loading = context->loading; if(show) { // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); view_stack_add_view(view_stack, loading_get_view(loading)); } else { view_stack_remove_view(view_stack, loading_get_view(loading)); // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); } } diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 010e470622..f1d0324fcc 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -111,7 +111,7 @@ void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool loc SubGhzViewReceiverModel * model, { model->bar_show = SubGhzViewReceiverBarShowLock; }, true); - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + furi_timer_start(subghz_receiver->timer, 1000); } else { with_view_model( subghz_receiver->view, @@ -453,7 +453,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowToUnlockPress; }, true); if(subghz_receiver->lock_count == 0) { - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + furi_timer_start(subghz_receiver->timer, 1000); } if(event->key == InputKeyBack && event->type == InputTypeShort) { subghz_receiver->lock_count++; @@ -467,7 +467,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowUnlock; }, true); //subghz_receiver->lock = false; - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(650)); + furi_timer_start(subghz_receiver->timer, 650); } return true; diff --git a/lib/subghz/protocols/alutech_at_4n.h b/lib/subghz/protocols/alutech_at_4n.h index b7b5b9edef..a57152556a 100644 --- a/lib/subghz/protocols/alutech_at_4n.h +++ b/lib/subghz/protocols/alutech_at_4n.h @@ -23,24 +23,6 @@ void* subghz_protocol_encoder_alutech_at_4n_alloc(SubGhzEnvironment* environment */ void subghz_protocol_encoder_alutech_at_4n_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderAlutech_at_4n instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 24 bit - * @param btn Button number, 8 bit - * @param cnt Counter value, 16 bit - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_alutech_at_4n_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderAlutech_at_4n instance diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index 8183877b02..c5e45a68d8 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -14,22 +14,6 @@ void atomo_decrypt(uint8_t* buff); void atomo_encrypt(uint8_t* buff); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 24 bit - * @param cnt Counter value, 16 bit - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_came_atomo_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint16_t cnt, - SubGhzRadioPreset* preset); - /** * Allocate SubGhzProtocolEncoderCameAtomo. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/protocols/faac_slh.h b/lib/subghz/protocols/faac_slh.h index 8d03d7907d..16b6f031e6 100644 --- a/lib/subghz/protocols/faac_slh.h +++ b/lib/subghz/protocols/faac_slh.h @@ -24,28 +24,6 @@ void* subghz_protocol_encoder_faac_slh_alloc(SubGhzEnvironment* environment); */ void subghz_protocol_encoder_faac_slh_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 28 bit - * @param btn Button number, 4 bit - * @param cnt Counter value, 16 bit - * @param seed Seed value, 32 bit - * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_faac_slh_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint32_t cnt, - uint32_t seed, - const char* manufacture_name, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index d8d27293e9..4abd14413b 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -25,48 +25,6 @@ void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment); */ void subghz_protocol_encoder_keeloq_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 28 bit - * @param btn Button number, 4 bit - * @param cnt Counter value, 16 bit - * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_keeloq_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - const char* manufacture_name, - SubGhzRadioPreset* preset); - -/** - * Key generation for BFT. - * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 28 bit - * @param btn Button number, 4 bit - * @param cnt Counter value, 16 bit - * @param seed Seed value, 32 bit - * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_keeloq_bft_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - uint32_t seed, - const char* manufacture_name, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h index 5f8b306441..4d635d3fbd 100644 --- a/lib/subghz/protocols/nice_flor_s.h +++ b/lib/subghz/protocols/nice_flor_s.h @@ -48,26 +48,6 @@ LevelDuration subghz_protocol_encoder_nice_flor_s_yield(void* context); uint64_t subghz_protocol_nice_flor_s_encrypt(uint64_t data, const char* file_name); -/** - * New remote generation. - * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number - * @param btn Button number, 4 bit - * @param cnt Counter value, 16 bit - * @param preset Modulation, SubGhzRadioPreset - * @param nice_one Nice One if true, Nice Flor S if false - * @return true On success - */ -bool subghz_protocol_nice_flor_s_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - SubGhzRadioPreset* preset, - bool nice_one); - /** * Allocate SubGhzProtocolDecoderNiceFlorS. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index 174f175cdf..d7ae21c2a2 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -31,7 +31,7 @@ bool subghz_protocol_secplus_v2_create_data( * @param flipper_format Pointer to a FlipperFormat instance * @param serial Serial number, 28 bit * @param btn Button number, 4 bit - * @param cnt Container value, 16 bit + * @param cnt Counter value, 16 bit * @param manufacture_name Name of manufacturer's key * @param preset Modulation, SubGhzRadioPreset * @return true On success @@ -45,6 +45,160 @@ bool subghz_protocol_keeloq_create_data( const char* manufacture_name, SubGhzRadioPreset* preset); +/** + * Key generation for BFT. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param seed Seed value, 32 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_keeloq_bft_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param seed Seed value, 32 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_faac_slh_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderAlutech_at_4n instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_alutech_at_4n_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_came_atomo_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset); + +/** + * New remote generation. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @param nice_one Nice One if true, Nice Flor S if false + * @return true On success + */ +bool subghz_protocol_nice_flor_s_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset, + bool nice_one); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_star_line_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_somfy_telis_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_somfy_keytis_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; void subghz_protocol_decoder_bin_raw_data_input_rssi( diff --git a/lib/subghz/protocols/somfy_keytis.h b/lib/subghz/protocols/somfy_keytis.h index 0e339c8891..d7e8df109b 100644 --- a/lib/subghz/protocols/somfy_keytis.h +++ b/lib/subghz/protocols/somfy_keytis.h @@ -24,24 +24,6 @@ void* subghz_protocol_encoder_somfy_keytis_alloc(SubGhzEnvironment* environment) */ void subghz_protocol_encoder_somfy_keytis_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 24 bit - * @param btn Button number, 8 bit - * @param cnt Counter value, 16 bit - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_somfy_keytis_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h index cb5cf8e96f..a072079dab 100644 --- a/lib/subghz/protocols/somfy_telis.h +++ b/lib/subghz/protocols/somfy_telis.h @@ -24,24 +24,6 @@ void* subghz_protocol_encoder_somfy_telis_alloc(SubGhzEnvironment* environment); */ void subghz_protocol_encoder_somfy_telis_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 24 bit - * @param btn Button number, 8 bit - * @param cnt Counter value, 16 bit - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_somfy_telis_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h index 851819ec70..e7b15ca0b7 100644 --- a/lib/subghz/protocols/star_line.h +++ b/lib/subghz/protocols/star_line.h @@ -24,26 +24,6 @@ void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment); */ void subghz_protocol_encoder_star_line_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderStarLine instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 24 bit - * @param btn Button number, 8 bit - * @param cnt Counter value, 16 bit - * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_star_line_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - const char* manufacture_name, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderStarLine instance diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index fd95831416..63f7397343 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -853,8 +853,6 @@ Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, Function,-,drem,double,"double, double" Function,-,dremf,float,"float, float" -Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, -Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" @@ -1685,7 +1683,6 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" -Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -2423,8 +2420,6 @@ Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" Function,+,pb_release,void,"const pb_msgdesc_t*, void*" Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" -Function,-,pcTaskGetName,char*,TaskHandle_t -Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -2500,12 +2495,6 @@ Function,-,putchar_unlocked,int,int Function,-,putenv,int,char* Function,-,puts,int,const char* Function,-,putw,int,"int, FILE*" -Function,-,pvPortCalloc,void*,"size_t, size_t" -Function,-,pvPortMalloc,void*,size_t -Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" -Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, -Function,-,pvTimerGetTimerID,void*,const TimerHandle_t -Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" Function,-,quick_exit,void,int @@ -2885,6 +2874,7 @@ Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, u Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" Function,-,subghz_keystore_reset_kl,void,SubGhzKeystore* Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" +Function,+,subghz_protocol_alutech_at_4n_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_add_to_128_bit,void,"SubGhzBlockDecoder*, uint8_t, uint64_t*" @@ -2906,6 +2896,7 @@ Function,+,subghz_protocol_blocks_parity_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" +Function,+,subghz_protocol_came_atomo_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_decoder_base_deserialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*" Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" @@ -2923,7 +2914,10 @@ Function,+,subghz_protocol_encoder_raw_deserialize,SubGhzProtocolStatus,"void*, Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* +Function,+,subghz_protocol_faac_slh_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, uint32_t, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* @@ -2935,6 +2929,9 @@ Function,+,subghz_protocol_registry_get_by_index,const SubGhzProtocol*,"const Su Function,+,subghz_protocol_registry_get_by_name,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, const char*" Function,+,subghz_protocol_secplus_v1_check_fixed,_Bool,uint32_t Function,+,subghz_protocol_secplus_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, SubGhzRadioPreset*" +Function,+,subghz_protocol_somfy_keytis_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" +Function,+,subghz_protocol_somfy_telis_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" +Function,+,subghz_protocol_star_line_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" Function,+,subghz_receiver_free,void,SubGhzReceiver* @@ -3074,67 +3071,10 @@ Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" -Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" -Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" -Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, -Function,-,ulTaskGetIdleRunTimePercent,uint32_t, Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,-,uxListRemove,UBaseType_t,ListItem_t* -Function,-,uxTaskGetNumberOfTasks,UBaseType_t, -Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t -Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t -Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" -Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t -Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t -Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t -Function,-,uxTaskResetEventItemValue,TickType_t, -Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t -Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t -Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vListInitialise,void,List_t* -Function,-,vListInitialiseItem,void,ListItem_t* -Function,-,vListInsert,void,"List_t*, ListItem_t*" -Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" -Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* -Function,-,vPortEndScheduler,void, -Function,+,vPortEnterCritical,void, -Function,+,vPortExitCritical,void, -Function,-,vPortFree,void,void* -Function,-,vPortGetHeapStats,void,HeapStats_t* -Function,-,vPortInitialiseBlocks,void, -Function,-,vPortSuppressTicksAndSleep,void,TickType_t -Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" -Function,-,vTaskDelay,void,const TickType_t -Function,-,vTaskDelete,void,TaskHandle_t -Function,-,vTaskEndScheduler,void, -Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" -Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" -Function,-,vTaskGetRunTimeStats,void,char* -Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* -Function,-,vTaskList,void,char* -Function,-,vTaskMissedYield,void, -Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" -Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" -Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" -Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" -Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" -Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" -Function,-,vTaskResume,void,TaskHandle_t -Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" -Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" -Function,-,vTaskSetTimeOutState,void,TimeOut_t* -Function,-,vTaskStartScheduler,void, -Function,-,vTaskStepTick,void,TickType_t -Function,-,vTaskSuspend,void,TaskHandle_t -Function,-,vTaskSuspendAll,void, -Function,-,vTaskSwitchContext,void, -Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" -Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" -Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* @@ -3252,43 +3192,6 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* -Function,-,xPortGetFreeHeapSize,size_t, -Function,-,xPortGetMinimumEverFreeHeapSize,size_t, -Function,-,xPortStartScheduler,BaseType_t, -Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t -Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" -Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t -Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" -Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" -Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" -Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" -Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" -Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" -Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" -Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" -Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, -Function,+,xTaskGetHandle,TaskHandle_t,const char* -Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, -Function,+,xTaskGetSchedulerState,BaseType_t, -Function,+,xTaskGetTickCount,TickType_t, -Function,-,xTaskGetTickCountFromISR,TickType_t, -Function,-,xTaskIncrementTick,BaseType_t, -Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t -Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t -Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* -Function,-,xTaskResumeAll,BaseType_t, -Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t -Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" -Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" -Function,-,xTimerCreateTimerTask,BaseType_t, -Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" -Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t -Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t -Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t -Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, -Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t -Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" -Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" Function,-,y0,double,double Function,-,y0f,float,float Function,-,y1,double,double From 18377185c7c3fb5ed0ae8ce168a8e5f76e330fc5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:02:01 +0400 Subject: [PATCH 039/111] upd dronich --- .drone.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.drone.yml b/.drone.yml index 341f49e4e1..d58733504b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -23,7 +23,7 @@ steps: - export WORKFLOW_BRANCH_OR_TAG=release-cfw - export FORCE_NO_DIRTY=yes - export FBT_GIT_SUBMODULE_SHALLOW=1 - - rm -rf assets/resources/apps/ + - rm -rf applications/main/clock_app/resources/apps/ - rm -rf build/ - rm -rf dist/ - rm -rf .sconsign.dblite @@ -46,8 +46,8 @@ steps: - 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 - - cp -R base_pack_build/artifacts-base/* assets/resources/apps/ - - cp -R base_pack_build/apps_data/* assets/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 - rm -rf all-the-apps-base.tgz - rm -f build/f7-firmware-C/toolbox/version.* @@ -66,7 +66,7 @@ steps: commands: - wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz - tar zxvf all-the-apps-extra.tgz - - cp -R extra_pack_build/artifacts-extra/* assets/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 - export WORKFLOW_BRANCH_OR_TAG=release-cfw @@ -109,7 +109,7 @@ steps: - git checkout -- . - rm -f assets/dolphin/external/manifest.txt - cp .ci_files/anims_ofw.txt assets/dolphin/external/manifest.txt - - rm -rf assets/resources/apps/ + - rm -rf applications/main/clock_app/resources/apps/ - export DIST_SUFFIX=${DRONE_TAG}n - export WORKFLOW_BRANCH_OR_TAG=no-custom-anims - export FORCE_NO_DIRTY=yes @@ -118,8 +118,8 @@ steps: - ./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 - - cp -R base_pack_build/artifacts-base/* assets/resources/apps/ - - cp -R base_pack_build/apps_data/* assets/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 - rm -rf all-the-apps-base.tgz - rm -f build/f7-firmware-C/toolbox/version.* @@ -397,7 +397,7 @@ steps: - export WORKFLOW_BRANCH_OR_TAG=dev-cfw - export FORCE_NO_DIRTY=yes - export FBT_GIT_SUBMODULE_SHALLOW=1 - - rm -rf assets/resources/apps/ + - rm -rf applications/main/clock_app/resources/apps/ - rm -rf build/ - rm -rf dist/ - rm -rf .sconsign.dblite @@ -421,8 +421,8 @@ steps: - 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 - - cp -R base_pack_build/artifacts-base/* assets/resources/apps/ - - cp -R base_pack_build/apps_data/* assets/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 - rm -rf all-the-apps-base.tgz - rm -f build/f7-firmware-C/toolbox/version.* @@ -441,7 +441,7 @@ steps: commands: - wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz - tar zxvf all-the-apps-extra.tgz - - cp -R extra_pack_build/artifacts-extra/* assets/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 - export WORKFLOW_BRANCH_OR_TAG=dev-cfw From dc246ddb0958b17707b4dad9e8e1609f3517d662 Mon Sep 17 00:00:00 2001 From: Tobias Jost Date: Wed, 15 Nov 2023 08:39:29 +0100 Subject: [PATCH 040/111] Fix limited_credit_value having wrong value in mf_desfire_file_settings_parse (#3204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 30313ae2bc..129dcdf5e1 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -201,7 +201,7 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer data->value.lo_limit = layout.value.lo_limit; data->value.hi_limit = layout.value.hi_limit; - data->value.limited_credit_value = layout.value.hi_limit; + data->value.limited_credit_value = layout.value.limited_credit_value; data->value.limited_credit_enabled = layout.value.limited_credit_enabled; } else if( From d0b9a3a4ae7abbb1a0e97616226d2b77d86f08e7 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:02:35 +0300 Subject: [PATCH 041/111] [NFC] MF Ultralight no pwd polling adjustment (#3207) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 19 +++++++++--- .../mf_ultralight/mf_ultralight_listener.c | 30 +++++++++---------- .../mf_ultralight/mf_ultralight_poller.c | 4 +++ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 2d647f8ef5..ce5a9cb9cc 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -203,10 +203,21 @@ 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(); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c index 70c6f6de2a..5bef2a354c 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -122,7 +122,7 @@ static MfUltralightCommand uint16_t pages_total = instance->data->pages_total; MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_READ: %d", start_page); + FURI_LOG_T(TAG, "CMD_READ: %d", start_page); do { bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); @@ -154,7 +154,7 @@ static MfUltralightCommand static MfUltralightCommand mf_ultralight_listener_fast_read_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_FAST_READ"); + FURI_LOG_T(TAG, "CMD_FAST_READ"); do { if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastRead)) @@ -206,7 +206,7 @@ static MfUltralightCommand uint16_t pages_total = instance->data->pages_total; MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_WRITE"); + FURI_LOG_T(TAG, "CMD_WRITE"); do { bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); @@ -235,7 +235,7 @@ static MfUltralightCommand static MfUltralightCommand mf_ultralight_listener_fast_write_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_FAST_WRITE"); + FURI_LOG_T(TAG, "CMD_FAST_WRITE"); do { if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastWrite)) @@ -261,7 +261,7 @@ static MfUltralightCommand UNUSED(buffer); MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_GET_VERSION"); + FURI_LOG_T(TAG, "CMD_GET_VERSION"); if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadVersion)) { bit_buffer_copy_bytes( @@ -280,7 +280,7 @@ static MfUltralightCommand mf_ultralight_listener_read_signature_handler( UNUSED(buffer); MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_READ_SIG"); + FURI_LOG_T(TAG, "CMD_READ_SIG"); if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadSignature)) { bit_buffer_copy_bytes( @@ -297,7 +297,7 @@ static MfUltralightCommand mf_ultralight_listener_read_counter_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_READ_CNT"); + FURI_LOG_T(TAG, "CMD_READ_CNT"); do { uint8_t counter_num = bit_buffer_get_byte(buffer, 1); @@ -338,7 +338,7 @@ static MfUltralightCommand mf_ultralight_listener_increase_counter_handler( BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_INCR_CNT"); + FURI_LOG_T(TAG, "CMD_INCR_CNT"); do { if(!mf_ultralight_support_feature( @@ -374,7 +374,7 @@ static MfUltralightCommand mf_ultralight_listener_check_tearing_handler( BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_CHECK_TEARING"); + FURI_LOG_T(TAG, "CMD_CHECK_TEARING"); do { uint8_t tearing_flag_num = bit_buffer_get_byte(buffer, 1); @@ -410,7 +410,7 @@ static MfUltralightCommand MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; UNUSED(instance); UNUSED(buffer); - FURI_LOG_D(TAG, "CMD_VCSL"); + FURI_LOG_T(TAG, "CMD_VCSL"); do { if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportVcsl)) break; @@ -432,7 +432,7 @@ static MfUltralightCommand mf_ultralight_listener_auth_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_AUTH"); + FURI_LOG_T(TAG, "CMD_AUTH"); do { if(!mf_ultralight_support_feature( @@ -474,7 +474,7 @@ static MfUltralightCommand static MfUltralightCommand mf_ultralight_comp_write_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_CM_WR_2"); + FURI_LOG_T(TAG, "CMD_CM_WR_2"); do { if(bit_buffer_get_size_bytes(buffer) != 16) break; @@ -492,7 +492,7 @@ static MfUltralightCommand mf_ultralight_comp_write_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_CM_WR_1"); + FURI_LOG_T(TAG, "CMD_CM_WR_1"); do { if(!mf_ultralight_support_feature( @@ -532,7 +532,7 @@ static MfUltralightCommand MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; UNUSED(instance); UNUSED(buffer); - FURI_LOG_D(TAG, "CMD_SEC_SEL_2"); + FURI_LOG_T(TAG, "CMD_SEC_SEL_2"); do { if(bit_buffer_get_size_bytes(buffer) != 4) break; @@ -550,7 +550,7 @@ static MfUltralightCommand mf_ultralight_sector_select_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; UNUSED(buffer); - FURI_LOG_D(TAG, "CMD_SEC_SEL_1"); + FURI_LOG_T(TAG, "CMD_SEC_SEL_1"); do { if(!mf_ultralight_support_feature( diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index bf0ced38d0..4ad7bc147d 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -487,6 +487,7 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll sizeof(MfUltralightAuthPassword), config->password.data); config->pack = instance->auth_context.pack; + instance->auth_context.auth_success = true; } } @@ -496,6 +497,9 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll // original card config->auth0 = instance->pages_read; config->access.prot = true; + } else if(!instance->auth_context.auth_success) { + instance->pages_read -= 2; + instance->data->pages_read -= 2; } } while(false); From c00776ca2279a86a62c253cfd2c3a910dabd7795 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 15 Nov 2023 12:32:45 +0400 Subject: [PATCH 042/111] [FL-3666] NFC API improvements (#3214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 44 +-- .../nfc/plugins/supported_cards/plantain.c | 8 +- .../main/nfc/plugins/supported_cards/troika.c | 8 +- .../nfc/plugins/supported_cards/two_cities.c | 8 +- lib/drivers/SConscript | 2 + lib/nfc/SConscript | 6 +- lib/nfc/nfc_poller.c | 72 +++++ lib/nfc/nfc_poller.h | 37 +++ lib/nfc/protocols/felica/felica_poller.c | 4 +- lib/nfc/protocols/felica/felica_poller.h | 40 ++- lib/nfc/protocols/felica/felica_poller_i.c | 6 +- lib/nfc/protocols/felica/felica_poller_i.h | 4 +- .../iso14443_3a/iso14443_3a_poller.c | 4 +- .../iso14443_3a/iso14443_3a_poller.h | 115 ++++++- .../iso14443_3a/iso14443_3a_poller_i.c | 5 +- .../iso14443_3a/iso14443_3a_poller_i.h | 20 -- ...r_sync_api.c => iso14443_3a_poller_sync.c} | 4 +- ...r_sync_api.h => iso14443_3a_poller_sync.h} | 2 +- .../iso14443_3b/iso14443_3b_poller.c | 4 +- .../iso14443_3b/iso14443_3b_poller.h | 71 ++++- .../iso14443_3b/iso14443_3b_poller_i.c | 5 +- .../iso14443_3b/iso14443_3b_poller_i.h | 10 - lib/nfc/protocols/iso14443_4a/iso14443_4a.h | 16 +- lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h | 16 - .../iso14443_4a/iso14443_4a_poller.c | 3 +- .../iso14443_4a/iso14443_4a_poller.h | 70 ++++- .../iso14443_4a/iso14443_4a_poller_i.c | 2 +- .../iso14443_4a/iso14443_4a_poller_i.h | 10 - .../iso14443_4b/iso14443_4b_poller.h | 56 +++- .../iso14443_4b/iso14443_4b_poller_i.h | 7 - .../protocols/iso15693_3/iso15693_3_poller.c | 4 +- .../protocols/iso15693_3/iso15693_3_poller.h | 132 +++++++- .../iso15693_3/iso15693_3_poller_i.c | 28 +- .../iso15693_3/iso15693_3_poller_i.h | 30 -- .../protocols/mf_classic/mf_classic_poller.c | 68 ++--- .../protocols/mf_classic/mf_classic_poller.h | 289 ++++++++++++++---- .../mf_classic/mf_classic_poller_i.c | 16 +- .../mf_classic/mf_classic_poller_i.h | 31 -- ...er_sync_api.c => mf_classic_poller_sync.c} | 69 ++--- ...er_sync_api.h => mf_classic_poller_sync.h} | 16 +- .../protocols/mf_desfire/mf_desfire_poller.c | 15 +- .../protocols/mf_desfire/mf_desfire_poller.h | 259 +++++++++++++++- .../mf_desfire/mf_desfire_poller_i.c | 62 ++-- .../mf_desfire/mf_desfire_poller_i.h | 72 ----- .../mf_ultralight/mf_ultralight_poller.c | 25 +- .../mf_ultralight/mf_ultralight_poller.h | 178 ++++++++++- .../mf_ultralight/mf_ultralight_poller_i.c | 28 +- .../mf_ultralight/mf_ultralight_poller_i.h | 40 --- ...sync_api.c => mf_ultralight_poller_sync.c} | 51 ++-- .../mf_ultralight/mf_ultralight_poller_sync.h | 34 +++ .../mf_ultralight_poller_sync_api.h | 30 -- lib/nfc/protocols/nfc_protocol.h | 10 +- lib/nfc/protocols/slix/slix_poller.c | 7 +- lib/nfc/protocols/slix/slix_poller.h | 68 ++++- lib/nfc/protocols/slix/slix_poller_i.c | 4 +- lib/nfc/protocols/slix/slix_poller_i.h | 10 - lib/nfc/protocols/st25tb/st25tb_poller.c | 4 +- lib/nfc/protocols/st25tb/st25tb_poller.h | 17 ++ lib/nfc/protocols/st25tb/st25tb_poller_i.c | 19 +- lib/nfc/protocols/st25tb/st25tb_poller_i.h | 17 -- targets/f18/api_symbols.csv | 27 +- targets/f7/api_symbols.csv | 116 +++++-- 62 files changed, 1712 insertions(+), 723 deletions(-) rename lib/nfc/protocols/iso14443_3a/{iso14443_3a_poller_sync_api.c => iso14443_3a_poller_sync.c} (93%) rename lib/nfc/protocols/iso14443_3a/{iso14443_3a_poller_sync_api.h => iso14443_3a_poller_sync.h} (58%) rename lib/nfc/protocols/mf_classic/{mf_classic_poller_sync_api.c => mf_classic_poller_sync.c} (88%) rename lib/nfc/protocols/mf_classic/{mf_classic_poller_sync_api.h => mf_classic_poller_sync.h} (64%) rename lib/nfc/protocols/mf_ultralight/{mf_ultralight_poller_sync_api.c => mf_ultralight_poller_sync.c} (83%) create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h delete mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index ce5a9cb9cc..0dcd09046d 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -7,10 +7,10 @@ #include #include #include -#include +#include #include -#include -#include +#include +#include #include #include @@ -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( @@ -221,8 +221,8 @@ static void mf_ultralight_reader_test(const char* path) { 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); @@ -270,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); @@ -308,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)), @@ -321,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 = @@ -355,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); @@ -383,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); @@ -413,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"); diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index cb8c0093d0..a21e1cd415 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -4,7 +4,7 @@ #include #include -#include +#include #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; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index d42b977c6c..7cf1e4dd8c 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -4,7 +4,7 @@ #include #include -#include +#include #define TAG "Troika" @@ -91,7 +91,7 @@ static bool troika_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; @@ -118,7 +118,7 @@ static bool troika_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; @@ -136,7 +136,7 @@ static bool troika_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; diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index fb964103ee..1748d372d2 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -4,7 +4,7 @@ #include #include -#include +#include #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; diff --git a/lib/drivers/SConscript b/lib/drivers/SConscript index 103472ccb3..cf93d4bce9 100644 --- a/lib/drivers/SConscript +++ b/lib/drivers/SConscript @@ -6,6 +6,8 @@ env.Append( ], SDK_HEADERS=[ File("cc1101_regs.h"), + File("st25r3916_reg.h"), + File("st25r3916.h"), ], ) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 6c55cf5d2b..605a8639dd 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -36,9 +36,9 @@ env.Append( File("protocols/mf_ultralight/mf_ultralight_listener.h"), File("protocols/mf_classic/mf_classic_listener.h"), # Sync API - File("protocols/iso14443_3a/iso14443_3a_poller_sync_api.h"), - File("protocols/mf_ultralight/mf_ultralight_poller_sync_api.h"), - File("protocols/mf_classic/mf_classic_poller_sync_api.h"), + File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), + File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"), + File("protocols/mf_classic/mf_classic_poller_sync.h"), # Misc File("helpers/nfc_util.h"), File("helpers/iso14443_crc.h"), diff --git a/lib/nfc/nfc_poller.c b/lib/nfc/nfc_poller.c index ffe0318a0d..48be37d925 100644 --- a/lib/nfc/nfc_poller.c +++ b/lib/nfc/nfc_poller.c @@ -28,6 +28,9 @@ struct NfcPoller { NfcPollerList list; NfcPollerSessionState session_state; bool protocol_detected; + + NfcGenericCallbackEx callback; + void* context; }; static void nfc_poller_list_alloc(NfcPoller* instance) { @@ -127,6 +130,75 @@ void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* co nfc_start(instance->nfc, nfc_poller_start_callback, instance); } +static NfcCommand nfc_poller_start_ex_tail_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol != NfcProtocolInvalid); + + NfcPoller* instance = context; + NfcCommand command = NfcCommandContinue; + + NfcGenericEventEx poller_event = { + .poller = instance->list.tail->poller, + .parent_event_data = event.event_data, + }; + + command = instance->callback(poller_event, instance->context); + + return command; +} + +static NfcCommand nfc_poller_start_ex_head_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcCommand command = NfcCommandContinue; + NfcPoller* instance = context; + + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->protocol); + + if(parent_protocol == NfcProtocolInvalid) { + NfcGenericEventEx poller_event = { + .poller = instance->list.tail->poller, + .parent_event_data = &event, + }; + + command = instance->callback(poller_event, instance->context); + } else { + NfcGenericEvent poller_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + NfcPollerListElement* head_poller = instance->list.head; + command = head_poller->poller_api->run(poller_event, head_poller->poller); + } + + if(instance->session_state == NfcPollerSessionStateStopRequest) { + command = NfcCommandStop; + } + + return command; +} + +void nfc_poller_start_ex(NfcPoller* instance, NfcGenericCallbackEx callback, void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(instance->session_state == NfcPollerSessionStateIdle); + + instance->callback = callback; + instance->context = context; + + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->protocol); + if(parent_protocol != NfcProtocolInvalid) { + NfcPollerListElement* iter = instance->list.head; + while(iter->protocol != parent_protocol) iter = iter->child; + + iter->poller_api->set_callback(iter->poller, nfc_poller_start_ex_tail_callback, instance); + } + + instance->session_state = NfcPollerSessionStateActive; + nfc_start(instance->nfc, nfc_poller_start_ex_head_callback, instance); +} + void nfc_poller_stop(NfcPoller* instance) { furi_assert(instance); furi_assert(instance->nfc); diff --git a/lib/nfc/nfc_poller.h b/lib/nfc/nfc_poller.h index 8ae01a8e43..18fbfb388a 100644 --- a/lib/nfc/nfc_poller.h +++ b/lib/nfc/nfc_poller.h @@ -26,6 +26,31 @@ extern "C" { */ typedef struct NfcPoller NfcPoller; +/** + * @brief Extended generic Nfc event type. + * + * An extended generic Nfc event contains protocol poller and it's parent protocol event data. + * If protocol has no parent, then events are produced by Nfc instance. + * + * The parent_event_data field is protocol-specific and should be cast to the appropriate type before use. + */ +typedef struct { + NfcGenericInstance* poller; /**< Pointer to the protocol poller. */ + NfcGenericEventData* + parent_event_data /**< Pointer to the protocol's parent poller event data. */; +} NfcGenericEventEx; + +/** + * @brief Extended generic Nfc event callback type. + * + * A function of this type must be passed as the callback parameter upon extended start of a poller. + * + * @param [in] event Nfc extended generic event, passed by value, complete with protocol type and data. + * @param [in,out] context pointer to the user-specific context (set when starting a poller/listener instance). + * @returns the command which the event producer must execute. + */ +typedef NfcCommand (*NfcGenericCallbackEx)(NfcGenericEventEx event, void* context); + /** * @brief Allocate an NfcPoller instance. * @@ -57,6 +82,18 @@ void nfc_poller_free(NfcPoller* instance); */ void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context); +/** + * @brief Start an NfcPoller instance in extended mode. + * + * When nfc poller is started in extended mode, callback will be called with parent protocol events + * and protocol instance. This mode enables to make custom poller state machines. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_poller_start_ex(NfcPoller* instance, NfcGenericCallbackEx callback, void* context); + /** * @brief Stop an NfcPoller instance. * diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 3fc2affedc..23b1604e19 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -66,7 +66,7 @@ static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != FelicaPollerStateActivated) { - FelicaError error = felica_poller_async_activate(instance, instance->data); + FelicaError error = felica_poller_activate(instance, instance->data); if(error == FelicaErrorNone) { instance->felica_event.type = FelicaPollerEventTypeReady; instance->felica_event_data.error = error; @@ -100,7 +100,7 @@ static bool felica_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == FelicaPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - FelicaError error = felica_poller_async_activate(instance, instance->data); + FelicaError error = felica_poller_activate(instance, instance->data); protocol_detected = (error == FelicaErrorNone); } diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index 7d0c9525e7..45fd9a9a1f 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -9,22 +9,50 @@ extern "C" { #endif +/** + * @brief FelicaPoller opaque type definition. + */ typedef struct FelicaPoller FelicaPoller; +/** + * @brief Enumeration of possible Felica poller event types. + */ typedef enum { - FelicaPollerEventTypeError, - FelicaPollerEventTypeReady, + FelicaPollerEventTypeError, /**< The card was activated by the poller. */ + FelicaPollerEventTypeReady, /**< An error occured during activation procedure. */ } FelicaPollerEventType; -typedef struct { - FelicaError error; +/** + * @brief Felica poller event data. + */ +typedef union { + FelicaError error; /**< Error code indicating card activation fail reason. */ } FelicaPollerEventData; +/** + * @brief FelicaPoller poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - FelicaPollerEventType type; - FelicaPollerEventData* data; + FelicaPollerEventType type; /**< Type of emmitted event. */ + FelicaPollerEventData* data; /**< Pointer to event specific data. */ } FelicaPollerEvent; +/** + * @brief Perform collision resolution procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the collision resolution procedure as defined in FeliCa standars. The data + * field will be filled with Felica data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Felica data structure to be filled. + * @return FelicaErrorNone on success, an error code on failure. + */ +FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index d8015fdfad..bfbf150ef9 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -49,7 +49,7 @@ static FelicaError felica_poller_frame_exchange( return ret; } -FelicaError felica_poller_async_polling( +FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp) { @@ -93,7 +93,7 @@ FelicaError felica_poller_async_polling( return error; } -FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data) { +FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { furi_assert(instance); felica_reset(data); @@ -112,7 +112,7 @@ FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* dat }; FelicaPollerPollingResponse polling_resp = {}; - ret = felica_poller_async_polling(instance, &polling_cmd, &polling_resp); + ret = felica_poller_polling(instance, &polling_cmd, &polling_resp); if(ret != FelicaErrorNone) { FURI_LOG_T(TAG, "Activation failed error: %d", ret); diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index e12f014726..3bd4d91f9f 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -48,13 +48,11 @@ typedef struct { const FelicaData* felica_poller_get_data(FelicaPoller* instance); -FelicaError felica_poller_async_polling( +FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); -FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c index 880092c333..158250acdf 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c @@ -72,7 +72,7 @@ static NfcCommand iso14443_3a_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != Iso14443_3aPollerStateActivated) { Iso14443_3aData data = {}; - Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, &data); + Iso14443_3aError error = iso14443_3a_poller_activate(instance, &data); if(error == Iso14443_3aErrorNone) { instance->state = Iso14443_3aPollerStateActivated; instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady; @@ -111,7 +111,7 @@ static bool iso14443_3a_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == Iso14443_3aPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, NULL); + Iso14443_3aError error = iso14443_3a_poller_activate(instance, NULL); protocol_detected = (error == Iso14443_3aErrorNone); } diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h index 385cd52256..42e4b4bf52 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h @@ -9,34 +9,137 @@ extern "C" { #endif +/** + * @brief Iso14443_3aPoller opaque type definition. + */ typedef struct Iso14443_3aPoller Iso14443_3aPoller; +/** + * @brief Enumeration of possible Iso14443_3a poller event types. + */ typedef enum { - Iso14443_3aPollerEventTypeError, - Iso14443_3aPollerEventTypeReady, + Iso14443_3aPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_3aPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_3aPollerEventType; -typedef struct { - Iso14443_3aError error; +/** + * @brief Iso14443_3a poller event data. + */ +typedef union { + Iso14443_3aError error; /**< Error code indicating card activation fail reason. */ } Iso14443_3aPollerEventData; +/** + * @brief Iso14443_3a poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_3aPollerEventType type; - Iso14443_3aPollerEventData* data; + Iso14443_3aPollerEventType type; /**< Type of emmitted event. */ + Iso14443_3aPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_3aPollerEvent; +/** + * @brief Transmit and receive Iso14443_3a frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ Iso14443_3aError iso14443_3a_poller_txrx( Iso14443_3aPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt); +/** + * @brief Transmit and receive Iso14443_3a standard frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ Iso14443_3aError iso14443_3a_poller_send_standard_frame( Iso14443_3aPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt); +/** + * @brief Transmit and receive Iso14443_3a frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * Custom parity bits must be set in the tx_buffer. The rx_buffer will contain + * the received data with the parity bits. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Checks presence of Iso14443_3a complient card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_3aErrorNone if card is present, an error code otherwise. + */ +Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance); + +/** + * @brief Perform collision resolution procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the collision resolution procedure as defined in Iso14443-3a. The iso14443_3a_data + * field will be filled with Iso14443-3a data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] iso14443_3a_data pointer to the Iso14443_3a data structure to be filled. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError + iso14443_3a_poller_activate(Iso14443_3aPoller* instance, Iso14443_3aData* iso14443_3a_data); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_3aPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c index 3434dc8e35..2be88bc515 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c @@ -94,9 +94,8 @@ Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance) { return Iso14443_3aErrorNone; } -Iso14443_3aError iso14443_3a_poller_async_activate( - Iso14443_3aPoller* instance, - Iso14443_3aData* iso14443_3a_data) { +Iso14443_3aError + iso14443_3a_poller_activate(Iso14443_3aPoller* instance, Iso14443_3aData* iso14443_3a_data) { furi_assert(instance); furi_assert(instance->nfc); furi_assert(instance->tx_buffer); diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h index 063ef15566..764f1a6b59 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h @@ -39,15 +39,9 @@ typedef enum { Iso14443_3aPollerStateActivated, } Iso14443_3aPollerState; -typedef enum { - Iso14443_3aPollerConfigStateIdle, - Iso14443_3aPollerConfigStateDone, -} Iso14443_3aPollerConfigState; - struct Iso14443_3aPoller { Nfc* nfc; Iso14443_3aPollerState state; - Iso14443_3aPollerConfigState config_state; Iso14443_3aPollerColRes col_res; Iso14443_3aData* data; BitBuffer* tx_buffer; @@ -62,20 +56,6 @@ struct Iso14443_3aPoller { const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance); -Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance); - -Iso14443_3aError iso14443_3a_poller_async_activate( - Iso14443_3aPoller* instance, - Iso14443_3aData* iso14443_3a_data); - -Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance); - -Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( - Iso14443_3aPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.c similarity index 93% rename from lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c rename to lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.c index 2bab1a881a..ea7a6ae156 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.c @@ -1,4 +1,4 @@ -#include "iso14443_3a_poller_sync_api.h" +#include "iso14443_3a_poller_sync.h" #include "iso14443_3a_poller_i.h" #include @@ -34,7 +34,7 @@ NfcCommand iso14443_3a_poller_read_callback(NfcGenericEvent event, void* context return NfcCommandStop; } -Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data) { +Iso14443_3aError iso14443_3a_poller_sync_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data) { furi_assert(nfc); furi_assert(iso14443_3a_data); diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h similarity index 58% rename from lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h rename to lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h index ed17ff4324..72f084d1b0 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h @@ -7,7 +7,7 @@ extern "C" { #endif -Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data); +Iso14443_3aError iso14443_3a_poller_sync_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c index 9507f28c41..f0c9b67ad1 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c @@ -70,7 +70,7 @@ static NfcCommand iso14443_3b_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != Iso14443_3bPollerStateActivated) { - Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + Iso14443_3bError error = iso14443_3b_poller_activate(instance, instance->data); if(error == Iso14443_3bErrorNone) { instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady; instance->iso14443_3b_event_data.error = error; @@ -104,7 +104,7 @@ static bool iso14443_3b_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == Iso14443_3bPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + Iso14443_3bError error = iso14443_3b_poller_activate(instance, instance->data); protocol_detected = (error == Iso14443_3bErrorNone); } diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h index d25d9dbe9f..940903c1dc 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h @@ -9,22 +9,81 @@ extern "C" { #endif +/** + * @brief Iso14443_3bPoller opaque type definition. + */ typedef struct Iso14443_3bPoller Iso14443_3bPoller; +/** + * @brief Enumeration of possible Iso14443_3b poller event types. + */ typedef enum { - Iso14443_3bPollerEventTypeError, - Iso14443_3bPollerEventTypeReady, + Iso14443_3bPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_3bPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_3bPollerEventType; -typedef struct { - Iso14443_3bError error; +/** + * @brief Iso14443_3b poller event data. + */ +typedef union { + Iso14443_3bError error; /**< Error code indicating card activation fail reason. */ } Iso14443_3bPollerEventData; +/** + * @brief Iso14443_3b poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_3bPollerEventType type; - Iso14443_3bPollerEventData* data; + Iso14443_3bPollerEventType type; /**< Type of emmitted event. */ + Iso14443_3bPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_3bPollerEvent; +/** + * @brief Transmit and receive Iso14443_3b frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3bErrorNone on success, an error code on failure. + */ +Iso14443_3bError iso14443_3b_poller_send_frame( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Perform collision resolution procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the collision resolution procedure as defined in Iso14443-3b. The data + * field will be filled with Iso14443-3b data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Iso14443_3b data structure to be filled. + * @return Iso14443_3bErrorNone on success, an error code on failure. + */ +Iso14443_3bError iso14443_3b_poller_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_3bPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_3bErrorNone on success, an error code on failure. + */ +Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c index 95668ccf22..1ee5237c64 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c @@ -21,7 +21,7 @@ static Iso14443_3bError iso14443_3b_poller_prepare_trx(Iso14443_3bPoller* instan furi_assert(instance); if(instance->state == Iso14443_3bPollerStateIdle) { - return iso14443_3b_poller_async_activate(instance, NULL); + return iso14443_3b_poller_activate(instance, NULL); } return Iso14443_3bErrorNone; @@ -63,8 +63,7 @@ static Iso14443_3bError iso14443_3b_poller_frame_exchange( return ret; } -Iso14443_3bError - iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data) { +Iso14443_3bError iso14443_3b_poller_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data) { furi_assert(instance); furi_assert(instance->nfc); diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h index 5821d6373f..2503c2e41e 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h @@ -34,16 +34,6 @@ struct Iso14443_3bPoller { const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance); -Iso14443_3bError - iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data); - -Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance); - -Iso14443_3bError iso14443_3b_poller_send_frame( - Iso14443_3bPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h index 9580c14040..df212152de 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h @@ -2,6 +2,8 @@ #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -28,7 +30,19 @@ typedef enum { Iso14443_4aFrameOptionCid, } Iso14443_4aFrameOption; -typedef struct Iso14443_4aData Iso14443_4aData; +typedef struct { + uint8_t tl; + uint8_t t0; + uint8_t ta_1; + uint8_t tb_1; + uint8_t tc_1; + SimpleArray* t1_tk; +} Iso14443_4aAtsData; + +typedef struct { + Iso14443_3aData* iso14443_3a_data; + Iso14443_4aAtsData ats_data; +} Iso14443_4aData; // Virtual methods diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h index e45fb90cca..e5483a6ba1 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h @@ -2,8 +2,6 @@ #include "iso14443_4a.h" -#include - #define ISO14443_4A_CMD_READ_ATS (0xE0) // ATS bit definitions @@ -23,20 +21,6 @@ #define ISO14443_4A_ATS_TC1_NAD (1U << 0) #define ISO14443_4A_ATS_TC1_CID (1U << 1) -typedef struct { - uint8_t tl; - uint8_t t0; - uint8_t ta_1; - uint8_t tb_1; - uint8_t tc_1; - SimpleArray* t1_tk; -} Iso14443_4aAtsData; - -struct Iso14443_4aData { - Iso14443_3aData* iso14443_3a_data; - Iso14443_4aAtsData ats_data; -}; - bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf); Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c index c07cc6b7f0..e20048b509 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c @@ -55,8 +55,7 @@ static NfcCommand iso14443_4a_poller_handler_idle(Iso14443_4aPoller* instance) { } static NfcCommand iso14443_4a_poller_handler_read_ats(Iso14443_4aPoller* instance) { - Iso14443_4aError error = - iso14443_4a_poller_async_read_ats(instance, &instance->data->ats_data); + Iso14443_4aError error = iso14443_4a_poller_read_ats(instance, &instance->data->ats_data); if(error == Iso14443_4aErrorNone) { FURI_LOG_D(TAG, "Read ATS success"); instance->poller_state = Iso14443_4aPollerStateReady; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index b224299e0a..73eb6ef74d 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -8,22 +8,80 @@ extern "C" { #endif +/** + * @brief Iso14443_4aPoller opaque type definition. + */ typedef struct Iso14443_4aPoller Iso14443_4aPoller; +/** + * @brief Enumeration of possible Iso14443_4a poller event types. + */ typedef enum { - Iso14443_4aPollerEventTypeError, - Iso14443_4aPollerEventTypeReady, + Iso14443_4aPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_4aPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_4aPollerEventType; -typedef struct { - Iso14443_4aError error; +/** + * @brief Iso14443_4a poller event data. + */ +typedef union { + Iso14443_4aError error; /**< Error code indicating card activation fail reason. */ } Iso14443_4aPollerEventData; +/** + * @brief Iso14443_4a poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_4aPollerEventType type; - Iso14443_4aPollerEventData* data; + Iso14443_4aPollerEventType type; /**< Type of emmitted event. */ + Iso14443_4aPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_4aPollerEvent; +/** + * @brief Transmit and receive Iso14443_4a blocks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer. The fwt parameter is calculated during activation procedure. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_4aPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); + +/** + * @brief Read Answer To Select (ATS) from the card. + * + * Must ONLY be used inside the callback function. + * + * Send Request Answer To Select (RATS) command to the card and parse the response. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with ATS data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError + iso14443_4a_poller_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index 7221e2aa94..938e4e715f 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -18,7 +18,7 @@ Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) { } Iso14443_4aError - iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data) { + iso14443_4a_poller_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data) { furi_assert(instance); bit_buffer_reset(instance->tx_buffer); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h index ce878cb40a..7aae852e43 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h @@ -45,16 +45,6 @@ struct Iso14443_4aPoller { const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance); -Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); - -Iso14443_4aError - iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data); - -Iso14443_4aError iso14443_4a_poller_send_block( - Iso14443_4aPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h index e60090c04e..03b288c079 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h @@ -8,22 +8,66 @@ extern "C" { #endif +/** + * @brief Iso14443_4bPoller opaque type definition. + */ typedef struct Iso14443_4bPoller Iso14443_4bPoller; +/** + * @brief Enumeration of possible Iso14443_4b poller event types. + */ typedef enum { - Iso14443_4bPollerEventTypeError, - Iso14443_4bPollerEventTypeReady, + Iso14443_4bPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_4bPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_4bPollerEventType; -typedef struct { - Iso14443_4bError error; +/** + * @brief Iso14443_4b poller event data. + */ +typedef union { + Iso14443_4bError error; /**< Error code indicating card activation fail reason. */ } Iso14443_4bPollerEventData; +/** + * @brief Iso14443_4b poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_4bPollerEventType type; - Iso14443_4bPollerEventData* data; + Iso14443_4bPollerEventType type; /**< Type of emmitted event. */ + Iso14443_4bPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_4bPollerEvent; +/** + * @brief Transmit and receive Iso14443_4b blocks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer. The fwt parameter is calculated during activation procedure. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4bErrorNone on success, an error code on failure. + */ +Iso14443_4bError iso14443_4b_poller_send_block( + Iso14443_4bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_4aPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_4bErrorNone on success, an error code on failure. + */ +Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h index bd55c61882..fc9c2632b2 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h @@ -42,13 +42,6 @@ struct Iso14443_4bPoller { const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance); -Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); - -Iso14443_4bError iso14443_4b_poller_send_block( - Iso14443_4bPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c index 9500e16539..cf27b1f3fd 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c @@ -70,7 +70,7 @@ static NfcCommand iso15693_3_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != Iso15693_3PollerStateActivated) { - Iso15693_3Error error = iso15693_3_poller_async_activate(instance, instance->data); + Iso15693_3Error error = iso15693_3_poller_activate(instance, instance->data); if(error == Iso15693_3ErrorNone) { instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady; instance->iso15693_3_event_data.error = error; @@ -105,7 +105,7 @@ static bool iso15693_3_poller_detect(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { uint8_t uid[ISO15693_3_UID_SIZE]; - Iso15693_3Error error = iso15693_3_poller_async_inventory(instance, uid); + Iso15693_3Error error = iso15693_3_poller_inventory(instance, uid); protocol_detected = (error == Iso15693_3ErrorNone); } diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h index 9d73242f1a..a187ceace1 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -8,22 +8,142 @@ extern "C" { #endif +/** + * @brief Iso15693_3Poller opaque type definition. + */ typedef struct Iso15693_3Poller Iso15693_3Poller; +/** + * @brief Enumeration of possible Iso15693_3 poller event types. + */ typedef enum { - Iso15693_3PollerEventTypeError, - Iso15693_3PollerEventTypeReady, + Iso15693_3PollerEventTypeError, /**< The card was activated by the poller. */ + Iso15693_3PollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso15693_3PollerEventType; -typedef struct { - Iso15693_3Error error; +/** + * @brief Iso15693_3 poller event data. + */ +typedef union { + Iso15693_3Error error; /**< Error code indicating card activation fail reason. */ } Iso15693_3PollerEventData; +/** + * @brief Iso15693_3 poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso15693_3PollerEventType type; - Iso15693_3PollerEventData* data; + Iso15693_3PollerEventType type; /**< Type of emmitted event. */ + Iso15693_3PollerEventData* data; /**< Pointer to event specific data. */ } Iso15693_3PollerEvent; +/** + * @brief Transmit and receive Iso15693_3 frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_send_frame( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Perform activation procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the activation procedure as defined in Iso15693_3. The data + * field will be filled with Iso15693_3Data data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Iso15693_3 data structure to be filled. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_3Data* data); + +/** + * @brief Send invertory command and parse response. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] uid pointer to the buffer to be filled with the UID. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_inventory(Iso15693_3Poller* instance, uint8_t* uid); + +/** + * @brief Send get system info command and parse response. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Iso15693_3SystemInfo structure to be filled. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error + iso15693_3_poller_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data); + +/** + * @brief Read Iso15693_3 block. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with the block data. + * @param[in] block_number block number to be read. + * @param[in] block_size size of the block to be read. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_read_block( + Iso15693_3Poller* instance, + uint8_t* data, + uint8_t block_number, + uint8_t block_size); + +/** + * @brief Read multiple Iso15693_3 blocks. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with the block data. + * @param[in] block_count number of blocks to be read. + * @param[in] block_size size of the blocks to be read. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_read_blocks( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count, + uint8_t block_size); + +/** + * @brief Get Iso15693_3 block security status. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with the block security status. + * @param[in] block_count block security number to be read. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_get_blocks_security( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index 8b8d8cee04..917f7dbb8e 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -36,7 +36,7 @@ static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) furi_assert(instance); if(instance->state == Iso15693_3PollerStateIdle) { - return iso15693_3_poller_async_activate(instance, NULL); + return iso15693_3_poller_activate(instance, NULL); } return Iso15693_3ErrorNone; @@ -80,8 +80,7 @@ static Iso15693_3Error iso15693_3_poller_frame_exchange( return ret; } -Iso15693_3Error - iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data) { +Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_3Data* data) { furi_assert(instance); furi_assert(instance->nfc); @@ -93,7 +92,7 @@ Iso15693_3Error instance->state = Iso15693_3PollerStateColResInProgress; // Inventory: Mandatory command - ret = iso15693_3_poller_async_inventory(instance, data->uid); + ret = iso15693_3_poller_inventory(instance, data->uid); if(ret != Iso15693_3ErrorNone) { instance->state = Iso15693_3PollerStateColResFailed; break; @@ -103,7 +102,7 @@ Iso15693_3Error // Get system info: Optional command Iso15693_3SystemInfo* system_info = &data->system_info; - ret = iso15693_3_poller_async_get_system_info(instance, system_info); + ret = iso15693_3_poller_get_system_info(instance, system_info); if(ret != Iso15693_3ErrorNone) { ret = iso15693_3_poller_filter_error(ret); break; @@ -111,7 +110,7 @@ Iso15693_3Error // Read blocks: Optional command simple_array_init(data->block_data, system_info->block_count * system_info->block_size); - ret = iso15693_3_poller_async_read_blocks( + ret = iso15693_3_poller_read_blocks( instance, simple_array_get_data(data->block_data), system_info->block_count, @@ -124,7 +123,7 @@ Iso15693_3Error // Get block security status: Optional command simple_array_init(data->block_security, system_info->block_count); - ret = iso15693_3_poller_async_get_blocks_security( + ret = iso15693_3_poller_get_blocks_security( instance, simple_array_get_data(data->block_security), system_info->block_count); if(ret != Iso15693_3ErrorNone) { ret = iso15693_3_poller_filter_error(ret); @@ -136,7 +135,7 @@ Iso15693_3Error return ret; } -Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid) { +Iso15693_3Error iso15693_3_poller_inventory(Iso15693_3Poller* instance, uint8_t* uid) { furi_assert(instance); furi_assert(instance->nfc); furi_assert(uid); @@ -165,9 +164,8 @@ Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, ui return ret; } -Iso15693_3Error iso15693_3_poller_async_get_system_info( - Iso15693_3Poller* instance, - Iso15693_3SystemInfo* data) { +Iso15693_3Error + iso15693_3_poller_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data) { furi_assert(instance); furi_assert(data); @@ -193,7 +191,7 @@ Iso15693_3Error iso15693_3_poller_async_get_system_info( return ret; } -Iso15693_3Error iso15693_3_poller_async_read_block( +Iso15693_3Error iso15693_3_poller_read_block( Iso15693_3Poller* instance, uint8_t* data, uint8_t block_number, @@ -222,7 +220,7 @@ Iso15693_3Error iso15693_3_poller_async_read_block( return ret; } -Iso15693_3Error iso15693_3_poller_async_read_blocks( +Iso15693_3Error iso15693_3_poller_read_blocks( Iso15693_3Poller* instance, uint8_t* data, uint16_t block_count, @@ -235,14 +233,14 @@ Iso15693_3Error iso15693_3_poller_async_read_blocks( Iso15693_3Error ret = Iso15693_3ErrorNone; for(uint32_t i = 0; i < block_count; ++i) { - ret = iso15693_3_poller_async_read_block(instance, &data[block_size * i], i, block_size); + ret = iso15693_3_poller_read_block(instance, &data[block_size * i], i, block_size); if(ret != Iso15693_3ErrorNone) break; } return ret; } -Iso15693_3Error iso15693_3_poller_async_get_blocks_security( +Iso15693_3Error iso15693_3_poller_get_blocks_security( Iso15693_3Poller* instance, uint8_t* data, uint16_t block_count) { diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h index 154ee684c9..346d0d724c 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h @@ -35,36 +35,6 @@ struct Iso15693_3Poller { const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance); -Iso15693_3Error iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data); - -Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid); - -Iso15693_3Error - iso15693_3_poller_async_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data); - -Iso15693_3Error iso15693_3_poller_async_read_block( - Iso15693_3Poller* instance, - uint8_t* data, - uint8_t block_number, - uint8_t block_size); - -Iso15693_3Error iso15693_3_poller_async_read_blocks( - Iso15693_3Poller* instance, - uint8_t* data, - uint16_t block_count, - uint8_t block_size); - -Iso15693_3Error iso15693_3_poller_async_get_blocks_security( - Iso15693_3Poller* instance, - uint8_t* data, - uint16_t block_count); - -Iso15693_3Error iso15693_3_poller_send_frame( - Iso15693_3Poller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3eba6ee569..dbc32a1b51 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -85,7 +85,7 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { iso14443_3a_copy( instance->data->iso14443_3a_data, iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); - MfClassicError error = mf_classic_async_get_nt(instance, 254, MfClassicKeyTypeA, NULL); + MfClassicError error = mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType4k; instance->state = MfClassicPollerStateStart; @@ -95,7 +95,7 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { instance->current_type_check = MfClassicType1k; } } else if(instance->current_type_check == MfClassicType1k) { - MfClassicError error = mf_classic_async_get_nt(instance, 62, MfClassicKeyTypeA, NULL); + MfClassicError error = mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType1k; FURI_LOG_D(TAG, "1K detected"); @@ -234,7 +234,7 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { do { // Authenticate to sector - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block); @@ -243,8 +243,8 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { } // Read block from tag - error = - mf_classic_async_read_block(instance, write_ctx->current_block, &write_ctx->tag_block); + error = mf_classic_poller_read_block( + instance, write_ctx->current_block, &write_ctx->tag_block); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to read block %d", write_ctx->current_block); instance->state = MfClassicPollerStateFail; @@ -252,11 +252,11 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { } if(write_ctx->is_value_block) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateWriteValueBlock; } else { if(write_ctx->need_halt_before_write) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); } instance->state = MfClassicPollerStateWriteBlock; } @@ -292,7 +292,7 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { // Reauth if necessary if(write_ctx->need_halt_before_write) { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL); if(error != MfClassicErrorNone) { FURI_LOG_D( @@ -303,7 +303,7 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { } // Write block - error = mf_classic_async_write_block( + error = mf_classic_poller_write_block( instance, write_ctx->current_block, &instance->mfc_event_data.write_block_data.write_block); @@ -315,7 +315,7 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { } while(false); - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); write_ctx->current_block++; instance->state = MfClassicPollerStateCheckWriteConditions; @@ -403,18 +403,18 @@ NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance &write_ctx->sec_tr.key_b; MfClassicError error = - mf_classic_async_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); + mf_classic_poller_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_cmd(instance, write_ctx->current_block, value_cmd, diff); + error = mf_classic_poller_value_cmd(instance, write_ctx->current_block, value_cmd, diff); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_transfer(instance, write_ctx->current_block); + error = mf_classic_poller_value_transfer(instance, write_ctx->current_block); if(error != MfClassicErrorNone) break; } while(false); - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); write_ctx->is_value_block = false; write_ctx->current_block++; instance->state = MfClassicPollerStateCheckWriteConditions; @@ -462,7 +462,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* sec_read_ctx->current_block, sec_read_ctx->key_type == MfClassicKeyTypeA ? 'A' : 'B', key); - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, sec_read_ctx->current_block, &sec_read_ctx->key, @@ -481,7 +481,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* FURI_LOG_D(TAG, "Reading block %d", sec_read_ctx->current_block); MfClassicBlock read_block = {}; - error = mf_classic_async_read_block(instance, sec_read_ctx->current_block, &read_block); + error = mf_classic_poller_read_block(instance, sec_read_ctx->current_block, &read_block); if(error == MfClassicErrorNone) { mf_classic_set_block_read(instance->data, sec_read_ctx->current_block, &read_block); if(sec_read_ctx->key_type == MfClassicKeyTypeA) { @@ -489,7 +489,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* instance, sec_read_ctx->current_block, &read_block); } } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); sec_read_ctx->auth_passed = false; } } while(false); @@ -497,7 +497,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* uint8_t sec_tr_num = mf_classic_get_sector_trailer_num_by_sector(sec_read_ctx->current_sector); sec_read_ctx->current_block++; if(sec_read_ctx->current_block > sec_tr_num) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateRequestReadSector; } @@ -532,7 +532,7 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); @@ -545,7 +545,7 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateAuthKeyB; } } @@ -570,7 +570,7 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); @@ -584,7 +584,7 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateRequestKey; } } @@ -621,7 +621,7 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { if(mf_classic_is_block_read(instance->data, block_num)) break; if(!dict_attack_ctx->auth_passed) { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, block_num, &dict_attack_ctx->current_key, @@ -635,10 +635,10 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { } FURI_LOG_D(TAG, "Reading block %d", block_num); - error = mf_classic_async_read_block(instance, block_num, &block); + error = mf_classic_poller_read_block(instance, block_num, &block); if(error != MfClassicErrorNone) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; FURI_LOG_D(TAG, "Failed to read block %d", block_num); } else { @@ -655,7 +655,7 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { if(dict_attack_ctx->current_block > sec_tr_block_num) { mf_classic_poller_handle_data_update(instance); - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; if(dict_attack_ctx->current_sector == instance->sectors_total) { @@ -713,7 +713,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); @@ -726,7 +726,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateKeyReuseReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateKeyReuseStart; } @@ -748,7 +748,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); @@ -762,7 +762,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateKeyReuseReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateKeyReuseStart; } @@ -783,7 +783,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst if(mf_classic_is_block_read(instance->data, block_num)) break; if(!dict_attack_ctx->auth_passed) { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, block_num, &dict_attack_ctx->current_key, @@ -796,10 +796,10 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst } FURI_LOG_D(TAG, "Reading block %d", block_num); - error = mf_classic_async_read_block(instance, block_num, &block); + error = mf_classic_poller_read_block(instance, block_num, &block); if(error != MfClassicErrorNone) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; FURI_LOG_D(TAG, "Failed to read block %d", block_num); } else { @@ -814,7 +814,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->reuse_key_sector); dict_attack_ctx->current_block++; if(dict_attack_ctx->current_block > sec_tr_block_num) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; mf_classic_poller_handle_data_update(instance); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index da1f3c3dce..f05a6800ad 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -7,103 +7,274 @@ extern "C" { #endif +/** + * @brief MfClassicPoller opaque type definition. + */ typedef struct MfClassicPoller MfClassicPoller; +/** + * @brief Enumeration of possible MfClassic poller event types. + */ typedef enum { - // Start event - MfClassicPollerEventTypeRequestMode, - - // Read with key cache events - MfClassicPollerEventTypeRequestReadSector, - - // Write events - MfClassicPollerEventTypeRequestSectorTrailer, - MfClassicPollerEventTypeRequestWriteBlock, - - // Dictionary attack events - MfClassicPollerEventTypeRequestKey, - MfClassicPollerEventTypeNextSector, - MfClassicPollerEventTypeDataUpdate, - MfClassicPollerEventTypeFoundKeyA, - MfClassicPollerEventTypeFoundKeyB, - MfClassicPollerEventTypeCardNotDetected, - MfClassicPollerEventTypeKeyAttackStart, - MfClassicPollerEventTypeKeyAttackStop, - MfClassicPollerEventTypeKeyAttackNextSector, - - // Common events - MfClassicPollerEventTypeCardDetected, - MfClassicPollerEventTypeCardLost, - MfClassicPollerEventTypeSuccess, - MfClassicPollerEventTypeFail, + MfClassicPollerEventTypeRequestMode, /**< Poller requests to fill the mode. */ + + MfClassicPollerEventTypeRequestReadSector, /**< Poller requests data to read sector. */ + + MfClassicPollerEventTypeRequestSectorTrailer, /**< Poller requests sector trailer for writing block. */ + MfClassicPollerEventTypeRequestWriteBlock, /**< Poller requests data to write block. */ + + MfClassicPollerEventTypeRequestKey, /**< Poller requests key for sector authentication. */ + MfClassicPollerEventTypeNextSector, /**< Poller switches to next sector during dictionary attack. */ + MfClassicPollerEventTypeDataUpdate, /**< Poller updates data. */ + MfClassicPollerEventTypeFoundKeyA, /**< Poller found key A. */ + MfClassicPollerEventTypeFoundKeyB, /**< Poller found key B. */ + MfClassicPollerEventTypeKeyAttackStart, /**< Poller starts key attack. */ + MfClassicPollerEventTypeKeyAttackStop, /**< Poller stops key attack. */ + MfClassicPollerEventTypeKeyAttackNextSector, /**< Poller switches to next sector during key attack. */ + + MfClassicPollerEventTypeCardDetected, /**< Poller detected card. */ + MfClassicPollerEventTypeCardLost, /**< Poller lost card. */ + MfClassicPollerEventTypeSuccess, /**< Poller succeeded. */ + MfClassicPollerEventTypeFail, /**< Poller failed. */ } MfClassicPollerEventType; +/** + * @brief MfClassic poller mode. + */ typedef enum { - MfClassicPollerModeRead, - MfClassicPollerModeWrite, - MfClassicPollerModeDictAttack, + MfClassicPollerModeRead, /**< Poller reading mode. */ + MfClassicPollerModeWrite, /**< Poller writing mode. */ + MfClassicPollerModeDictAttack, /**< Poller dictionary attack mode. */ } MfClassicPollerMode; +/** + * @brief MfClassic poller request mode event data. + * + * This instance of this structure must be filled on MfClassicPollerEventTypeRequestMode event. + */ typedef struct { - MfClassicPollerMode mode; - const MfClassicData* data; + MfClassicPollerMode mode; /**< Mode to be used by poller. */ + const MfClassicData* data; /**< Data to be used by poller. */ } MfClassicPollerEventDataRequestMode; +/** + * @brief MfClassic poller next sector event data. + * + * The instance of this structure is filled by poller and passed with + * MfClassicPollerEventTypeNextSector event. + */ typedef struct { - uint8_t current_sector; + uint8_t current_sector; /**< Current sector number. */ } MfClassicPollerEventDataDictAttackNextSector; +/** + * @brief MfClassic poller update event data. + * + * The instance of this structure is filled by poller and passed with + * MfClassicPollerEventTypeDataUpdate event. + */ typedef struct { - uint8_t sectors_read; - uint8_t keys_found; - uint8_t current_sector; + uint8_t sectors_read; /**< Number of sectors read. */ + uint8_t keys_found; /**< Number of keys found. */ + uint8_t current_sector; /**< Current sector number. */ } MfClassicPollerEventDataUpdate; +/** + * @brief MfClassic poller key request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestKey event. + */ typedef struct { - MfClassicKey key; - bool key_provided; + MfClassicKey key; /**< Key to be used by poller. */ + bool key_provided; /**< Flag indicating if key is provided. */ } MfClassicPollerEventDataKeyRequest; +/** + * @brief MfClassic poller read sector request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestReadSector event. + */ typedef struct { - uint8_t sector_num; - MfClassicKey key; - MfClassicKeyType key_type; - bool key_provided; + uint8_t sector_num; /**< Sector number to be read. */ + MfClassicKey key; /**< Key to be used by poller. */ + MfClassicKeyType key_type; /**< Key type to be used by poller. */ + bool key_provided; /**< Flag indicating if key is provided. */ } MfClassicPollerEventDataReadSectorRequest; +/** + * @brief MfClassic poller sector trailer request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestSectorTrailer event. + */ typedef struct { - uint8_t sector_num; - MfClassicBlock sector_trailer; - bool sector_trailer_provided; + uint8_t sector_num; /**< Sector number to be read. */ + MfClassicBlock sector_trailer; /**< Sector trailer to be used by poller. */ + bool sector_trailer_provided; /**< Flag indicating if sector trailer is provided. */ } MfClassicPollerEventDataSectorTrailerRequest; +/** + * @brief MfClassic poller write block request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestWriteBlock event. + */ typedef struct { - uint8_t block_num; - MfClassicBlock write_block; - bool write_block_provided; + uint8_t block_num; /**< Block number to be written. */ + MfClassicBlock write_block; /**< Block to be written. */ + bool write_block_provided; /**< Flag indicating if block is provided. */ } MfClassicPollerEventDataWriteBlockRequest; +/** + * @brief MfClassic poller key attack event data. + * + * The instance of this structure is filled by poller and passed with + * MfClassicPollerEventTypeKeyAttackNextSector event. + */ typedef struct { - uint8_t current_sector; + uint8_t current_sector; /**< Current sector number. */ } MfClassicPollerEventKeyAttackData; +/** + * @brief MfClassic poller event data. + */ typedef union { - MfClassicError error; - MfClassicPollerEventDataRequestMode poller_mode; - MfClassicPollerEventDataDictAttackNextSector next_sector_data; - MfClassicPollerEventDataKeyRequest key_request_data; - MfClassicPollerEventDataUpdate data_update; - MfClassicPollerEventDataReadSectorRequest read_sector_request_data; - MfClassicPollerEventKeyAttackData key_attack_data; - MfClassicPollerEventDataSectorTrailerRequest sec_tr_data; - MfClassicPollerEventDataWriteBlockRequest write_block_data; + MfClassicError error; /**< Error code on MfClassicPollerEventTypeFail event. */ + MfClassicPollerEventDataRequestMode poller_mode; /**< Poller mode context. */ + MfClassicPollerEventDataDictAttackNextSector next_sector_data; /**< Next sector context. */ + MfClassicPollerEventDataKeyRequest key_request_data; /**< Key request context. */ + MfClassicPollerEventDataUpdate data_update; /**< Data update context. */ + MfClassicPollerEventDataReadSectorRequest + read_sector_request_data; /**< Read sector request context. */ + MfClassicPollerEventKeyAttackData key_attack_data; /**< Key attack context. */ + MfClassicPollerEventDataSectorTrailerRequest sec_tr_data; /**< Sector trailer request context. */ + MfClassicPollerEventDataWriteBlockRequest write_block_data; /**< Write block request context. */ } MfClassicPollerEventData; +/** + * @brief MfClassic poller event. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - MfClassicPollerEventType type; - MfClassicPollerEventData* data; + MfClassicPollerEventType type; /**< Event type. */ + MfClassicPollerEventData* data; /**< Pointer to event specific data. */ } MfClassicPollerEvent; +/** + * @brief Collect tag nonce during authentication. + * + * Must ONLY be used inside the callback function. + * + * Starts authentication procedure and collects tag nonce. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + +/** + * @brief Perform authentication. + * + * Must ONLY be used inside the callback function. + * + * Perform authentication as specified in Mf Classic protocol. Initialize crypto state for futher + * communication with the tag. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key key to be used for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +/** + * @brief Halt the tag. + * + * Must ONLY be used inside the callback function. + * + * Halt the tag and reset crypto state of the poller. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_halt(MfClassicPoller* instance); + +/** + * @brief Read block from tag. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be read. + * @param[out] data pointer to the MfClassicBlock structure to be filled with block data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_read_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data); + +/** + * @brief Write block to tag. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be written. + * @param[in] data pointer to the MfClassicBlock structure to be written. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_write_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data); + +/** + * @brief Perform value command on tag. + * + * Must ONLY be used inside the callback function. + * + * Perform Increment, Decrement or Restore command on tag. The result is stored in internal transfer + * block of the tag. Use mf_classic_poller_value_transfer to transfer the result to the tag. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be used for value command. + * @param[in] cmd value command to be performed. + * @param[in] data value to be used for value command. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_value_cmd( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicValueCommand cmd, + int32_t data); + +/** + * @brief Transfer internal transfer block to tag. + * + * Must ONLY be used inside the callback function. + * + * Transfer internal transfer block to tag. The block is filled by mf_classic_poller_value_cmd. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be used for value command. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 7eab4fe3b5..4b071815ea 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -33,7 +33,7 @@ MfClassicError mf_classic_process_error(Iso14443_3aError error) { return ret; } -MfClassicError mf_classic_async_get_nt( +MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, @@ -69,7 +69,7 @@ MfClassicError mf_classic_async_get_nt( return ret; } -MfClassicError mf_classic_async_auth( +MfClassicError mf_classic_poller_auth( MfClassicPoller* instance, uint8_t block_num, MfClassicKey* key, @@ -84,7 +84,7 @@ MfClassicError mf_classic_async_auth( iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); MfClassicNt nt = {}; - ret = mf_classic_async_get_nt(instance, block_num, key_type, &nt); + ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); if(ret != MfClassicErrorNone) break; if(data) { data->nt = nt; @@ -130,7 +130,7 @@ MfClassicError mf_classic_async_auth( return ret; } -MfClassicError mf_classic_async_halt(MfClassicPoller* instance) { +MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -158,7 +158,7 @@ MfClassicError mf_classic_async_halt(MfClassicPoller* instance) { return ret; } -MfClassicError mf_classic_async_read_block( +MfClassicError mf_classic_poller_read_block( MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data) { @@ -204,7 +204,7 @@ MfClassicError mf_classic_async_read_block( return ret; } -MfClassicError mf_classic_async_write_block( +MfClassicError mf_classic_poller_write_block( MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data) { @@ -275,7 +275,7 @@ MfClassicError mf_classic_async_write_block( return ret; } -MfClassicError mf_classic_async_value_cmd( +MfClassicError mf_classic_poller_value_cmd( MfClassicPoller* instance, uint8_t block_num, MfClassicValueCommand cmd, @@ -345,7 +345,7 @@ MfClassicError mf_classic_async_value_cmd( return ret; } -MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num) { +MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index c6f4ccf7f0..0be42196f1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -169,37 +169,6 @@ MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller); void mf_classic_poller_free(MfClassicPoller* instance); -MfClassicError mf_classic_async_get_nt( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicKeyType key_type, - MfClassicNt* nt); - -MfClassicError mf_classic_async_auth( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicKey* key, - MfClassicKeyType key_type, - MfClassicAuthContext* data); - -MfClassicError mf_classic_async_halt(MfClassicPoller* instance); - -MfClassicError - mf_classic_async_read_block(MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data); - -MfClassicError mf_classic_async_write_block( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicBlock* data); - -MfClassicError mf_classic_async_value_cmd( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicValueCommand cmd, - int32_t data); - -MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c similarity index 88% rename from lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c rename to lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c index 8b9fb69f16..69954452aa 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c @@ -1,3 +1,4 @@ +#include "mf_classic_poller_sync.h" #include "mf_classic_poller_i.h" #include @@ -32,7 +33,7 @@ typedef MfClassicError ( static MfClassicError mf_classic_poller_collect_nt_handler( MfClassicPoller* poller, MfClassicPollerContextData* data) { - return mf_classic_async_get_nt( + return mf_classic_poller_get_nt( poller, data->collect_nt_context.block, data->collect_nt_context.key_type, @@ -41,7 +42,7 @@ static MfClassicError mf_classic_poller_collect_nt_handler( static MfClassicError mf_classic_poller_auth_handler(MfClassicPoller* poller, MfClassicPollerContextData* data) { - return mf_classic_async_auth( + return mf_classic_poller_auth( poller, data->auth_context.block_num, &data->auth_context.key, @@ -55,7 +56,7 @@ static MfClassicError mf_classic_poller_read_block_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->read_block_context.block_num, &data->read_block_context.key, @@ -63,11 +64,11 @@ static MfClassicError mf_classic_poller_read_block_handler( NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_read_block( + error = mf_classic_poller_read_block( poller, data->read_block_context.block_num, &data->read_block_context.block); if(error != MfClassicErrorNone) break; - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; } while(false); @@ -81,7 +82,7 @@ static MfClassicError mf_classic_poller_write_block_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->read_block_context.block_num, &data->read_block_context.key, @@ -89,11 +90,11 @@ static MfClassicError mf_classic_poller_write_block_handler( NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_write_block( + error = mf_classic_poller_write_block( poller, data->write_block_context.block_num, &data->write_block_context.block); if(error != MfClassicErrorNone) break; - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; } while(false); @@ -107,7 +108,7 @@ static MfClassicError mf_classic_poller_read_value_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->read_value_context.block_num, &data->read_value_context.key, @@ -116,7 +117,7 @@ static MfClassicError mf_classic_poller_read_value_handler( if(error != MfClassicErrorNone) break; MfClassicBlock block = {}; - error = mf_classic_async_read_block(poller, data->read_value_context.block_num, &block); + error = mf_classic_poller_read_block(poller, data->read_value_context.block_num, &block); if(error != MfClassicErrorNone) break; if(!mf_classic_block_to_value(&block, &data->read_value_context.value, NULL)) { @@ -124,7 +125,7 @@ static MfClassicError mf_classic_poller_read_value_handler( break; } - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; } while(false); @@ -138,7 +139,7 @@ static MfClassicError mf_classic_poller_change_value_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->change_value_context.block_num, &data->change_value_context.key, @@ -146,21 +147,21 @@ static MfClassicError mf_classic_poller_change_value_handler( NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_cmd( + error = mf_classic_poller_value_cmd( poller, data->change_value_context.block_num, data->change_value_context.value_cmd, data->change_value_context.data); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_transfer(poller, data->change_value_context.block_num); + error = mf_classic_poller_value_transfer(poller, data->change_value_context.block_num); if(error != MfClassicErrorNone) break; MfClassicBlock block = {}; - error = mf_classic_async_read_block(poller, data->change_value_context.block_num, &block); + error = mf_classic_poller_read_block(poller, data->change_value_context.block_num, &block); if(error != MfClassicErrorNone) break; - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; if(!mf_classic_block_to_value(&block, &data->change_value_context.new_value, NULL)) { @@ -182,16 +183,14 @@ static const MfClassicPollerCmdHandler mf_classic_poller_cmd_handlers[MfClassicP [MfClassicPollerCmdTypeChangeValue] = mf_classic_poller_change_value_handler, }; -static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* context) { - furi_assert(event.instance); - furi_assert(event.protocol == NfcProtocolIso14443_3a); - furi_assert(event.event_data); +static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEventEx event, void* context) { + furi_assert(event.poller); + furi_assert(event.parent_event_data); furi_assert(context); MfClassicPollerContext* poller_context = context; - Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; - Iso14443_3aPoller* iso14443_3a_poller = event.instance; - MfClassicPoller* mfc_poller = mf_classic_poller_alloc(iso14443_3a_poller); + Iso14443_3aPollerEvent* iso14443_3a_event = event.parent_event_data; + MfClassicPoller* mfc_poller = event.poller; if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { poller_context->error = mf_classic_poller_cmd_handlers[poller_context->cmd_type]( @@ -202,8 +201,6 @@ static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* co furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT); - mf_classic_poller_free(mfc_poller); - return NfcCommandStop; } @@ -212,8 +209,8 @@ static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerCon poller_ctx->thread_id = furi_thread_get_current_id(); - NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); - nfc_poller_start(poller, mf_classic_poller_cmd_callback, poller_ctx); + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfClassic); + nfc_poller_start_ex(poller, mf_classic_poller_cmd_callback, poller_ctx); furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT); @@ -223,7 +220,7 @@ static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerCon return poller_ctx->error; } -MfClassicError mf_classic_poller_collect_nt( +MfClassicError mf_classic_poller_sync_collect_nt( Nfc* nfc, uint8_t block_num, MfClassicKeyType key_type, @@ -247,7 +244,7 @@ MfClassicError mf_classic_poller_collect_nt( return error; } -MfClassicError mf_classic_poller_auth( +MfClassicError mf_classic_poller_sync_auth( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -274,7 +271,7 @@ MfClassicError mf_classic_poller_auth( return error; } -MfClassicError mf_classic_poller_read_block( +MfClassicError mf_classic_poller_sync_read_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -300,7 +297,7 @@ MfClassicError mf_classic_poller_read_block( return error; } -MfClassicError mf_classic_poller_write_block( +MfClassicError mf_classic_poller_sync_write_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -323,7 +320,7 @@ MfClassicError mf_classic_poller_write_block( return error; } -MfClassicError mf_classic_poller_read_value( +MfClassicError mf_classic_poller_sync_read_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -349,7 +346,7 @@ MfClassicError mf_classic_poller_read_value( return error; } -MfClassicError mf_classic_poller_change_value( +MfClassicError mf_classic_poller_sync_change_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -461,7 +458,7 @@ NfcCommand mf_classic_poller_read_callback(NfcGenericEvent event, void* context) } MfClassicError - mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) { + mf_classic_poller_sync_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) { furi_assert(nfc); furi_assert(keys); furi_assert(data); @@ -498,7 +495,7 @@ MfClassicError return error; } -MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) { +MfClassicError mf_classic_poller_sync_detect_type(Nfc* nfc, MfClassicType* type) { furi_assert(nfc); furi_assert(type); @@ -512,7 +509,7 @@ MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) { size_t i = 0; for(i = 0; i < COUNT_OF(mf_classic_verify_block); i++) { - error = mf_classic_poller_collect_nt( + error = mf_classic_poller_sync_collect_nt( nfc, mf_classic_verify_block[MfClassicTypeNum - i - 1], MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { *type = MfClassicTypeNum - i - 1; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h similarity index 64% rename from lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h rename to lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h index 11db291b80..d384e46e4e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h @@ -7,41 +7,41 @@ extern "C" { #endif -MfClassicError mf_classic_poller_collect_nt( +MfClassicError mf_classic_poller_sync_collect_nt( Nfc* nfc, uint8_t block_num, MfClassicKeyType key_type, MfClassicNt* nt); -MfClassicError mf_classic_poller_auth( +MfClassicError mf_classic_poller_sync_auth( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data); -MfClassicError mf_classic_poller_read_block( +MfClassicError mf_classic_poller_sync_read_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicBlock* data); -MfClassicError mf_classic_poller_write_block( +MfClassicError mf_classic_poller_sync_write_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicBlock* data); -MfClassicError mf_classic_poller_read_value( +MfClassicError mf_classic_poller_sync_read_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, int32_t* value); -MfClassicError mf_classic_poller_change_value( +MfClassicError mf_classic_poller_sync_change_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -49,10 +49,10 @@ MfClassicError mf_classic_poller_change_value( int32_t data, int32_t* new_value); -MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type); +MfClassicError mf_classic_poller_sync_detect_type(Nfc* nfc, MfClassicType* type); MfClassicError - mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data); + mf_classic_poller_sync_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index 11db021d57..5af033d4ca 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -61,7 +61,7 @@ static NfcCommand mf_desfire_poller_handler_idle(MfDesfirePoller* instance) { } static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instance) { - instance->error = mf_desfire_poller_async_read_version(instance, &instance->data->version); + instance->error = mf_desfire_poller_read_version(instance, &instance->data->version); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read version success"); instance->state = MfDesfirePollerStateReadFreeMemory; @@ -75,8 +75,7 @@ static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instan } static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { - instance->error = - mf_desfire_poller_async_read_free_memory(instance, &instance->data->free_memory); + instance->error = mf_desfire_poller_read_free_memory(instance, &instance->data->free_memory); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; @@ -91,7 +90,7 @@ static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* in static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { instance->error = - mf_desfire_poller_async_read_key_settings(instance, &instance->data->master_key_settings); + mf_desfire_poller_read_key_settings(instance, &instance->data->master_key_settings); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key settings success"); instance->state = MfDesfirePollerStateReadMasterKeyVersion; @@ -105,7 +104,7 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePo } static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePoller* instance) { - instance->error = mf_desfire_poller_async_read_key_versions( + instance->error = mf_desfire_poller_read_key_versions( instance, instance->data->master_key_versions, instance->data->master_key_settings.max_keys); @@ -123,7 +122,7 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePol static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller* instance) { instance->error = - mf_desfire_poller_async_read_application_ids(instance, instance->data->application_ids); + mf_desfire_poller_read_application_ids(instance, instance->data->application_ids); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read application ids success"); instance->state = MfDesfirePollerStateReadApplications; @@ -137,7 +136,7 @@ static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller } static NfcCommand mf_desfire_poller_handler_read_applications(MfDesfirePoller* instance) { - instance->error = mf_desfire_poller_async_read_applications( + instance->error = mf_desfire_poller_read_applications( instance, instance->data->application_ids, instance->data->applications); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read applications success"); @@ -227,7 +226,7 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { MfDesfireVersion version = {}; - const MfDesfireError error = mf_desfire_poller_async_read_version(instance, &version); + const MfDesfireError error = mf_desfire_poller_read_version(instance, &version); protocol_detected = (error == MfDesfireErrorNone); } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h index 360b0508ff..6ef2f3f68d 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -8,24 +8,267 @@ extern "C" { #endif +/** + * @brief MfDesfirePoller opaque type definition. + */ typedef struct MfDesfirePoller MfDesfirePoller; +/** + * @brief Enumeration of possible MfDesfire poller event types. + */ typedef enum { - MfDesfirePollerEventTypeReadSuccess, - MfDesfirePollerEventTypeReadFailed, + MfDesfirePollerEventTypeReadSuccess, /**< Card was read successfully. */ + MfDesfirePollerEventTypeReadFailed, /**< Poller failed to read card. */ } MfDesfirePollerEventType; -typedef struct { - union { - MfDesfireError error; - }; +/** + * @brief MfDesfire poller event data. + */ +typedef union { + MfDesfireError error; /**< Error code indicating card reading fail reason. */ } MfDesfirePollerEventData; +/** + * @brief MfDesfire poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - MfDesfirePollerEventType type; - MfDesfirePollerEventData* data; + MfDesfirePollerEventType type; /**< Type of emmitted event. */ + MfDesfirePollerEventData* data; /**< Pointer to event specific data. */ } MfDesfirePollerEvent; +/** + * @brief Transmit and receive MfDesfire chunks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_send_chunks( + MfDesfirePoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Read MfDesfire card version. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireVersion structure to be filled with version data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data); + +/** + * @brief Read free memory available on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireFreeMemory structure to be filled with free memory data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data); + +/** + * @brief Read key settings on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireKeySettings structure to be filled with key settings data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_key_settings(MfDesfirePoller* instance, MfDesfireKeySettings* data); + +/** + * @brief Read key versions on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SimpleArray structure to be filled with key versions data. + * @param[in] count number of key versions to read. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_key_versions( + MfDesfirePoller* instance, + SimpleArray* data, + uint32_t count); + +/** + * @brief Read applications IDs on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SimpleArray structure to be filled with application ids data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_application_ids(MfDesfirePoller* instance, SimpleArray* data); + +/** + * @brief Select application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id pointer to the MfDesfireApplicationId structure with application id to select. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_select_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id); + +/** + * @brief Read file IDs for selected application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SimpleArray structure to be filled with file ids data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, SimpleArray* data); + +/** + * @brief Read file settings on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read settings for. + * @param[out] data pointer to the MfDesfireFileSettings structure to be filled with file settings data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_settings( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileSettings* data); + +/** + * @brief Read multiple file settings on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] file_ids pointer to the SimpleArray structure array with file ids to read settings for. + * @param[out] data pointer to the SimpleArray structure array to be filled with file settings data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_settings_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + SimpleArray* data); + +/** + * @brief Read file data on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read data from. + * @param[in] offset offset in bytes to start reading from. + * @param[in] size number of bytes to read. + * @param[out] data pointer to the MfDesfireFileData structure to be filled with file data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +/** + * @brief Read file value on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read value from. + * @param[out] data pointer to the MfDesfireFileData structure to be filled with file value. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_value( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileData* data); + +/** + * @brief Read file records on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read data from. + * @param[in] offset offset in bytes to start reading from. + * @param[in] size number of bytes to read. + * @param[out] data pointer to the MfDesfireFileData structure to be filled with file records data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_records( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +/** + * @brief Read data from multiple files on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] file_ids pointer to the SimpleArray structure array with files ids to read data from. + * @param[in] file_settings pointer to the SimpleArray structure array with files settings to read data from. + * @param[out] data pointer to the SimpleArray structure array to be filled with files data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_data_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + const SimpleArray* file_settings, + SimpleArray* data); + +/** + * @brief Read application data for selected application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireApplication structure to be filled with application data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_application(MfDesfirePoller* instance, MfDesfireApplication* data); + +/** + * @brief Read multiple applications data on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] app_ids pointer to the SimpleArray structure array with application ids to read data from. + * @param[out] data pointer to the SimpleArray structure array to be filled with applications data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_applications( + MfDesfirePoller* instance, + const SimpleArray* app_ids, + SimpleArray* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 38ae2f466d..0b2d841382 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -74,8 +74,7 @@ MfDesfireError mf_desfire_send_chunks( return error; } -MfDesfireError - mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { +MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -97,7 +96,7 @@ MfDesfireError } MfDesfireError - mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data) { + mf_desfire_poller_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -118,9 +117,8 @@ MfDesfireError return error; } -MfDesfireError mf_desfire_poller_async_read_key_settings( - MfDesfirePoller* instance, - MfDesfireKeySettings* data) { +MfDesfireError + mf_desfire_poller_read_key_settings(MfDesfirePoller* instance, MfDesfireKeySettings* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -141,7 +139,7 @@ MfDesfireError mf_desfire_poller_async_read_key_settings( return error; } -MfDesfireError mf_desfire_poller_async_read_key_versions( +MfDesfireError mf_desfire_poller_read_key_versions( MfDesfirePoller* instance, SimpleArray* data, uint32_t count) { @@ -172,7 +170,7 @@ MfDesfireError mf_desfire_poller_async_read_key_versions( } MfDesfireError - mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data) { + mf_desfire_poller_read_application_ids(MfDesfirePoller* instance, SimpleArray* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -203,7 +201,7 @@ MfDesfireError return error; } -MfDesfireError mf_desfire_poller_async_select_application( +MfDesfireError mf_desfire_poller_select_application( MfDesfirePoller* instance, const MfDesfireApplicationId* id) { furi_assert(instance); @@ -219,8 +217,7 @@ MfDesfireError mf_desfire_poller_async_select_application( return error; } -MfDesfireError - mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data) { +MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, SimpleArray* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -250,7 +247,7 @@ MfDesfireError return error; } -MfDesfireError mf_desfire_poller_async_read_file_settings( +MfDesfireError mf_desfire_poller_read_file_settings( MfDesfirePoller* instance, MfDesfireFileId id, MfDesfireFileSettings* data) { @@ -275,7 +272,7 @@ MfDesfireError mf_desfire_poller_async_read_file_settings( return error; } -MfDesfireError mf_desfire_poller_async_read_file_settings_multi( +MfDesfireError mf_desfire_poller_read_file_settings_multi( MfDesfirePoller* instance, const SimpleArray* file_ids, SimpleArray* data) { @@ -290,15 +287,14 @@ MfDesfireError mf_desfire_poller_async_read_file_settings_multi( for(uint32_t i = 0; i < file_id_count; ++i) { const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); - error = mf_desfire_poller_async_read_file_settings( - instance, file_id, simple_array_get(data, i)); + error = mf_desfire_poller_read_file_settings(instance, file_id, simple_array_get(data, i)); if(error != MfDesfireErrorNone) break; } return error; } -MfDesfireError mf_desfire_poller_async_read_file_data( +MfDesfireError mf_desfire_poller_read_file_data( MfDesfirePoller* instance, MfDesfireFileId id, uint32_t offset, @@ -327,7 +323,7 @@ MfDesfireError mf_desfire_poller_async_read_file_data( return error; } -MfDesfireError mf_desfire_poller_async_read_file_value( +MfDesfireError mf_desfire_poller_read_file_value( MfDesfirePoller* instance, MfDesfireFileId id, MfDesfireFileData* data) { @@ -352,7 +348,7 @@ MfDesfireError mf_desfire_poller_async_read_file_value( return error; } -MfDesfireError mf_desfire_poller_async_read_file_records( +MfDesfireError mf_desfire_poller_read_file_records( MfDesfirePoller* instance, MfDesfireFileId id, uint32_t offset, @@ -381,7 +377,7 @@ MfDesfireError mf_desfire_poller_async_read_file_records( return error; } -MfDesfireError mf_desfire_poller_async_read_file_data_multi( +MfDesfireError mf_desfire_poller_read_file_data_multi( MfDesfirePoller* instance, const SimpleArray* file_ids, const SimpleArray* file_settings, @@ -404,14 +400,14 @@ MfDesfireError mf_desfire_poller_async_read_file_data_multi( MfDesfireFileData* file_data = simple_array_get(data, i); if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { - error = mf_desfire_poller_async_read_file_data( + error = mf_desfire_poller_read_file_data( instance, file_id, 0, file_settings_cur->data.size, file_data); } else if(file_type == MfDesfireFileTypeValue) { - error = mf_desfire_poller_async_read_file_value(instance, file_id, file_data); + error = mf_desfire_poller_read_file_value(instance, file_id, file_data); } else if( file_type == MfDesfireFileTypeLinearRecord || file_type == MfDesfireFileTypeCyclicRecord) { - error = mf_desfire_poller_async_read_file_records( + error = mf_desfire_poller_read_file_records( instance, file_id, 0, file_settings_cur->data.size, file_data); } @@ -421,30 +417,29 @@ MfDesfireError mf_desfire_poller_async_read_file_data_multi( return error; } -MfDesfireError mf_desfire_poller_async_read_application( - MfDesfirePoller* instance, - MfDesfireApplication* data) { +MfDesfireError + mf_desfire_poller_read_application(MfDesfirePoller* instance, MfDesfireApplication* data) { furi_assert(instance); furi_assert(data); MfDesfireError error; do { - error = mf_desfire_poller_async_read_key_settings(instance, &data->key_settings); + error = mf_desfire_poller_read_key_settings(instance, &data->key_settings); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_key_versions( + error = mf_desfire_poller_read_key_versions( instance, data->key_versions, data->key_settings.max_keys); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_file_ids(instance, data->file_ids); + error = mf_desfire_poller_read_file_ids(instance, data->file_ids); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_file_settings_multi( + error = mf_desfire_poller_read_file_settings_multi( instance, data->file_ids, data->file_settings); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_file_data_multi( + error = mf_desfire_poller_read_file_data_multi( instance, data->file_ids, data->file_settings, data->file_data); if(error != MfDesfireErrorNone) break; @@ -453,7 +448,7 @@ MfDesfireError mf_desfire_poller_async_read_application( return error; } -MfDesfireError mf_desfire_poller_async_read_applications( +MfDesfireError mf_desfire_poller_read_applications( MfDesfirePoller* instance, const SimpleArray* app_ids, SimpleArray* data) { @@ -468,12 +463,11 @@ MfDesfireError mf_desfire_poller_async_read_applications( for(uint32_t i = 0; i < app_id_count; ++i) { do { - error = mf_desfire_poller_async_select_application( - instance, simple_array_cget(app_ids, i)); + error = mf_desfire_poller_select_application(instance, simple_array_cget(app_ids, i)); if(error != MfDesfireErrorNone) break; MfDesfireApplication* current_app = simple_array_get(data, i); - error = mf_desfire_poller_async_read_application(instance, current_app); + error = mf_desfire_poller_read_application(instance, current_app); } while(false); } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index abc48d0eb5..1c80af36fb 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -49,78 +49,6 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error); const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); -MfDesfireError mf_desfire_send_chunks( - MfDesfirePoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - -MfDesfireError - mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data); - -MfDesfireError - mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data); - -MfDesfireError mf_desfire_poller_async_read_key_settings( - MfDesfirePoller* instance, - MfDesfireKeySettings* data); - -MfDesfireError mf_desfire_poller_async_read_key_versions( - MfDesfirePoller* instance, - SimpleArray* data, - uint32_t count); - -MfDesfireError - mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data); - -MfDesfireError mf_desfire_poller_async_select_application( - MfDesfirePoller* instance, - const MfDesfireApplicationId* id); - -MfDesfireError mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data); - -MfDesfireError mf_desfire_poller_async_read_file_settings( - MfDesfirePoller* instance, - MfDesfireFileId id, - MfDesfireFileSettings* data); - -MfDesfireError mf_desfire_poller_async_read_file_settings_multi( - MfDesfirePoller* instance, - const SimpleArray* file_ids, - SimpleArray* data); - -MfDesfireError mf_desfire_poller_async_read_file_data( - MfDesfirePoller* instance, - MfDesfireFileId id, - uint32_t offset, - size_t size, - MfDesfireFileData* data); - -MfDesfireError mf_desfire_poller_async_read_file_value( - MfDesfirePoller* instance, - MfDesfireFileId id, - MfDesfireFileData* data); - -MfDesfireError mf_desfire_poller_async_read_file_records( - MfDesfirePoller* instance, - MfDesfireFileId id, - uint32_t offset, - size_t size, - MfDesfireFileData* data); - -MfDesfireError mf_desfire_poller_async_read_file_data_multi( - MfDesfirePoller* instance, - const SimpleArray* file_ids, - const SimpleArray* file_settings, - SimpleArray* data); - -MfDesfireError - mf_desfire_poller_async_read_application(MfDesfirePoller* instance, MfDesfireApplication* data); - -MfDesfireError mf_desfire_poller_async_read_applications( - MfDesfirePoller* instance, - const SimpleArray* app_ids, - SimpleArray* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 4ad7bc147d..86ab68c8b1 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -230,7 +230,7 @@ static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance } static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) { - instance->error = mf_ultralight_poller_async_read_version(instance, &instance->data->version); + instance->error = mf_ultralight_poller_read_version(instance, &instance->data->version); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Read version success"); instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); @@ -245,7 +245,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* } static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPoller* instance) { - instance->error = mf_ultralight_poller_async_authenticate(instance); + instance->error = mf_ultralight_poller_authenticate(instance); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Ultralight C detected"); instance->data->type = MfUltralightTypeMfulC; @@ -260,7 +260,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) { MfUltralightPageReadCommandData data = {}; - instance->error = mf_ultralight_poller_async_read_page(instance, 41, &data); + instance->error = mf_ultralight_poller_read_page(instance, 41, &data); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "NTAG203 detected"); instance->data->type = MfUltralightTypeNTAG203; @@ -294,7 +294,7 @@ static NfcCommand mf_ultralight_poller_handler_read_signature(MfUltralightPoller instance->feature_set, MfUltralightFeatureSupportReadSignature)) { FURI_LOG_D(TAG, "Reading signature"); instance->error = - mf_ultralight_poller_async_read_signature(instance, &instance->data->signature); + mf_ultralight_poller_read_signature(instance, &instance->data->signature); if(instance->error != MfUltralightErrorNone) { FURI_LOG_D(TAG, "Read signature failed"); next_state = MfUltralightPollerStateReadFailed; @@ -337,7 +337,7 @@ static NfcCommand mf_ultralight_poller_handler_read_counters(MfUltralightPoller* } FURI_LOG_D(TAG, "Reading counter %d", instance->counters_read); - instance->error = mf_ultralight_poller_async_read_counter( + instance->error = mf_ultralight_poller_read_counter( instance, instance->counters_read, &instance->data->counter[instance->counters_read]); if(instance->error != MfUltralightErrorNone) { FURI_LOG_D(TAG, "Failed to read %d counter", instance->counters_read); @@ -363,7 +363,7 @@ static NfcCommand mf_ultralight_poller_handler_read_tearing_flags(MfUltralightPo if(single_counter) instance->tearing_flag_read = 2; FURI_LOG_D(TAG, "Reading tearing flag %d", instance->tearing_flag_read); - instance->error = mf_ultralight_poller_async_read_tearing_flag( + instance->error = mf_ultralight_poller_read_tearing_flag( instance, instance->tearing_flag_read, &instance->data->tearing_flag[instance->tearing_flag_read]); @@ -396,8 +396,7 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance uint32_t pass = nfc_util_bytes2num( instance->auth_context.password.data, sizeof(MfUltralightAuthPassword)); FURI_LOG_D(TAG, "Trying to authenticate with password %08lX", pass); - instance->error = - mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Auth success"); instance->auth_context.auth_success = true; @@ -428,13 +427,13 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in if(mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( instance, start_page, §or, &tag, &pages_left)) { instance->error = - mf_ultralight_poller_async_read_page_from_sector(instance, sector, tag, &data); + mf_ultralight_poller_read_page_from_sector(instance, sector, tag, &data); } else { FURI_LOG_D(TAG, "Failed to calculate sector and tag from %d page", start_page); instance->error = MfUltralightErrorProtocol; } } else { - instance->error = mf_ultralight_poller_async_read_page(instance, start_page, &data); + instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } if(instance->error == MfUltralightErrorNone) { @@ -478,8 +477,7 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll MF_ULTRALIGHT_DEFAULT_PASSWORD, sizeof(MfUltralightAuthPassword), instance->auth_context.password.data); - instance->error = - mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Default password detected"); nfc_util_num2bytes( @@ -576,8 +574,7 @@ static bool mf_ultralight_poller_detect(NfcGenericEvent event, void* context) { if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { MfUltralightPageReadCommandData read_page_cmd_data = {}; - MfUltralightError error = - mf_ultralight_poller_async_read_page(instance, 0, &read_page_cmd_data); + MfUltralightError error = mf_ultralight_poller_read_page(instance, 0, &read_page_cmd_data); protocol_detected = (error == MfUltralightErrorNone); iso14443_3a_poller_halt(instance->iso14443_3a_poller); } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 2d4ef33ea9..665d90cb70 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -7,35 +7,181 @@ extern "C" { #endif +/** + * @brief MfUltralightPoller opaque type definition. + */ typedef struct MfUltralightPoller MfUltralightPoller; +/** + * @brief Enumeration of possible MfUltralight poller event types. + */ typedef enum { - MfUltralightPollerEventTypeAuthRequest, - MfUltralightPollerEventTypeAuthSuccess, - MfUltralightPollerEventTypeAuthFailed, - MfUltralightPollerEventTypeReadSuccess, - MfUltralightPollerEventTypeReadFailed, + MfUltralightPollerEventTypeAuthRequest, /**< Poller requests to fill authentication context. */ + MfUltralightPollerEventTypeAuthSuccess, /**< Authentication succeeded. */ + MfUltralightPollerEventTypeAuthFailed, /**< Authentication failed. */ + MfUltralightPollerEventTypeReadSuccess, /**< Poller read card successfully. */ + MfUltralightPollerEventTypeReadFailed, /**< Poller failed to read card. */ } MfUltralightPollerEventType; +/** + * @brief MfUltralight poller authentication context. + */ typedef struct { - MfUltralightAuthPassword password; - MfUltralightAuthPack pack; - bool auth_success; - bool skip_auth; + MfUltralightAuthPassword password; /**< Password to be used for authentication. */ + MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */ + bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */ + bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */ } MfUltralightPollerAuthContext; -typedef struct { - union { - MfUltralightPollerAuthContext auth_context; - MfUltralightError error; - }; +/** + * @brief MfUltralight poller event data. + */ +typedef union { + MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ + MfUltralightError error; /**< Error code indicating reading fail reason. */ } MfUltralightPollerEventData; +/** + * @brief MfUltralight poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - MfUltralightPollerEventType type; - MfUltralightPollerEventData* data; + MfUltralightPollerEventType type; /**< Type of emmitted event. */ + MfUltralightPollerEventData* data; /**< Pointer to event specific data. */ } MfUltralightPollerEvent; +/** + * @brief Perform authentication with password. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in, out] data pointer to the authentication context. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_auth_pwd( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data); + +/** + * @brief Start authentication procedure. + * + * Must ONLY be used inside the callback function. + * + * This function now is used only to identify Mf Ultralight C cards. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return MfUltralightErrorNone if card supports authentication command, an error code on otherwise. + */ +MfUltralightError mf_ultralight_poller_authenticate(MfUltralightPoller* instance); + +/** + * @brief Read page from card. + * + * Must ONLY be used inside the callback function. + * + * Send read command and parse response. The response on this command is data of 4 pages starting + * from the page specified in the command. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] start_page page number to be read. + * @param[out] data pointer to the MfUltralightPageReadCommandData structure to be filled with page data. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_page( + MfUltralightPoller* instance, + uint8_t start_page, + MfUltralightPageReadCommandData* data); + +/** + * @brief Read page from sector. + * + * Must ONLY be used inside the callback function. + * + * This command should be used for NTAGI2C tags. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] sector sector number to be read. + * @param[in] tag tag number to be read. + * @param[out] data pointer to the MfUltralightPageReadCommandData structure to be filled with page data. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_page_from_sector( + MfUltralightPoller* instance, + uint8_t sector, + uint8_t tag, + MfUltralightPageReadCommandData* data); + +/** + * @brief Write page to card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] page page number to be written. + * @param[in] data pointer to the MfUltralightPage structure to be written. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_write_page( + MfUltralightPoller* instance, + uint8_t page, + const MfUltralightPage* data); + +/** + * @brief Read version from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfUltralightVersion structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError + mf_ultralight_poller_read_version(MfUltralightPoller* instance, MfUltralightVersion* data); + +/** + * @brief Read signature from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfUltralightSignature structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError + mf_ultralight_poller_read_signature(MfUltralightPoller* instance, MfUltralightSignature* data); + +/** + * @brief Read counter from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] counter_num counter number to be read. + * @param[out] data pointer to the MfUltralightCounter structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_counter( + MfUltralightPoller* instance, + uint8_t counter_num, + MfUltralightCounter* data); + +/** + * @brief Read tearing flag from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tearing_falg_num tearing flag number to be read. + * @param[out] data pointer to the MfUltralightTearingFlag structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_tearing_flag( + MfUltralightPoller* instance, + uint8_t tearing_falg_num, + MfUltralightTearingFlag* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index 795b03e65f..2d88db3e59 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -30,7 +30,7 @@ MfUltralightError mf_ultralight_process_error(Iso14443_3aError error) { return ret; } -MfUltralightError mf_ultralight_poller_async_auth_pwd( +MfUltralightError mf_ultralight_poller_auth_pwd( MfUltralightPoller* instance, MfUltralightPollerAuthContext* data) { uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009 @@ -59,7 +59,7 @@ MfUltralightError mf_ultralight_poller_async_auth_pwd( return ret; } -MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance) { +MfUltralightError mf_ultralight_poller_authenticate(MfUltralightPoller* instance) { uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00}; bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd)); @@ -86,7 +86,7 @@ MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* in return ret; } -MfUltralightError mf_ultralight_poller_async_read_page_from_sector( +MfUltralightError mf_ultralight_poller_read_page_from_sector( MfUltralightPoller* instance, uint8_t sector, uint8_t tag, @@ -122,13 +122,13 @@ MfUltralightError mf_ultralight_poller_async_read_page_from_sector( break; } - ret = mf_ultralight_poller_async_read_page(instance, tag, data); + ret = mf_ultralight_poller_read_page(instance, tag, data); } while(false); return ret; } -MfUltralightError mf_ultralight_poller_async_read_page( +MfUltralightError mf_ultralight_poller_read_page( MfUltralightPoller* instance, uint8_t start_page, MfUltralightPageReadCommandData* data) { @@ -158,10 +158,10 @@ MfUltralightError mf_ultralight_poller_async_read_page( return ret; } -MfUltralightError mf_ultralight_poller_async_write_page( +MfUltralightError mf_ultralight_poller_write_page( MfUltralightPoller* instance, uint8_t page, - MfUltralightPage* data) { + const MfUltralightPage* data) { MfUltralightError ret = MfUltralightErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -191,9 +191,8 @@ MfUltralightError mf_ultralight_poller_async_write_page( return ret; } -MfUltralightError mf_ultralight_poller_async_read_version( - MfUltralightPoller* instance, - MfUltralightVersion* data) { +MfUltralightError + mf_ultralight_poller_read_version(MfUltralightPoller* instance, MfUltralightVersion* data) { MfUltralightError ret = MfUltralightErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -221,9 +220,8 @@ MfUltralightError mf_ultralight_poller_async_read_version( return ret; } -MfUltralightError mf_ultralight_poller_async_read_signature( - MfUltralightPoller* instance, - MfUltralightSignature* data) { +MfUltralightError + mf_ultralight_poller_read_signature(MfUltralightPoller* instance, MfUltralightSignature* data) { MfUltralightError ret = MfUltralightErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -249,7 +247,7 @@ MfUltralightError mf_ultralight_poller_async_read_signature( return ret; } -MfUltralightError mf_ultralight_poller_async_read_counter( +MfUltralightError mf_ultralight_poller_read_counter( MfUltralightPoller* instance, uint8_t counter_num, MfUltralightCounter* data) { @@ -278,7 +276,7 @@ MfUltralightError mf_ultralight_poller_async_read_counter( return ret; } -MfUltralightError mf_ultralight_poller_async_read_tearing_flag( +MfUltralightError mf_ultralight_poller_read_tearing_flag( MfUltralightPoller* instance, uint8_t tearing_falg_num, MfUltralightTearingFlag* data) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index 13490cf1a9..c89402b421 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -103,46 +103,6 @@ bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( uint8_t* tag, uint8_t* pages_left); -MfUltralightError mf_ultralight_poller_async_auth_pwd( - MfUltralightPoller* instance, - MfUltralightPollerAuthContext* data); - -MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance); - -MfUltralightError mf_ultralight_poller_async_read_page( - MfUltralightPoller* instance, - uint8_t start_page, - MfUltralightPageReadCommandData* data); - -MfUltralightError mf_ultralight_poller_async_read_page_from_sector( - MfUltralightPoller* instance, - uint8_t sector, - uint8_t tag, - MfUltralightPageReadCommandData* data); - -MfUltralightError mf_ultralight_poller_async_write_page( - MfUltralightPoller* instance, - uint8_t page, - MfUltralightPage* data); - -MfUltralightError mf_ultralight_poller_async_read_version( - MfUltralightPoller* instance, - MfUltralightVersion* data); - -MfUltralightError mf_ultralight_poller_async_read_signature( - MfUltralightPoller* instance, - MfUltralightSignature* data); - -MfUltralightError mf_ultralight_poller_async_read_counter( - MfUltralightPoller* instance, - uint8_t counter_num, - MfUltralightCounter* data); - -MfUltralightError mf_ultralight_poller_async_read_tearing_flag( - MfUltralightPoller* instance, - uint8_t tearing_falg_num, - MfUltralightTearingFlag* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c similarity index 83% rename from lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c rename to lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c index 739df597d2..c4833facf0 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c @@ -1,3 +1,4 @@ +#include "mf_ultralight_poller_sync.h" #include "mf_ultralight_poller_i.h" #include @@ -31,40 +32,39 @@ typedef MfUltralightError (*MfUltralightPollerCmdHandler)( MfUltralightError mf_ultralight_poller_read_page_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_page( - poller, data->read_cmd.start_page, &data->read_cmd.data); + return mf_ultralight_poller_read_page(poller, data->read_cmd.start_page, &data->read_cmd.data); } MfUltralightError mf_ultralight_poller_write_page_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_write_page( + return mf_ultralight_poller_write_page( poller, data->write_cmd.page_to_write, &data->write_cmd.page); } MfUltralightError mf_ultralight_poller_read_version_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_version(poller, &data->version); + return mf_ultralight_poller_read_version(poller, &data->version); } MfUltralightError mf_ultralight_poller_read_signature_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_signature(poller, &data->signature); + return mf_ultralight_poller_read_signature(poller, &data->signature); } MfUltralightError mf_ultralight_poller_read_counter_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_counter( + return mf_ultralight_poller_read_counter( poller, data->counter_cmd.counter_num, &data->counter_cmd.data); } MfUltralightError mf_ultralight_poller_read_tearing_flag_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_tearing_flag( + return mf_ultralight_poller_read_tearing_flag( poller, data->tearing_flag_cmd.tearing_flag_num, &data->tearing_flag_cmd.data); } @@ -79,16 +79,14 @@ static const MfUltralightPollerCmdHandler mf_ultralight_poller_read_tearing_flag_handler, }; -static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEvent event, void* context) { - furi_assert(event.instance); - furi_assert(event.protocol == NfcProtocolIso14443_3a); - furi_assert(event.event_data); +static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEventEx event, void* context) { + furi_assert(event.poller); + furi_assert(event.parent_event_data); furi_assert(context); MfUltralightPollerContext* poller_context = context; - Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; - Iso14443_3aPoller* iso14443_3a_poller = event.instance; - MfUltralightPoller* mfu_poller = mf_ultralight_poller_alloc(iso14443_3a_poller); + Iso14443_3aPollerEvent* iso14443_3a_event = event.parent_event_data; + MfUltralightPoller* mfu_poller = event.poller; if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { poller_context->error = mf_ultralight_poller_cmd_handlers[poller_context->cmd_type]( @@ -99,8 +97,6 @@ static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEvent event, void* furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); - mf_ultralight_poller_free(mfu_poller); - return NfcCommandStop; } @@ -110,8 +106,8 @@ static MfUltralightError poller_ctx->thread_id = furi_thread_get_current_id(); - NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); - nfc_poller_start(poller, mf_ultralight_poller_cmd_callback, poller_ctx); + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight); + nfc_poller_start_ex(poller, mf_ultralight_poller_cmd_callback, poller_ctx); furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); @@ -121,7 +117,8 @@ static MfUltralightError return poller_ctx->error; } -MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { +MfUltralightError + mf_ultralight_poller_sync_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { furi_assert(nfc); furi_assert(data); @@ -140,7 +137,7 @@ MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltr } MfUltralightError - mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { + mf_ultralight_poller_sync_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { furi_assert(nfc); furi_assert(data); @@ -158,7 +155,7 @@ MfUltralightError return error; } -MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data) { +MfUltralightError mf_ultralight_poller_sync_read_version(Nfc* nfc, MfUltralightVersion* data) { furi_assert(nfc); furi_assert(data); @@ -175,7 +172,7 @@ MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersio return error; } -MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data) { +MfUltralightError mf_ultralight_poller_sync_read_signature(Nfc* nfc, MfUltralightSignature* data) { furi_assert(nfc); furi_assert(data); @@ -192,8 +189,10 @@ MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSign return error; } -MfUltralightError - mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data) { +MfUltralightError mf_ultralight_poller_sync_read_counter( + Nfc* nfc, + uint8_t counter_num, + MfUltralightCounter* data) { furi_assert(nfc); furi_assert(data); @@ -211,7 +210,7 @@ MfUltralightError return error; } -MfUltralightError mf_ultralight_poller_read_tearing_flag( +MfUltralightError mf_ultralight_poller_sync_read_tearing_flag( Nfc* nfc, uint8_t flag_num, MfUltralightTearingFlag* data) { @@ -261,7 +260,7 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void return command; } -MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data) { +MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data) { furi_assert(nfc); furi_assert(data); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h new file mode 100644 index 0000000000..ac585aad7e --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h @@ -0,0 +1,34 @@ +#pragma once + +#include "mf_ultralight.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +MfUltralightError + mf_ultralight_poller_sync_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError + mf_ultralight_poller_sync_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_sync_read_version(Nfc* nfc, MfUltralightVersion* data); + +MfUltralightError mf_ultralight_poller_sync_read_signature(Nfc* nfc, MfUltralightSignature* data); + +MfUltralightError mf_ultralight_poller_sync_read_counter( + Nfc* nfc, + uint8_t counter_num, + MfUltralightCounter* data); + +MfUltralightError mf_ultralight_poller_sync_read_tearing_flag( + Nfc* nfc, + uint8_t flag_num, + MfUltralightTearingFlag* data); + +MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h deleted file mode 100644 index a0124ae092..0000000000 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "mf_ultralight.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); - -MfUltralightError mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); - -MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data); - -MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data); - -MfUltralightError - mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data); - -MfUltralightError mf_ultralight_poller_read_tearing_flag( - Nfc* nfc, - uint8_t flag_num, - MfUltralightTearingFlag* data); - -MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data); - -#ifdef __cplusplus -} -#endif diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index 55aa8a5895..ee63453335 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -61,9 +61,9 @@ * | | * +- protocol_name_listener_defs.h | * | - * +- protocol_name_sync_api.h | + * +- protocol_name_sync.h | * | |- add for synchronous API support - * +- protocol_name_sync_api.c | + * +- protocol_name_sync.c | * | * ``` * @@ -83,8 +83,8 @@ * | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. | * | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. | * | protocol_name_listener_defs.h | Declarations for use by the NfcListener library. See nfc_listener_base.h for more info. Optional, needed for emulation support. | - * | protocol_name_sync_api.h | Synchronous API declarations. (See below for sync API explanation). Optional.| - * | protocol_name_sync_api.c | Synchronous API implementation. Optional. | + * | protocol_name_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional.| + * | protocol_name_sync.c | Synchronous API implementation. Optional. | * * ## 3 Implement the code * @@ -145,7 +145,7 @@ * `protocol_name_poller_defs` structure under the appropriate index. * 5. (Optional) If emulation support was implemented, do the step 4, but for the listener. * 6. Add `protocol_name.h`, `protocol_name_poller.h`, and optionally, `protocol_name_listener.h` - * and `protocol_name_sync_api.h` into the `SDK_HEADERS` list in the SConscript file. + * and `protocol_name_sync.h` into the `SDK_HEADERS` list in the SConscript file. * This will export the protocol's types and functions for use by the applications. * 7. Done! * diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 9731bfc6b8..46a1711943 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -50,8 +50,7 @@ static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { } static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { - instance->error = - slix_poller_async_get_nxp_system_info(instance, &instance->data->system_info); + instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); if(instance->error == SlixErrorNone) { instance->poller_state = SlixPollerStateReadSignature; } else { @@ -62,7 +61,7 @@ static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) } static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { - instance->error = slix_poller_async_read_signature(instance, &instance->data->signature); + instance->error = slix_poller_read_signature(instance, &instance->data->signature); if(instance->error == SlixErrorNone) { instance->poller_state = SlixPollerStateReady; } else { @@ -141,7 +140,7 @@ static bool slix_poller_detect(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { if(slix_get_type(instance->data) < SlixTypeCount) { SlixSystemInfo system_info = {}; - SlixError error = slix_poller_async_get_nxp_system_info(instance, &system_info); + SlixError error = slix_poller_get_nxp_system_info(instance, &system_info); protocol_detected = (error == SlixErrorNone); } } diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index f4c7214de4..62d60be5fe 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -8,22 +8,78 @@ extern "C" { #endif +/** + * @brief SlixPoller opaque type definition. + */ typedef struct SlixPoller SlixPoller; +/** + * @brief Enumeration of possible Slix poller event types. + */ typedef enum { - SlixPollerEventTypeError, - SlixPollerEventTypeReady, + SlixPollerEventTypeError, /**< An error occured while reading card. */ + SlixPollerEventTypeReady, /**< The card was successfully read by the poller. */ } SlixPollerEventType; -typedef struct { - SlixError error; +/** + * @brief Slixs poller event data. + */ +typedef union { + SlixError error; /**< Error code indicating card reaing fail reason. */ } SlixPollerEventData; +/** + * @brief Slix poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - SlixPollerEventType type; - SlixPollerEventData* data; + SlixPollerEventType type; /**< Type of emmitted event. */ + SlixPollerEventData* data; /**< Pointer to event specific data. */ } SlixPollerEvent; +/** + * @brief Transmit and receive Slix frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_send_frame( + SlixPoller* instance, + const BitBuffer* tx_data, + BitBuffer* rx_data, + uint32_t fwt); + +/** + * @brief Send get nxp system info command and parse response. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SlixSystemInfo structure to be filled. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data); + +/** + * @brief Read signature from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SlixSignature structure to be filled. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index a36e7694a4..6d7bdf3779 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -32,7 +32,7 @@ SlixError slix_poller_send_frame( return slix_process_iso15693_3_error(iso15693_3_error); } -SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data) { +SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data) { furi_assert(instance); furi_assert(data); @@ -50,7 +50,7 @@ SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystem return error; } -SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data) { +SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) { furi_assert(instance); furi_assert(data); diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h index c6a8a3c33e..1fda1a7d24 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.h +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -33,16 +33,6 @@ struct SlixPoller { void* context; }; -SlixError slix_poller_send_frame( - SlixPoller* instance, - const BitBuffer* tx_data, - BitBuffer* rx_data, - uint32_t fwt); - -SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data); - -SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c index df659a2051..2bc5dd9410 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -71,7 +71,7 @@ static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != St25tbPollerStateActivated) { - St25tbError error = st25tb_poller_async_activate(instance, instance->data); + St25tbError error = st25tb_poller_activate(instance, instance->data); if(error == St25tbErrorNone) { instance->st25tb_event.type = St25tbPollerEventTypeReady; @@ -106,7 +106,7 @@ static bool st25tb_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == St25tbPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - St25tbError error = st25tb_poller_async_initiate(instance, NULL); + St25tbError error = st25tb_poller_initiate(instance, NULL); protocol_detected = (error == St25tbErrorNone); } diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.h b/lib/nfc/protocols/st25tb/st25tb_poller.h index a521b6d5b4..d3b85e3066 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller.h @@ -25,6 +25,23 @@ typedef struct { St25tbPollerEventData* data; } St25tbPollerEvent; +St25tbError st25tb_poller_send_frame( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id); + +St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data); + +St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid); + +St25tbError + st25tb_poller_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); + +St25tbError st25tb_poller_halt(St25tbPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.c b/lib/nfc/protocols/st25tb/st25tb_poller_i.c index bcbc69382a..76c9a8b1fa 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.c @@ -22,7 +22,7 @@ static St25tbError st25tb_poller_prepare_trx(St25tbPoller* instance) { furi_assert(instance); if(instance->state == St25tbPollerStateIdle) { - return st25tb_poller_async_activate(instance, NULL); + return st25tb_poller_activate(instance, NULL); } return St25tbErrorNone; @@ -85,7 +85,7 @@ St25tbType st25tb_get_type_from_uid(const uint8_t uid[ST25TB_UID_SIZE]) { } } -St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id) { +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id) { // Send Initiate() furi_assert(instance); furi_assert(instance->nfc); @@ -117,7 +117,7 @@ St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_i return ret; } -St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data) { +St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data) { furi_assert(instance); furi_assert(instance->nfc); @@ -126,7 +126,7 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat St25tbError ret; do { - ret = st25tb_poller_async_initiate(instance, &data->chip_id); + ret = st25tb_poller_initiate(instance, &data->chip_id); if(ret != St25tbErrorNone) { break; } @@ -162,7 +162,7 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat } instance->state = St25tbPollerStateActivated; - ret = st25tb_poller_async_get_uid(instance, data->uid); + ret = st25tb_poller_get_uid(instance, data->uid); if(ret != St25tbErrorNone) { instance->state = St25tbPollerStateActivationFailed; break; @@ -171,7 +171,7 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat bool read_blocks = true; for(uint8_t i = 0; i < st25tb_get_block_count(data->type); i++) { - ret = st25tb_poller_async_read_block(instance, &data->blocks[i], i); + ret = st25tb_poller_read_block(instance, &data->blocks[i], i); if(ret != St25tbErrorNone) { read_blocks = false; break; @@ -180,14 +180,13 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat if(!read_blocks) { break; } - ret = st25tb_poller_async_read_block( - instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + ret = st25tb_poller_read_block(instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); } while(false); return ret; } -St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]) { +St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid) { furi_assert(instance); furi_assert(instance->nfc); @@ -221,7 +220,7 @@ St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25 } St25tbError - st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number) { + st25tb_poller_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number) { furi_assert(instance); furi_assert(instance->nfc); furi_assert(block); diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.h b/lib/nfc/protocols/st25tb/st25tb_poller_i.h index 7f38f2d45b..27218d7b44 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.h @@ -34,23 +34,6 @@ struct St25tbPoller { const St25tbData* st25tb_poller_get_data(St25tbPoller* instance); -St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id); - -St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data); - -St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]); - -St25tbError - st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); - -St25tbError st25tb_poller_halt(St25tbPoller* instance); - -St25tbError st25tb_poller_send_frame( - St25tbPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt); - #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 0eadd799d4..44829c264d 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,44.0,, +Version,+,45.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -39,6 +39,8 @@ Header,+,applications/services/storage/storage.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, +Header,+,lib/drivers/st25r3916.h,, +Header,+,lib/drivers/st25r3916_reg.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -2045,6 +2047,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9834531953..514f332868 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,44.0,, +Version,+,45.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -40,6 +40,8 @@ Header,+,applications/services/storage/storage.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, +Header,+,lib/drivers/st25r3916.h,, +Header,+,lib/drivers/st25r3916_reg.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -117,7 +119,7 @@ Header,+,lib/nfc/nfc_scanner.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, -Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h,, Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b.h,, Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h,, Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a.h,, @@ -128,13 +130,13 @@ Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, -Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, -Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, Header,+,lib/nfc/protocols/slix/slix.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, @@ -1861,9 +1863,13 @@ Function,+,iso14443_3a_get_sak,uint8_t,const Iso14443_3aData* Function,+,iso14443_3a_get_uid,const uint8_t*,"const Iso14443_3aData*, size_t*" Function,+,iso14443_3a_is_equal,_Bool,"const Iso14443_3aData*, const Iso14443_3aData*" Function,+,iso14443_3a_load,_Bool,"Iso14443_3aData*, FlipperFormat*, uint32_t" -Function,+,iso14443_3a_poller_read,Iso14443_3aError,"Nfc*, Iso14443_3aData*" +Function,+,iso14443_3a_poller_activate,Iso14443_3aError,"Iso14443_3aPoller*, Iso14443_3aData*" +Function,+,iso14443_3a_poller_check_presence,Iso14443_3aError,Iso14443_3aPoller* +Function,+,iso14443_3a_poller_halt,Iso14443_3aError,Iso14443_3aPoller* Function,+,iso14443_3a_poller_send_standard_frame,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_poller_sync_read,Iso14443_3aError,"Nfc*, Iso14443_3aData*" Function,+,iso14443_3a_poller_txrx,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_poller_txrx_custom_parity,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,iso14443_3a_reset,void,Iso14443_3aData* Function,+,iso14443_3a_save,_Bool,"const Iso14443_3aData*, FlipperFormat*" Function,+,iso14443_3a_set_atqa,void,"Iso14443_3aData*, const uint8_t[2]" @@ -1882,6 +1888,9 @@ Function,+,iso14443_3b_get_fwt_fc_max,uint32_t,const Iso14443_3bData* Function,+,iso14443_3b_get_uid,const uint8_t*,"const Iso14443_3bData*, size_t*" Function,+,iso14443_3b_is_equal,_Bool,"const Iso14443_3bData*, const Iso14443_3bData*" Function,+,iso14443_3b_load,_Bool,"Iso14443_3bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_3b_poller_activate,Iso14443_3bError,"Iso14443_3bPoller*, Iso14443_3bData*" +Function,+,iso14443_3b_poller_halt,Iso14443_3bError,Iso14443_3bPoller* +Function,+,iso14443_3b_poller_send_frame,Iso14443_3bError,"Iso14443_3bPoller*, const BitBuffer*, BitBuffer*" Function,+,iso14443_3b_reset,void,Iso14443_3bData* Function,+,iso14443_3b_save,_Bool,"const Iso14443_3bData*, FlipperFormat*" Function,+,iso14443_3b_set_uid,_Bool,"Iso14443_3bData*, const uint8_t*, size_t" @@ -1900,6 +1909,9 @@ Function,+,iso14443_4a_get_historical_bytes,const uint8_t*,"const Iso14443_4aDat Function,+,iso14443_4a_get_uid,const uint8_t*,"const Iso14443_4aData*, size_t*" Function,+,iso14443_4a_is_equal,_Bool,"const Iso14443_4aData*, const Iso14443_4aData*" Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* +Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" +Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" Function,+,iso14443_4a_reset,void,Iso14443_4aData* Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" @@ -1914,6 +1926,8 @@ Function,+,iso14443_4b_get_device_name,const char*,"const Iso14443_4bData*, NfcD Function,+,iso14443_4b_get_uid,const uint8_t*,"const Iso14443_4bData*, size_t*" Function,+,iso14443_4b_is_equal,_Bool,"const Iso14443_4bData*, const Iso14443_4bData*" Function,+,iso14443_4b_load,_Bool,"Iso14443_4bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4b_poller_halt,Iso14443_4bError,Iso14443_4bPoller* +Function,+,iso14443_4b_poller_send_block,Iso14443_4bError,"Iso14443_4bPoller*, const BitBuffer*, BitBuffer*" Function,+,iso14443_4b_reset,void,Iso14443_4bData* Function,+,iso14443_4b_save,_Bool,"const Iso14443_4bData*, FlipperFormat*" Function,+,iso14443_4b_set_uid,_Bool,"Iso14443_4bData*, const uint8_t*, size_t" @@ -2141,14 +2155,21 @@ Function,+,mf_classic_is_sector_read,_Bool,"const MfClassicData*, uint8_t" Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" -Function,+,mf_classic_poller_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" -Function,+,mf_classic_poller_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" -Function,+,mf_classic_poller_detect_type,MfClassicError,"Nfc*, MfClassicType*" -Function,+,mf_classic_poller_read,MfClassicError,"Nfc*, const MfClassicDeviceKeys*, MfClassicData*" -Function,+,mf_classic_poller_read_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" -Function,+,mf_classic_poller_read_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t*" -Function,+,mf_classic_poller_write_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* +Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_poller_sync_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_sync_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" +Function,+,mf_classic_poller_sync_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_sync_detect_type,MfClassicError,"Nfc*, MfClassicType*" +Function,+,mf_classic_poller_sync_read,MfClassicError,"Nfc*, const MfClassicDeviceKeys*, MfClassicData*" +Function,+,mf_classic_poller_sync_read_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_sync_read_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t*" +Function,+,mf_classic_poller_sync_write_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_value_cmd,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicValueCommand, int32_t" +Function,+,mf_classic_poller_value_transfer,MfClassicError,"MfClassicPoller*, uint8_t" +Function,+,mf_classic_poller_write_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" Function,+,mf_classic_reset,void,MfClassicData* Function,+,mf_classic_save,_Bool,"const MfClassicData*, FlipperFormat*" Function,+,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlock*" @@ -2168,8 +2189,24 @@ Function,+,mf_desfire_get_file_settings,const MfDesfireFileSettings*,"const MfDe Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*" Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*" Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t" +Function,+,mf_desfire_poller_read_application,MfDesfireError,"MfDesfirePoller*, MfDesfireApplication*" +Function,+,mf_desfire_poller_read_application_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*" +Function,+,mf_desfire_poller_read_applications,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_data,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, uint32_t, size_t, MfDesfireFileData*" +Function,+,mf_desfire_poller_read_file_data_multi,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, const SimpleArray*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_records,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, uint32_t, size_t, MfDesfireFileData*" +Function,+,mf_desfire_poller_read_file_settings,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, MfDesfireFileSettings*" +Function,+,mf_desfire_poller_read_file_settings_multi,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_value,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, MfDesfireFileData*" +Function,+,mf_desfire_poller_read_free_memory,MfDesfireError,"MfDesfirePoller*, MfDesfireFreeMemory*" +Function,+,mf_desfire_poller_read_key_settings,MfDesfireError,"MfDesfirePoller*, MfDesfireKeySettings*" +Function,+,mf_desfire_poller_read_key_versions,MfDesfireError,"MfDesfirePoller*, SimpleArray*, uint32_t" +Function,+,mf_desfire_poller_read_version,MfDesfireError,"MfDesfirePoller*, MfDesfireVersion*" +Function,+,mf_desfire_poller_select_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*" Function,+,mf_desfire_reset,void,MfDesfireData* Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" +Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" Function,+,mf_ultralight_alloc,MfUltralightData*, @@ -2190,13 +2227,22 @@ Function,+,mf_ultralight_is_counter_configured,_Bool,const MfUltralightData* Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltralightData*" Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" -Function,+,mf_ultralight_poller_read_card,MfUltralightError,"Nfc*, MfUltralightData*" -Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" -Function,+,mf_ultralight_poller_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" -Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" -Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"Nfc*, uint8_t, MfUltralightTearingFlag*" -Function,+,mf_ultralight_poller_read_version,MfUltralightError,"Nfc*, MfUltralightVersion*" -Function,+,mf_ultralight_poller_write_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_auth_pwd,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" +Function,+,mf_ultralight_poller_authenticate,MfUltralightError,MfUltralightPoller* +Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightCounter*" +Function,+,mf_ultralight_poller_read_page,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightPageReadCommandData*" +Function,+,mf_ultralight_poller_read_page_from_sector,MfUltralightError,"MfUltralightPoller*, uint8_t, uint8_t, MfUltralightPageReadCommandData*" +Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"MfUltralightPoller*, MfUltralightSignature*" +Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightTearingFlag*" +Function,+,mf_ultralight_poller_read_version,MfUltralightError,"MfUltralightPoller*, MfUltralightVersion*" +Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*" +Function,+,mf_ultralight_poller_sync_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" +Function,+,mf_ultralight_poller_sync_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_sync_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" +Function,+,mf_ultralight_poller_sync_read_tearing_flag,MfUltralightError,"Nfc*, uint8_t, MfUltralightTearingFlag*" +Function,+,mf_ultralight_poller_sync_read_version,MfUltralightError,"Nfc*, MfUltralightVersion*" +Function,+,mf_ultralight_poller_sync_write_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_write_page,MfUltralightError,"MfUltralightPoller*, uint8_t, const MfUltralightPage*" Function,+,mf_ultralight_reset,void,MfUltralightData* Function,+,mf_ultralight_save,_Bool,"const MfUltralightData*, FlipperFormat*" Function,+,mf_ultralight_set_uid,_Bool,"MfUltralightData*, const uint8_t*, size_t" @@ -2290,6 +2336,7 @@ Function,+,nfc_poller_free,void,NfcPoller* Function,+,nfc_poller_get_data,const NfcDeviceData*,const NfcPoller* Function,+,nfc_poller_get_protocol,NfcProtocol,const NfcPoller* Function,+,nfc_poller_start,void,"NfcPoller*, NfcGenericCallback, void*" +Function,+,nfc_poller_start_ex,void,"NfcPoller*, NfcGenericCallbackEx, void*" Function,+,nfc_poller_stop,void,NfcPoller* Function,+,nfc_poller_trx,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_protocol_get_parent,NfcProtocol,NfcProtocol @@ -2608,6 +2655,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25tb_alloc,St25tbData*, Function,+,st25tb_copy,void,"St25tbData*, const St25tbData*" Function,+,st25tb_free,void,St25tbData* @@ -2617,6 +2687,12 @@ Function,+,st25tb_get_device_name,const char*,"const St25tbData*, NfcDeviceNameT Function,+,st25tb_get_uid,const uint8_t*,"const St25tbData*, size_t*" Function,+,st25tb_is_equal,_Bool,"const St25tbData*, const St25tbData*" Function,+,st25tb_load,_Bool,"St25tbData*, FlipperFormat*, uint32_t" +Function,+,st25tb_poller_activate,St25tbError,"St25tbPoller*, St25tbData*" +Function,+,st25tb_poller_get_uid,St25tbError,"St25tbPoller*, uint8_t*" +Function,+,st25tb_poller_halt,St25tbError,St25tbPoller* +Function,+,st25tb_poller_initiate,St25tbError,"St25tbPoller*, uint8_t*" +Function,+,st25tb_poller_read_block,St25tbError,"St25tbPoller*, uint32_t*, uint8_t" +Function,+,st25tb_poller_send_frame,St25tbError,"St25tbPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,st25tb_reset,void,St25tbData* Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" From 615a1479734ecf2e03626153f60dc267800cc429 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:45:41 +0300 Subject: [PATCH 043/111] [FL-3608] Fix iButton crash on missing file (#3210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/ibutton/ibutton.c | 13 ++++++------- applications/main/ibutton/ibutton_i.h | 2 +- .../main/ibutton/scenes/ibutton_scene_add_value.c | 2 +- .../main/ibutton/scenes/ibutton_scene_rpc.c | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index fb6d9dcb52..afd51f7c9e 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -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); } } diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index 077b148076..c6a35f888b 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -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); diff --git a/applications/main/ibutton/scenes/ibutton_scene_add_value.c b/applications/main/ibutton/scenes/ibutton_scene_add_value.c index dc340771b2..71b852115e 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_add_value.c +++ b/applications/main/ibutton/scenes/ibutton_scene_add_value.c @@ -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); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index 7106fefaa2..f4f193a47e 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -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); From ba074068b0c0c620b6ab13b88c8e294b9d3aaf8d Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:56:13 +0300 Subject: [PATCH 044/111] [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 --- .../storage/filesystem_api_internal.h | 8 + applications/services/storage/storage.h | 593 ++++++++++++------ .../services/storage/storage_external_api.c | 40 +- .../services/storage/storage_message.h | 9 + .../services/storage/storage_processing.c | 50 ++ .../services/storage/storages/storage_ext.c | 11 + .../services/storage/storages/storage_int.c | 5 + targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- 9 files changed, 507 insertions(+), 215 deletions(-) diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index 967d3bb41c..ba98b380e9 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -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 */ diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 1abc8ed0eb..3caa155c7d 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -1,4 +1,9 @@ +/** + * @file storage.h + * @brief APIs for working with storages, directories and files. + */ #pragma once + #include #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); -/** 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); -/** 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. + * + * 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 source file - * @param destination destination file - * @param size size of data to copy - * @return bool success flag + * @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); -/******************* 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 - * - * @param storage The storage instance - * @param path path to file/directory - * @param timestamp the timestamp pointer +/** + * @brief Get the last access time in UNIX format. * - * @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. + * + * Necessary special directories will be created automatically if they did not exist. * - * @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 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. + * + * Source folder will be deleted if the migration was successful. * - * @param storage - * @param source - * @param dest - * @return FS_Error + * @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 dstmane 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 dstmane 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. + * + * 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 - * @param dirname - * @param filename - * @param fileextension - * @param nextfilename return name - * @param max_len max len name + * @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, diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index ed69b49a57..1027d43101 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -431,17 +431,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, "/"); + // Cannot overwrite a file with a directory + if(storage_file_exists(storage, new_path)) { + error = FSE_INVALID_NAME; + break; } - const char* dir_path_s = furi_string_get_cstr(dir_path); - if(strncmp(new_path, dir_path_s, strlen(dir_path_s)) == 0) { + + // 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; - furi_string_free(dir_path); break; } - furi_string_free(dir_path); + + // 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 +747,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) { diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 01bc203801..cd45906d4a 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -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 { diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 00126af6f3..9e96765b62 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -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); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 4630d99ea7..7e617c0ff2 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -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, }, }; diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c index 2534d47a11..39b092c1d1 100644 --- a/applications/services/storage/storages/storage_int.c +++ b/applications/services/storage/storages/storage_int.c @@ -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, }, }; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 44829c264d..8ed8f403c7 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.0,, +Version,+,45.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2071,6 +2071,7 @@ Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_ Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 514f332868..64695c2dbc 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.0,, +Version,+,45.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2698,6 +2698,7 @@ Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" Function,+,st25tb_verify,_Bool,"St25tbData*, const FuriString*" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" From a61b5d4b4cd7f11f4b836ba085860f58689905b8 Mon Sep 17 00:00:00 2001 From: Flipper Zelebro <149575765+flipperzelebro@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:04:45 +0100 Subject: [PATCH 045/111] Add Mastercode SubGHz Protocol (#3187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: あく --- .../unit_tests/subghz/mastercode.sub | 7 + .../unit_tests/subghz/mastercode_raw.sub | 6 + .../debug/unit_tests/subghz/subghz_test.c | 15 + lib/subghz/protocols/mastercode.c | 360 ++++++++++++++++++ lib/subghz/protocols/mastercode.h | 109 ++++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 7 files changed, 499 insertions(+) create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub create mode 100644 lib/subghz/protocols/mastercode.c create mode 100644 lib/subghz/protocols/mastercode.h diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub new file mode 100644 index 0000000000..f50abbfe8f --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub @@ -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 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub new file mode 100644 index 0000000000..69d3f396cd --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub @@ -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 diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 64e0591dfa..2f2b9e9811 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -654,6 +654,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( @@ -805,6 +812,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"); } @@ -855,6 +868,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); @@ -881,6 +895,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(); diff --git a/lib/subghz/protocols/mastercode.c b/lib/subghz/protocols/mastercode.c new file mode 100644 index 0000000000..54ad5bfaa4 --- /dev/null +++ b/lib/subghz/protocols/mastercode.c @@ -0,0 +1,360 @@ +#include "mastercode.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +// protocol MASTERCODE Clemsa MV1/MV12 +#define TAG "SubGhzProtocolMastercode" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" + +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_mastercode_const = { + .te_short = 1072, + .te_long = 2145, + .te_delta = 150, + .min_count_bit_for_found = 36, +}; + +struct SubGhzProtocolDecoderMastercode { + SubGhzProtocolDecoderBase base; + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderMastercode { + SubGhzProtocolEncoderBase base; + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + MastercodeDecoderStepReset = 0, + MastercodeDecoderStepSaveDuration, + MastercodeDecoderStepCheckDuration, +} MastercodeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_mastercode_decoder = { + .alloc = subghz_protocol_decoder_mastercode_alloc, + .free = subghz_protocol_decoder_mastercode_free, + + .feed = subghz_protocol_decoder_mastercode_feed, + .reset = subghz_protocol_decoder_mastercode_reset, + + .get_hash_data = subghz_protocol_decoder_mastercode_get_hash_data, + .serialize = subghz_protocol_decoder_mastercode_serialize, + .deserialize = subghz_protocol_decoder_mastercode_deserialize, + .get_string = subghz_protocol_decoder_mastercode_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_mastercode_encoder = { + .alloc = subghz_protocol_encoder_mastercode_alloc, + .free = subghz_protocol_encoder_mastercode_free, + + .deserialize = subghz_protocol_encoder_mastercode_deserialize, + .stop = subghz_protocol_encoder_mastercode_stop, + .yield = subghz_protocol_encoder_mastercode_yield, +}; + +const SubGhzProtocol subghz_protocol_mastercode = { + .name = SUBGHZ_PROTOCOL_MASTERCODE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_mastercode_decoder, + .encoder = &subghz_protocol_mastercode_encoder, +}; + +void* subghz_protocol_encoder_mastercode_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderMastercode* instance = malloc(sizeof(SubGhzProtocolEncoderMastercode)); + + instance->base.protocol = &subghz_protocol_mastercode; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 72; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_mastercode_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderMastercode* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderMastercode instance + * @return true On success + */ +static bool + subghz_protocol_encoder_mastercode_get_upload(SubGhzProtocolEncoderMastercode* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_mastercode_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_mastercode_const.te_long); + } + } + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_mastercode_const.te_short + + subghz_protocol_mastercode_const.te_short * 13); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_mastercode_const.te_long + + subghz_protocol_mastercode_const.te_short * 13); + } + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderMastercode* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_mastercode_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_mastercode_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + instance->encoder.is_running = true; + + } while(false); + + return ret; +} + +void subghz_protocol_encoder_mastercode_stop(void* context) { + SubGhzProtocolEncoderMastercode* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_mastercode_yield(void* context) { + SubGhzProtocolEncoderMastercode* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_mastercode_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMastercode* instance = malloc(sizeof(SubGhzProtocolDecoderMastercode)); + instance->base.protocol = &subghz_protocol_mastercode; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_mastercode_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + free(instance); +} + +void subghz_protocol_decoder_mastercode_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + instance->decoder.parser_step = MastercodeDecoderStepReset; +} + +void subghz_protocol_decoder_mastercode_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + + switch(instance->decoder.parser_step) { + case MastercodeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_short * 15) < + subghz_protocol_mastercode_const.te_delta * 15)) { + instance->decoder.parser_step = MastercodeDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case MastercodeDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = MastercodeDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + break; + + case MastercodeDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_mastercode_const.te_short) < + subghz_protocol_mastercode_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_long) < + subghz_protocol_mastercode_const.te_delta * 8)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = MastercodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_mastercode_const.te_long) < + subghz_protocol_mastercode_const.te_delta * 8) && + (DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_short) < + subghz_protocol_mastercode_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = MastercodeDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_short * 15) < + subghz_protocol_mastercode_const.te_delta * 15) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_mastercode_const.te_short) < + subghz_protocol_mastercode_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if((DURATION_DIFF( + instance->decoder.te_last, + subghz_protocol_mastercode_const.te_long) < + subghz_protocol_mastercode_const.te_delta * 8)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + + if(instance->decoder.decode_count_bit == + subghz_protocol_mastercode_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.parser_step = MastercodeDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_mastercode_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = (instance->data >> 4) & 0xFFFF; + instance->btn = (instance->data >> 2 & 0x03); +} + +uint8_t subghz_protocol_decoder_mastercode_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_mastercode_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_mastercode_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_mastercode_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + subghz_protocol_mastercode_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%llX Btn %X\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN "\r\n" + " -: " DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint64_t)(instance->generic.data), + instance->generic.btn, + SHOW_DIP_P(instance->generic.serial, DIP_P), + SHOW_DIP_P(instance->generic.serial, DIP_O), + SHOW_DIP_P(instance->generic.serial, DIP_N)); +} diff --git a/lib/subghz/protocols/mastercode.h b/lib/subghz/protocols/mastercode.h new file mode 100644 index 0000000000..c5c73db989 --- /dev/null +++ b/lib/subghz/protocols/mastercode.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MASTERCODE_NAME "Mastercode" + +typedef struct SubGhzProtocolDecoderMastercode SubGhzProtocolDecoderMastercode; +typedef struct SubGhzProtocolEncoderMastercode SubGhzProtocolEncoderMastercode; + +extern const SubGhzProtocolDecoder subghz_protocol_mastercode_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_mastercode_encoder; +extern const SubGhzProtocol subghz_protocol_mastercode; + +/** + * Allocate SubGhzProtocolEncoderMastercode. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMastercode* pointer to a SubGhzProtocolEncoderMastercode instance + */ +void* subghz_protocol_encoder_mastercode_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMastercode. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + */ +void subghz_protocol_encoder_mastercode_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + */ +void subghz_protocol_encoder_mastercode_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_mastercode_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMastercode. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMastercode* pointer to a SubGhzProtocolDecoderMastercode instance + */ +void* subghz_protocol_decoder_mastercode_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + */ +void subghz_protocol_decoder_mastercode_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + */ +void subghz_protocol_decoder_mastercode_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_mastercode_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_mastercode_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_mastercode_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param output Resulting text + */ +void subghz_protocol_decoder_mastercode_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 74244c5ff4..472a354e38 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -43,6 +43,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k, &subghz_protocol_bin_raw, + &subghz_protocol_mastercode, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index f1a28ac9b5..c5a090e993 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -44,3 +44,4 @@ #include "alutech_at_4n.h" #include "kinggates_stylo_4k.h" #include "bin_raw.h" +#include "mastercode.h" From 457aa5331fe8cbd72d2a17586ff903390f779fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 16 Nov 2023 01:11:05 +0900 Subject: [PATCH 046/111] 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 --- furi/core/timer.c | 11 +--- furi/core/timer.h | 13 +++++ targets/f7/furi_hal/furi_hal_flash.c | 2 +- targets/f7/furi_hal/furi_hal_power.c | 33 ++++++++++-- targets/f7/furi_hal/furi_hal_subghz.c | 78 +++++++++++++++++---------- 5 files changed, 95 insertions(+), 42 deletions(-) diff --git a/furi/core/timer.c b/furi/core/timer.c index 17347e5c7d..027e4cf40d 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -122,17 +122,10 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { furi_assert(instance); TimerHandle_t hTimer = (TimerHandle_t)instance; - FuriStatus stat; - if(xTimerIsTimerActive(hTimer) == pdFALSE) { - stat = FuriStatusErrorResource; - } else { - furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - stat = FuriStatusOk; - } + furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - /* Return execution status */ - return (stat); + return FuriStatusOk; } uint32_t furi_timer_is_running(FuriTimer* instance) { diff --git a/furi/core/timer.h b/furi/core/timer.h index d27ef5025e..f8f40c5626 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -32,6 +32,9 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co void furi_timer_free(FuriTimer* instance); /** Start timer + * + * @warning This is asynchronous call, real operation will happen as soon as + * timer service process this request. * * @param instance The pointer to FuriTimer instance * @param[in] ticks The interval in ticks @@ -41,6 +44,9 @@ void furi_timer_free(FuriTimer* instance); FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); /** Restart timer with previous timeout value + * + * @warning This is asynchronous call, real operation will happen as soon as + * timer service process this request. * * @param instance The pointer to FuriTimer instance * @param[in] ticks The interval in ticks @@ -50,6 +56,9 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); /** Stop timer + * + * @warning This is asynchronous call, real operation will happen as soon as + * timer service process this request. * * @param instance The pointer to FuriTimer instance * @@ -58,6 +67,10 @@ FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); FuriStatus furi_timer_stop(FuriTimer* instance); /** Is timer running + * + * @warning This cal may and will return obsolete timer state if timer + * commands are still in the queue. Please read FreeRTOS timer + * documentation first. * * @param instance The pointer to FuriTimer instance * diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 284d48bf53..7ac7a8bd12 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -101,7 +101,7 @@ void furi_hal_flash_init() { // WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR); /* Actually, reset all error flags on start */ if(READ_BIT(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS)) { - FURI_LOG_E(TAG, "FLASH->SR 0x%08lX", FLASH->SR); + FURI_LOG_W(TAG, "FLASH->SR 0x%08lX(Known ERRATA)", FLASH->SR); WRITE_REG(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS); } } diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index 119dee81f9..9e3a70da73 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -71,12 +71,37 @@ void furi_hal_power_init() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); // Find and init gauge - if(bq27220_init(&furi_hal_i2c_handle_power)) { - furi_hal_power.gauge_ok = bq27220_apply_data_memory( - &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); + size_t retry = 2; + while(retry > 0) { + furi_hal_power.gauge_ok = bq27220_init(&furi_hal_i2c_handle_power); + if(furi_hal_power.gauge_ok) { + furi_hal_power.gauge_ok = bq27220_apply_data_memory( + &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); + } + if(furi_hal_power.gauge_ok) { + break; + } else { + // Normal startup time is 250ms + // But if we try to access gauge at that stage it will become unresponsive + // 2 seconds timeout needed to restart communication + furi_delay_us(2020202); + } + retry--; } // Find and init charger - furi_hal_power.charger_ok = bq25896_init(&furi_hal_i2c_handle_power); + retry = 2; + while(retry > 0) { + furi_hal_power.charger_ok = bq25896_init(&furi_hal_i2c_handle_power); + if(furi_hal_power.charger_ok) { + break; + } else { + // Most likely I2C communication error + // 2 seconds should be enough for all chips on the line to timeout + // Also timing out here is very abnormal + furi_delay_us(2020202); + } + retry--; + } furi_hal_i2c_release(&furi_hal_i2c_handle_power); FURI_LOG_I(TAG, "Init OK"); diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index f751463532..a00ca7bf6d 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,7 @@ static uint32_t furi_hal_subghz_debug_gpio_buff[2]; /** SubGhz state */ typedef enum { SubGhzStateInit, /**< Init pending */ - + SubGhzStateBroken, /**< Chip power-on self test failed */ SubGhzStateIdle, /**< Idle, energy save mode */ SubGhzStateAsyncRx, /**< Async RX started */ @@ -69,46 +70,67 @@ const GpioPin* furi_hal_subghz_get_data_gpio() { void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); - furi_hal_subghz.state = SubGhzStateIdle; + furi_hal_subghz.state = SubGhzStateBroken; furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - + do { #ifdef FURI_HAL_SUBGHZ_TX_GPIO - furi_hal_gpio_init(&FURI_HAL_SUBGHZ_TX_GPIO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + &FURI_HAL_SUBGHZ_TX_GPIO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); #endif - // Reset - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + // Reset + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_reset(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); - // Prepare GD0 for power on self test - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + // Prepare GD0 for power on self test + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - // GD0 low - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); - while(furi_hal_gpio_read(&gpio_cc1101_g0) != false) - ; + // GD0 low + FuriHalCortexTimer timeout = furi_hal_cortex_timer_get(10000); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != false && + !furi_hal_cortex_timer_is_expired(timeout)) + ; - // GD0 high - cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); - while(furi_hal_gpio_read(&gpio_cc1101_g0) != true) - ; + if(furi_hal_gpio_read(&gpio_cc1101_g0) != false) { + break; + } - // Reset GD0 to floating state - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + // GD0 high + timeout = furi_hal_cortex_timer_get(10000); + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != true && + !furi_hal_cortex_timer_is_expired(timeout)) + ; + + if(furi_hal_gpio_read(&gpio_cc1101_g0) != true) { + break; + } - // RF switches - furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); + // Reset GD0 to floating state + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - // Go to sleep - cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); + // RF switches + furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); + + // Go to sleep + cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); + + furi_hal_subghz.state = SubGhzStateIdle; + } while(false); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - FURI_LOG_I(TAG, "Init OK"); + + if(furi_hal_subghz.state == SubGhzStateIdle) { + FURI_LOG_I(TAG, "Init OK"); + } else { + FURI_LOG_E(TAG, "Init Fail"); + } } void furi_hal_subghz_sleep() { From 98d5718ec9e39824745a2ca364f4d06a5a05c828 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 15 Nov 2023 20:27:35 +0400 Subject: [PATCH 047/111] fbt: improvements (#3217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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: あく --- SConstruct | 36 +++-- applications/debug/accessor/application.fam | 1 - .../debug/battery_test_app/application.fam | 1 - applications/debug/blink_test/application.fam | 1 - applications/debug/ccid_test/application.fam | 1 - applications/debug/crash_test/application.fam | 1 - .../debug/display_test/application.fam | 1 - .../debug/file_browser_test/application.fam | 1 - .../debug/keypad_test/application.fam | 1 - .../debug/locale_test/application.fam | 1 - .../debug/text_box_test/application.fam | 1 - applications/debug/uart_echo/application.fam | 1 - applications/debug/usb_mouse/application.fam | 1 - applications/debug/usb_test/application.fam | 1 - applications/debug/vibro_test/application.fam | 1 - documentation/AppManifests.md | 2 +- firmware.scons | 4 +- scripts/fbt/appmanifest.py | 129 ++++-------------- scripts/fbt_tools/fbt_apps.py | 103 +++++++++++++- scripts/fbt_tools/fbt_extapps.py | 21 ++- scripts/fbt_tools/fbt_sdk.py | 15 +- scripts/fbt_tools/fbt_version.py | 20 ++- scripts/fbt_tools/fwbin.py | 19 ++- scripts/fbt_tools/jflash.py | 13 +- scripts/fbt_tools/objdump.py | 3 +- scripts/fbt_tools/openocd.py | 1 + scripts/fbt_tools/pvsstudio.py | 24 +++- scripts/fbt_tools/strip.py | 3 +- scripts/ufbt/SConstruct | 24 +++- scripts/version.py | 2 +- site_scons/environ.scons | 4 +- 31 files changed, 271 insertions(+), 166 deletions(-) diff --git a/SConstruct b/SConstruct index 97d7e5e5e2..a2c5cd9e7a 100644 --- a/SConstruct +++ b/SConstruct @@ -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,13 +278,13 @@ 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"]], ) @@ -323,10 +325,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 +367,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 $)", ) diff --git a/applications/debug/accessor/application.fam b/applications/debug/accessor/application.fam index 6b84727112..65a6c86663 100644 --- a/applications/debug/accessor/application.fam +++ b/applications/debug/accessor/application.fam @@ -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, diff --git a/applications/debug/battery_test_app/application.fam b/applications/debug/battery_test_app/application.fam index f97d102791..5f4acd83d5 100644 --- a/applications/debug/battery_test_app/application.fam +++ b/applications/debug/battery_test_app/application.fam @@ -3,7 +3,6 @@ App( name="Battery Test", apptype=FlipperAppType.DEBUG, entry_point="battery_test_app", - cdefines=["APP_BATTERY_TEST"], requires=[ "gui", "power", diff --git a/applications/debug/blink_test/application.fam b/applications/debug/blink_test/application.fam index c6a8a922a4..d7d873fb9a 100644 --- a/applications/debug/blink_test/application.fam +++ b/applications/debug/blink_test/application.fam @@ -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, diff --git a/applications/debug/ccid_test/application.fam b/applications/debug/ccid_test/application.fam index e0cbc8d85e..ad90767708 100644 --- a/applications/debug/ccid_test/application.fam +++ b/applications/debug/ccid_test/application.fam @@ -3,7 +3,6 @@ App( name="CCID Debug", apptype=FlipperAppType.DEBUG, entry_point="ccid_test_app", - cdefines=["CCID_TEST"], requires=[ "gui", ], diff --git a/applications/debug/crash_test/application.fam b/applications/debug/crash_test/application.fam index 55f62f86d8..357efe667a 100644 --- a/applications/debug/crash_test/application.fam +++ b/applications/debug/crash_test/application.fam @@ -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", diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam index e8a00d2ae1..6a2d9c20c1 100644 --- a/applications/debug/display_test/application.fam +++ b/applications/debug/display_test/application.fam @@ -3,7 +3,6 @@ App( name="Display Test", apptype=FlipperAppType.DEBUG, entry_point="display_test_app", - cdefines=["APP_DISPLAY_TEST"], requires=["gui"], fap_libs=["misc"], stack_size=1 * 1024, diff --git a/applications/debug/file_browser_test/application.fam b/applications/debug/file_browser_test/application.fam index 4a401a649d..bb08ad2c55 100644 --- a/applications/debug/file_browser_test/application.fam +++ b/applications/debug/file_browser_test/application.fam @@ -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, diff --git a/applications/debug/keypad_test/application.fam b/applications/debug/keypad_test/application.fam index 6859af26f6..90851950b2 100644 --- a/applications/debug/keypad_test/application.fam +++ b/applications/debug/keypad_test/application.fam @@ -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, diff --git a/applications/debug/locale_test/application.fam b/applications/debug/locale_test/application.fam index e46eeff51c..d341122f99 100644 --- a/applications/debug/locale_test/application.fam +++ b/applications/debug/locale_test/application.fam @@ -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, diff --git a/applications/debug/text_box_test/application.fam b/applications/debug/text_box_test/application.fam index 3e54df9cc5..823c21d068 100644 --- a/applications/debug/text_box_test/application.fam +++ b/applications/debug/text_box_test/application.fam @@ -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, diff --git a/applications/debug/uart_echo/application.fam b/applications/debug/uart_echo/application.fam index 8863a1a942..7b030bcfa6 100644 --- a/applications/debug/uart_echo/application.fam +++ b/applications/debug/uart_echo/application.fam @@ -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, diff --git a/applications/debug/usb_mouse/application.fam b/applications/debug/usb_mouse/application.fam index 5c43400451..7747613d58 100644 --- a/applications/debug/usb_mouse/application.fam +++ b/applications/debug/usb_mouse/application.fam @@ -3,7 +3,6 @@ App( name="USB Mouse Demo", apptype=FlipperAppType.DEBUG, entry_point="usb_mouse_app", - cdefines=["APP_USB_MOUSE"], requires=["gui"], stack_size=1 * 1024, order=60, diff --git a/applications/debug/usb_test/application.fam b/applications/debug/usb_test/application.fam index 27395c34d4..463bb4a26e 100644 --- a/applications/debug/usb_test/application.fam +++ b/applications/debug/usb_test/application.fam @@ -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, diff --git a/applications/debug/vibro_test/application.fam b/applications/debug/vibro_test/application.fam index f7115cc962..c35a7223f8 100644 --- a/applications/debug/vibro_test/application.fam +++ b/applications/debug/vibro_test/application.fam @@ -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, diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index d190a798ba..9afdccb0e4 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -32,7 +32,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt - **name**: name displayed in menus. - **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. - **flags**: internal flags for system apps. Do not use. -- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. +- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. **For external applications**: specified definitions are used when building the application itself. - **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. - **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. - **provides**: functionally identical to **_requires_** field. diff --git a/firmware.scons b/firmware.scons index eca6afc4c7..004def9a99 100644 --- a/firmware.scons +++ b/firmware.scons @@ -219,7 +219,7 @@ AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction( fwelf, Action( - '${PYTHON3} "${BIN_SIZE_SCRIPT}" elf ${TARGET}', + [["${PYTHON3}", "${BIN_SIZE_SCRIPT}", "elf", "${TARGET}"]], "Firmware size", ), ) @@ -229,7 +229,7 @@ fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}") AddPostAction( fwbin, - Action('@${PYTHON3} "${BIN_SIZE_SCRIPT}" bin ${TARGET}'), + Action([["@${PYTHON3}", "${BIN_SIZE_SCRIPT}", "bin", "${TARGET}"]]), ) fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}") diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index bef4eb02b1..d32869b106 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -33,6 +33,8 @@ class FlipperAppType(Enum): @dataclass class FlipperApplication: APP_ID_REGEX: ClassVar[re.Pattern] = re.compile(r"^[a-z0-9_]+$") + PRIVATE_FIELD_PREFIX: ClassVar[str] = "_" + APP_MANIFEST_DEFAULT_NAME: ClassVar[str] = "application.fam" @dataclass class ExternallyBuiltFile: @@ -48,8 +50,6 @@ class Library: cdefines: List[str] = field(default_factory=list) cincludes: List[str] = field(default_factory=list) - PRIVATE_FIELD_PREFIX = "_" - appid: str apptype: FlipperAppType name: Optional[str] = "" @@ -117,8 +117,10 @@ def __post_init__(self): self.fap_version = tuple(int(v) for v in self.fap_version.split(".")) except ValueError: raise FlipperManifestException( - f"Invalid version string '{self.fap_version}'. Must be in the form 'major.minor'" + f"Invalid version '{self.fap_version}'. Must be in the form 'major.minor'" ) + if len(self.fap_version) < 2: + raise ValueError("Not enough version components") class AppManager: @@ -155,11 +157,20 @@ def _validate_app_params(self, *args, **kw): raise FlipperManifestException( f"App {kw.get('appid')} cannot have fal_embedded set" ) - # Harmless - cdefines for external apps are meaningless - # if apptype == FlipperAppType.EXTERNAL and kw.get("cdefines"): - # raise FlipperManifestException( - # f"External app {kw.get('appid')} must not have 'cdefines' in manifest" - # ) + + if apptype in AppBuildset.dist_app_types: + # For distributing .fap's resources, there's "fap_file_assets" + for app_property in ("resources",): + if kw.get(app_property): + raise FlipperManifestException( + f"App {kw.get('appid')} of type {apptype} cannot have '{app_property}' in manifest" + ) + else: + for app_property in ("fap_extbuild", "fap_private_libs", "fap_icon_assets"): + if kw.get(app_property): + raise FlipperManifestException( + f"App {kw.get('appid')} of type {apptype} must not have '{app_property}' in manifest" + ) def load_manifest(self, app_manifest_path: str, app_dir_node: object): if not os.path.exists(app_manifest_path): @@ -241,12 +252,21 @@ class AppBuildset: FlipperAppType.STARTUP, ) EXTERNAL_APP_TYPES_MAP = { + # AppType -> bool: true if always deploy, false if obey app set FlipperAppType.EXTERNAL: True, FlipperAppType.PLUGIN: True, FlipperAppType.DEBUG: True, FlipperAppType.MENUEXTERNAL: False, } + @classmethod + @property + def dist_app_types(cls): + """Applications that are installed on SD card""" + return list( + entry[0] for entry in cls.EXTERNAL_APP_TYPES_MAP.items() if entry[1] + ) + @staticmethod def print_writer(message): print(message) @@ -432,96 +452,3 @@ def get_builtin_app_folders(self): for source_type in app.sources ) ) - - -class ApplicationsCGenerator: - APP_TYPE_MAP = { - FlipperAppType.SERVICE: ("FlipperInternalApplication", "FLIPPER_SERVICES"), - FlipperAppType.SYSTEM: ("FlipperInternalApplication", "FLIPPER_SYSTEM_APPS"), - FlipperAppType.APP: ("FlipperInternalApplication", "FLIPPER_APPS"), - FlipperAppType.DEBUG: ("FlipperInternalApplication", "FLIPPER_DEBUG_APPS"), - FlipperAppType.SETTINGS: ( - "FlipperInternalApplication", - "FLIPPER_SETTINGS_APPS", - ), - FlipperAppType.STARTUP: ( - "FlipperInternalOnStartHook", - "FLIPPER_ON_SYSTEM_START", - ), - } - - APP_EXTERNAL_TYPE = ( - "FlipperExternalApplication", - "FLIPPER_EXTERNAL_APPS", - ) - - def __init__(self, buildset: AppBuildset, autorun_app: str = ""): - self.buildset = buildset - self.autorun = autorun_app - - def get_app_ep_forward(self, app: FlipperApplication): - if app.apptype == FlipperAppType.STARTUP: - return f"extern void {app.entry_point}();" - return f"extern int32_t {app.entry_point}(void* p);" - - def get_app_descr(self, app: FlipperApplication): - if app.apptype == FlipperAppType.STARTUP: - return app.entry_point - return f""" - {{.app = {app.entry_point}, - .name = "{app.name}", - .appid = "{app.appid}", - .stack_size = {app.stack_size}, - .icon = {f"&{app.icon}" if app.icon else "NULL"}, - .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" - - def get_external_app_descr(self, app: FlipperApplication): - app_path = "/ext/apps" - if app.fap_category: - app_path += f"/{app.fap_category}" - app_path += f"/{app.appid}.fap" - return f""" - {{ - .name = "{app.name}", - .icon = {f"&{app.icon}" if app.icon else "NULL"}, - .path = "{app_path}" }}""" - - def generate(self): - contents = [ - '#include "applications.h"', - "#include ", - f'const char* FLIPPER_AUTORUN_APP_NAME = "{self.autorun}";', - ] - for apptype in self.APP_TYPE_MAP: - contents.extend( - map(self.get_app_ep_forward, self.buildset.get_apps_of_type(apptype)) - ) - entry_type, entry_block = self.APP_TYPE_MAP[apptype] - contents.append(f"const {entry_type} {entry_block}[] = {{") - contents.append( - ",\n".join( - map(self.get_app_descr, self.buildset.get_apps_of_type(apptype)) - ) - ) - contents.append("};") - contents.append( - f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});" - ) - - archive_app = self.buildset.get_apps_of_type(FlipperAppType.ARCHIVE) - if archive_app: - contents.extend( - [ - self.get_app_ep_forward(archive_app[0]), - f"const FlipperInternalApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", - ] - ) - - entry_type, entry_block = self.APP_EXTERNAL_TYPE - external_apps = self.buildset.get_apps_of_type(FlipperAppType.MENUEXTERNAL) - contents.append(f"const {entry_type} {entry_block}[] = {{") - contents.append(",\n".join(map(self.get_external_app_descr, external_apps))) - contents.append("};") - contents.append(f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});") - - return "\n".join(contents) diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index edce194f07..dadf6dc0c8 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -1,25 +1,118 @@ from ansi.color import fg from fbt.appmanifest import ( - ApplicationsCGenerator, AppManager, + AppBuildset, + FlipperApplication, FlipperAppType, FlipperManifestException, ) from SCons.Action import Action from SCons.Builder import Builder from SCons.Errors import StopError -from SCons.Warnings import WarningOnByDefault, warn from SCons.Script import GetOption +from SCons.Warnings import WarningOnByDefault, warn # Adding objects for application management to env # AppManager env["APPMGR"] - loads all manifests; manages list of known apps # AppBuildset env["APPBUILD"] - contains subset of apps, filtered for current config +class ApplicationsCGenerator: + APP_TYPE_MAP = { + FlipperAppType.SERVICE: ("FlipperInternalApplication", "FLIPPER_SERVICES"), + FlipperAppType.SYSTEM: ("FlipperInternalApplication", "FLIPPER_SYSTEM_APPS"), + FlipperAppType.APP: ("FlipperInternalApplication", "FLIPPER_APPS"), + FlipperAppType.DEBUG: ("FlipperInternalApplication", "FLIPPER_DEBUG_APPS"), + FlipperAppType.SETTINGS: ( + "FlipperInternalApplication", + "FLIPPER_SETTINGS_APPS", + ), + FlipperAppType.STARTUP: ( + "FlipperInternalOnStartHook", + "FLIPPER_ON_SYSTEM_START", + ), + } + + APP_EXTERNAL_TYPE = ( + "FlipperExternalApplication", + "FLIPPER_EXTERNAL_APPS", + ) + + def __init__(self, buildset: AppBuildset, autorun_app: str = ""): + self.buildset = buildset + self.autorun = autorun_app + + def get_app_ep_forward(self, app: FlipperApplication): + if app.apptype == FlipperAppType.STARTUP: + return f"extern void {app.entry_point}();" + return f"extern int32_t {app.entry_point}(void* p);" + + def get_app_descr(self, app: FlipperApplication): + if app.apptype == FlipperAppType.STARTUP: + return app.entry_point + return f""" + {{.app = {app.entry_point}, + .name = "{app.name}", + .appid = "{app.appid}", + .stack_size = {app.stack_size}, + .icon = {f"&{app.icon}" if app.icon else "NULL"}, + .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" + + def get_external_app_descr(self, app: FlipperApplication): + app_path = "/ext/apps" + if app.fap_category: + app_path += f"/{app.fap_category}" + app_path += f"/{app.appid}.fap" + return f""" + {{ + .name = "{app.name}", + .icon = {f"&{app.icon}" if app.icon else "NULL"}, + .path = "{app_path}" }}""" + + def generate(self): + contents = [ + '#include "applications.h"', + "#include ", + f'const char* FLIPPER_AUTORUN_APP_NAME = "{self.autorun}";', + ] + for apptype in self.APP_TYPE_MAP: + contents.extend( + map(self.get_app_ep_forward, self.buildset.get_apps_of_type(apptype)) + ) + entry_type, entry_block = self.APP_TYPE_MAP[apptype] + contents.append(f"const {entry_type} {entry_block}[] = {{") + contents.append( + ",\n".join( + map(self.get_app_descr, self.buildset.get_apps_of_type(apptype)) + ) + ) + contents.append("};") + contents.append( + f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});" + ) + + archive_app = self.buildset.get_apps_of_type(FlipperAppType.ARCHIVE) + if archive_app: + contents.extend( + [ + self.get_app_ep_forward(archive_app[0]), + f"const FlipperInternalApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", + ] + ) + + entry_type, entry_block = self.APP_EXTERNAL_TYPE + external_apps = self.buildset.get_apps_of_type(FlipperAppType.MENUEXTERNAL) + contents.append(f"const {entry_type} {entry_block}[] = {{") + contents.append(",\n".join(map(self.get_external_app_descr, external_apps))) + contents.append("};") + contents.append(f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});") + + return "\n".join(contents) + + def LoadAppManifest(env, entry): try: - APP_MANIFEST_NAME = "application.fam" - manifest_glob = entry.glob(APP_MANIFEST_NAME) + manifest_glob = entry.glob(FlipperApplication.APP_MANIFEST_DEFAULT_NAME) if len(manifest_glob) == 0: try: disk_node = next(filter(lambda d: d.exists(), entry.get_all_rdirs())) @@ -27,7 +120,7 @@ def LoadAppManifest(env, entry): disk_node = entry raise FlipperManifestException( - f"App folder '{disk_node.abspath}': missing manifest ({APP_MANIFEST_NAME})" + f"App folder '{disk_node.abspath}': missing manifest ({FlipperApplication.APP_MANIFEST_DEFAULT_NAME})" ) app_manifest_file_path = manifest_glob[0].rfile().abspath diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index b88fa79291..a7914c4f87 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -58,7 +58,8 @@ def _setup_app_env(self): ) self.app_env.Append( CPPDEFINES=[ - ("FAP_VERSION", f'"{".".join(map(str, self.app.fap_version))}"') + ("FAP_VERSION", f'\\"{".".join(map(str, self.app.fap_version))}\\"'), + *self.app.cdefines, ], ) self.app_env.VariantDir(self.app_work_dir, self.app._appdir, duplicate=False) @@ -143,8 +144,8 @@ def _build_app(self): self.app._assets_dirs = [self.app._appdir.Dir(self.app.fap_file_assets)] self.app_env.Append( - LIBS=[*self.app.fap_libs, *self.private_libs], - CPPPATH=[self.app_work_dir, self.app._appdir], + LIBS=[*self.app.fap_libs, *self.private_libs, *self.app.fap_libs], + CPPPATH=[self.app_env.Dir(self.app_work_dir), self.app._appdir], ) app_sources = self.app_env.GatherSources( @@ -472,7 +473,19 @@ def AddAppLaunchTarget(env, appname, launch_target_name): components = _gather_app_components(env, appname) target = env.PhonyTarget( launch_target_name, - '${PYTHON3} "${APP_RUN_SCRIPT}" -p ${FLIP_PORT} ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + [ + [ + "${PYTHON3}", + "${APP_RUN_SCRIPT}", + "-p", + "${FLIP_PORT}", + "${EXTRA_ARGS}", + "-s", + "${SOURCES}", + "-t", + "${FLIPPER_FILE_TARGETS}", + ] + ], source=components.deploy_sources.values(), FLIPPER_FILE_TARGETS=components.deploy_sources.keys(), EXTRA_ARGS=components.extra_launch_args, diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 6350f14b8b..17acc8cf1a 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -285,7 +285,20 @@ def generate(env, **kw): "$SDK_AMALGAMATE_HEADER_COMSTR", ), Action( - "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", + [ + [ + "$CC", + "-o", + "$TARGET", + "-E", + "-P", + "$CCFLAGS", + "$_CCCOMCOM", + "$SDK_PP_FLAGS", + "-MMD", + "${TARGET}.c", + ] + ], "$SDK_AMALGAMATE_PP_COMSTR", ), ], diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index f1a782523f..e64167b3dc 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -19,10 +19,22 @@ def generate(env): BUILDERS={ "VersionBuilder": Builder( action=Action( - '${PYTHON3} "${VERSION_SCRIPT}" generate ' - "-t ${TARGET_HW} -fw-origin ${FIRMWARE_ORIGIN} " - '-o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', - "${VERSIONCOMSTR}", + [ + [ + "${PYTHON3}", + "${VERSION_SCRIPT}", + "generate", + "-t", + "${TARGET_HW}", + "--fw-origin", + "${FIRMWARE_ORIGIN}", + "-o", + "${TARGET.dir.posix}", + "--dir", + "${ROOT_DIR}", + "${VERSIONCOMSTR}", + ] + ] ), emitter=_version_emitter, ), diff --git a/scripts/fbt_tools/fwbin.py b/scripts/fbt_tools/fwbin.py index 06a435b6db..860f83b1b9 100644 --- a/scripts/fbt_tools/fwbin.py +++ b/scripts/fbt_tools/fwbin.py @@ -25,7 +25,7 @@ def generate(env): BUILDERS={ "HEXBuilder": Builder( action=Action( - '${OBJCOPY} -O ihex "${SOURCE}" "${TARGET}"', + [["${OBJCOPY}", "-O", "ihex", "${SOURCE}", "${TARGET}"]], "${HEXCOMSTR}", ), suffix=".hex", @@ -33,7 +33,7 @@ def generate(env): ), "BINBuilder": Builder( action=Action( - '${OBJCOPY} -O binary -S "${SOURCE}" "${TARGET}"', + [["${OBJCOPY}", "-O", "binary", "-S", "${SOURCE}", "${TARGET}"]], "${BINCOMSTR}", ), suffix=".bin", @@ -41,7 +41,20 @@ def generate(env): ), "DFUBuilder": Builder( action=Action( - '${PYTHON3} "${BIN2DFU}" -i "${SOURCE}" -o "${TARGET}" -a ${IMAGE_BASE_ADDRESS} -l "Flipper Zero F${TARGET_HW}"', + [ + [ + "${PYTHON3}", + "${BIN2DFU}", + "-i", + "${SOURCE}", + "-o", + "${TARGET}", + "-a", + "${IMAGE_BASE_ADDRESS}", + "-l", + "Flipper Zero F${TARGET_HW}", + ] + ], "${DFUCOMSTR}", ), suffix=".dfu", diff --git a/scripts/fbt_tools/jflash.py b/scripts/fbt_tools/jflash.py index aea7279b63..5eb9f2c193 100644 --- a/scripts/fbt_tools/jflash.py +++ b/scripts/fbt_tools/jflash.py @@ -1,5 +1,6 @@ from SCons.Builder import Builder from SCons.Defaults import Touch +from SCons.Action import Action def generate(env): @@ -9,13 +10,21 @@ def generate(env): "-auto", "-exit", ], - JFLASHCOM="${JFLASH} -openprj${JFLASHPROJECT} -open${SOURCE},${JFLASHADDR} ${JFLASHFLAGS}", ) env.Append( BUILDERS={ "JFlash": Builder( action=[ - "${JFLASHCOM}", + Action( + [ + [ + "${JFLASH}", + "-openprj${JFLASHPROJECT}", + "-open${SOURCE},${JFLASHADDR}", + "${JFLASHFLAGS}", + ] + ] + ), Touch("${TARGET}"), ], ), diff --git a/scripts/fbt_tools/objdump.py b/scripts/fbt_tools/objdump.py index 31f8176484..e3dbc6d875 100644 --- a/scripts/fbt_tools/objdump.py +++ b/scripts/fbt_tools/objdump.py @@ -6,13 +6,12 @@ def generate(env): env.SetDefault( OBJDUMP="objdump", OBJDUMPFLAGS=[], - OBJDUMPCOM="$OBJDUMP $OBJDUMPFLAGS -S $SOURCES > $TARGET", ) env.Append( BUILDERS={ "ObjDump": Builder( action=Action( - "${OBJDUMPCOM}", + [["$OBJDUMP", "$OBJDUMPFLAGS", "-S", "$SOURCES", ">", "$TARGET"]], "${OBJDUMPCOMSTR}", ), suffix=".lst", diff --git a/scripts/fbt_tools/openocd.py b/scripts/fbt_tools/openocd.py index 157d798f40..596f5f8a64 100644 --- a/scripts/fbt_tools/openocd.py +++ b/scripts/fbt_tools/openocd.py @@ -5,6 +5,7 @@ __OPENOCD_BIN = "openocd" +# TODO: FL-3663: rework argument passing to lists _oocd_action = Action( "${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", "${OPENOCDCOMSTR}", diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index f43db126e3..ecf9d4b068 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -79,7 +79,17 @@ def generate(env): BUILDERS={ "PVSCheck": Builder( action=Action( - '${PVSCHECKBIN} analyze ${PVSOPTIONS} -f "${SOURCE}" -o "${TARGET}"', + [ + [ + "${PVSCHECKBIN}", + "analyze", + "${PVSOPTIONS}", + "-f", + "${SOURCE}", + "-o", + "${TARGET}", + ] + ], "${PVSCHECKCOMSTR}", ), suffix=".log", @@ -92,7 +102,17 @@ def generate(env): # PlogConverter.exe and plog-converter have different behavior Mkdir("${TARGET.dir}") if env["PLATFORM"] == "win32" else None, Action(_set_browser_action, None), - '${PVSCONVBIN} ${PVSCONVOPTIONS} "${SOURCE}" -o "${REPORT_DIR}"', + Action( + [ + [ + "${PVSCONVBIN}", + "${PVSCONVOPTIONS}", + "${SOURCE}", + "-o", + "${REPORT_DIR}", + ] + ] + ), ], "${PVSCONVCOMSTR}", ), diff --git a/scripts/fbt_tools/strip.py b/scripts/fbt_tools/strip.py index ee14fc185a..39f3a620cf 100644 --- a/scripts/fbt_tools/strip.py +++ b/scripts/fbt_tools/strip.py @@ -6,13 +6,12 @@ def generate(env): env.SetDefault( STRIP="strip", STRIPFLAGS=[], - STRIPCOM="$STRIP $STRIPFLAGS $SOURCES -o $TARGET", ) env.Append( BUILDERS={ "ELFStripper": Builder( action=Action( - "${STRIPCOM}", + [["$STRIP", "$STRIPFLAGS", "$SOURCES", "-o", "$TARGET"]], "${STRIPCOMSTR}", ), suffix=".elf", diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 98e6b638f3..46d6635788 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -325,24 +325,26 @@ else: appenv.PhonyTarget( "cli", - '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py" -p ${FLIP_PORT}', + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]], ) # Update WiFi devboard firmware -dist_env.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py") +dist_env.PhonyTarget( + "devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]] +) # Linter dist_env.PhonyTarget( "lint", - "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]], source=original_app_dir.File(".clang-format"), LINT_SOURCES=[original_app_dir], ) dist_env.PhonyTarget( "format", - "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]], source=original_app_dir.File(".clang-format"), LINT_SOURCES=[original_app_dir], ) @@ -455,7 +457,17 @@ if dolphin_src_dir.exists(): ) dist_env.PhonyTarget( "dolphin_ext", - '${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send "${SOURCE}" /ext/dolphin', + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/storage.py", + "-p", + "${FLIP_PORT}", + "send", + "${SOURCE}", + "/ext/dolphin", + ] + ], source=ufbt_build_dir.Dir("dolphin"), ) else: @@ -467,7 +479,7 @@ else: dist_env.PhonyTarget( "env", - "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)", + "@echo $( ${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh $)", ) dist_env.PostConfigureUfbtEnvionment() diff --git a/scripts/version.py b/scripts/version.py index 4b1c739bc5..98b1b7e855 100755 --- a/scripts/version.py +++ b/scripts/version.py @@ -101,7 +101,7 @@ def init(self): required=True, ) self.parser_generate.add_argument( - "-fw-origin", + "--fw-origin", dest="firmware_origin", type=str, help="firmware origin", diff --git a/site_scons/environ.scons b/site_scons/environ.scons index b638b10185..74762cb15b 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -27,6 +27,8 @@ variables_to_forward = [ "PYTHONNOUSERSITE", "TMP", "TEMP", + # ccache + "CCACHE_DISABLE", # Colors for tools "TERM", ] @@ -62,7 +64,7 @@ coreenv = VAR_ENV.Clone( # Setting up temp file parameters - to overcome command line length limits TEMPFILEARGESCFUNC=tempfile_arg_esc_func, ROOT_DIR=Dir("#"), - FBT_SCRIPT_DIR="${ROOT_DIR}/scripts", + FBT_SCRIPT_DIR=Dir("#/scripts"), ) # If DIST_SUFFIX is set in environment, is has precedence (set by CI) From 4b3e8aba294bf94be4ac7a73873202e3078afb2c Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 15 Nov 2023 19:39:27 +0300 Subject: [PATCH 048/111] [FL-3664] 64k does not enough (#3216) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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: あく --- .../debug/unit_tests/furi/furi_record_test.c | 27 +++++--- applications/debug/unit_tests/minunit.h | 5 ++ .../debug/unit_tests/storage/dirwalk_test.c | 2 +- .../debug/unit_tests/storage/storage_test.c | 65 +++++++++++++++++++ .../debug/unit_tests/subghz/subghz_test.c | 1 + applications/debug/unit_tests/test_index.c | 10 +++ .../main/archive/helpers/archive_favorites.c | 4 +- applications/main/subghz/subghz_cli.c | 2 +- .../services/notification/notification_app.c | 4 +- applications/services/rpc/rpc_storage.c | 2 +- applications/services/storage/storage.h | 6 +- applications/services/storage/storage_cli.c | 22 +++---- .../services/storage/storage_external_api.c | 41 +++++++++++- .../scenes/storage_settings_scene_benchmark.c | 4 +- .../updater/util/update_task_worker_flasher.c | 2 +- lib/flipper_application/elf/elf_file.c | 2 +- lib/music_worker/music_worker.c | 2 +- lib/toolbox/crc32_calc.c | 2 +- lib/toolbox/md5_calc.c | 4 +- lib/toolbox/saved_struct.c | 6 +- lib/toolbox/stream/file_stream.c | 24 +------ lib/update_util/dfu_file.c | 6 +- lib/update_util/update_operation.c | 2 +- targets/f18/api_symbols.csv | 10 +-- targets/f7/api_symbols.csv | 10 +-- 25 files changed, 186 insertions(+), 79 deletions(-) diff --git a/applications/debug/unit_tests/furi/furi_record_test.c b/applications/debug/unit_tests/furi/furi_record_test.c index 512ddfdc4a..236e1efc56 100644 --- a/applications/debug/unit_tests/furi/furi_record_test.c +++ b/applications/debug/unit_tests/furi/furi_record_test.c @@ -3,18 +3,29 @@ #include #include "../minunit.h" +#define TEST_RECORD_NAME "test/holding" + void test_furi_create_open() { - // 1. Create record + // 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/holding", (void*)&test_data); + furi_record_create(TEST_RECORD_NAME, (void*)&test_data); - // 2. Open it - void* record = furi_record_open("test/holding"); + // 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); + + // Clean up + furi_record_destroy(TEST_RECORD_NAME); - // 4. Clean up - furi_record_destroy("test/holding"); + // Test that record does not exist + mu_check(furi_record_exists(TEST_RECORD_NAME) == false); } diff --git a/applications/debug/unit_tests/minunit.h b/applications/debug/unit_tests/minunit.h index 69bfba6d92..083db5a9a9 100644 --- a/applications/debug/unit_tests/minunit.h +++ b/applications/debug/unit_tests/minunit.h @@ -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( \ diff --git a/applications/debug/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/storage/dirwalk_test.c index e0842a7a43..19ac336fff 100644 --- a/applications/debug/unit_tests/storage/dirwalk_test.c +++ b/applications/debug/unit_tests/storage/dirwalk_test.c @@ -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); diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 13188e5e0f..5ea36935b1 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -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); diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 2f2b9e9811..60c7abd032 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -326,6 +326,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; } diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 7c1b6b4447..d7afaa3c4f 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -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); diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index f395ee5a11..682aa6b383 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -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) { diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 0a7b521273..e1b5e86841 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -946,7 +946,7 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { static bool subghz_on_system_start_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { File* file = istream->state; - uint16_t ret = storage_file_read(file, buf, count); + size_t ret = storage_file_read(file, buf, count); return (count == ret); } diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 5769ced926..9baa738b79 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -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; diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index ee024b823a..913d89f1af 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -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); } diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 3caa155c7d..20a371fc08 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -123,7 +123,7 @@ bool storage_file_is_dir(File* file); * @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); /** * @brief Write bytes from a buffer to a file. @@ -133,7 +133,7 @@ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read); * @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); /** * @brief Change the current access position in a file. @@ -207,7 +207,7 @@ bool storage_file_exists(Storage* storage, const char* path); * @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); /******************* Directory Functions *******************/ diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 59489d459c..2927022a3f 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -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)); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 1027d43101..666090346a 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -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) { diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index 8359c00be3..a5bf1b9d37 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -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}; diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index c560319928..1b4b079003 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -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) { diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index b2c9445ffe..8a78cca413 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -101,7 +101,7 @@ static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, FuriString* buffer[ELF_NAME_BUFFER_LEN] = 0; while(true) { - uint16_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN); + size_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN); furi_string_cat(name, buffer); if(strlen(buffer) < ELF_NAME_BUFFER_LEN) { result = true; diff --git a/lib/music_worker/music_worker.c b/lib/music_worker/music_worker.c index 61fc838f2e..279d126737 100644 --- a/lib/music_worker/music_worker.c +++ b/lib/music_worker/music_worker.c @@ -396,7 +396,7 @@ bool music_worker_load_rtttl_from_file(MusicWorker* instance, const char* file_p break; }; - uint16_t ret = 0; + size_t ret = 0; do { uint8_t buffer[65] = {0}; ret = storage_file_read(file, buffer, sizeof(buffer) - 1); diff --git a/lib/toolbox/crc32_calc.c b/lib/toolbox/crc32_calc.c index c0cd169b18..78295167f3 100644 --- a/lib/toolbox/crc32_calc.c +++ b/lib/toolbox/crc32_calc.c @@ -14,7 +14,7 @@ uint32_t crc32_calc_file(File* file, const FileCrcProgressCb progress_cb, void* uint32_t file_crc = 0; uint8_t* data_buffer = malloc(CRC_DATA_BUFFER_MAX_LEN); - uint16_t data_buffer_valid_len; + size_t data_buffer_valid_len; uint32_t file_size = storage_file_size(file); diff --git a/lib/toolbox/md5_calc.c b/lib/toolbox/md5_calc.c index b050295a14..7f335a33f2 100644 --- a/lib/toolbox/md5_calc.c +++ b/lib/toolbox/md5_calc.c @@ -5,13 +5,13 @@ bool md5_calc_file(File* file, const char* path, unsigned char output[16], FS_Er bool result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); if(result) { - const uint16_t size_to_read = 512; + const size_t size_to_read = 512; uint8_t* data = malloc(size_to_read); md5_context* md5_ctx = malloc(sizeof(md5_context)); md5_starts(md5_ctx); while(true) { - uint16_t read_size = storage_file_read(file, data, size_to_read); + size_t read_size = storage_file_read(file, data, size_to_read); if(read_size == 0) break; md5_update(md5_ctx, data, read_size); } diff --git a/lib/toolbox/saved_struct.c b/lib/toolbox/saved_struct.c index 02b73f2104..2f1c09c8eb 100644 --- a/lib/toolbox/saved_struct.c +++ b/lib/toolbox/saved_struct.c @@ -46,7 +46,7 @@ bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, header.flags = 0; header.timestamp = 0; - uint16_t bytes_count = storage_file_write(file, &header, sizeof(header)); + size_t bytes_count = storage_file_write(file, &header, sizeof(header)); bytes_count += storage_file_write(file, data, size); if(bytes_count != (size + sizeof(header))) { @@ -79,7 +79,7 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, } if(result) { - uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + size_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); bytes_count += storage_file_read(file, data_read, size); if(bytes_count != (sizeof(SavedStructHeader) + size)) { @@ -146,7 +146,7 @@ bool saved_struct_get_payload_size( break; } - uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + size_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); if(bytes_count != sizeof(SavedStructHeader)) { FURI_LOG_E(TAG, "Failed to read header"); break; diff --git a/lib/toolbox/stream/file_stream.c b/lib/toolbox/stream/file_stream.c index 095dce472c..2b5348b3e8 100644 --- a/lib/toolbox/stream/file_stream.c +++ b/lib/toolbox/stream/file_stream.c @@ -134,31 +134,11 @@ static size_t file_stream_size(FileStream* stream) { } static size_t file_stream_write(FileStream* stream, const uint8_t* data, size_t size) { - // TODO FL-3545: cache - size_t need_to_write = size; - while(need_to_write > 0) { - uint16_t was_written = - storage_file_write(stream->file, data + (size - need_to_write), need_to_write); - need_to_write -= was_written; - - if(was_written == 0) break; - } - - return size - need_to_write; + return storage_file_write(stream->file, data, size); } static size_t file_stream_read(FileStream* stream, uint8_t* data, size_t size) { - // TODO FL-3545: cache - size_t need_to_read = size; - while(need_to_read > 0) { - uint16_t was_read = - storage_file_read(stream->file, data + (size - need_to_read), need_to_read); - need_to_read -= was_read; - - if(was_read == 0) break; - } - - return size - need_to_read; + return storage_file_read(stream->file, data, size); } static bool file_stream_delete_and_insert( diff --git a/lib/update_util/dfu_file.c b/lib/update_util/dfu_file.c index eef9f06459..85b661e8e0 100644 --- a/lib/update_util/dfu_file.c +++ b/lib/update_util/dfu_file.c @@ -22,7 +22,7 @@ uint8_t dfu_file_validate_headers(File* dfuf, const DfuValidationParams* referen DfuPrefix dfu_prefix = {0}; DfuSuffix dfu_suffix = {0}; - uint16_t bytes_read = 0; + size_t bytes_read = 0; if(!storage_file_is_open(dfuf) || !storage_file_seek(dfuf, 0, true)) { return 0; @@ -90,7 +90,7 @@ static DfuUpdateBlockResult dfu_file_perform_task_for_update_pages( } 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 < header->dwElementSize) { @@ -125,7 +125,7 @@ static DfuUpdateBlockResult dfu_file_perform_task_for_update_pages( bool dfu_file_process_targets(const DfuUpdateTask* task, File* dfuf, const uint8_t n_targets) { TargetPrefix target_prefix = {0}; ImageElementHeader image_element = {0}; - uint16_t bytes_read = 0; + size_t bytes_read = 0; if(!storage_file_seek(dfuf, sizeof(DfuPrefix), true)) { return UpdateBlockResult_Failed; diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 0cecfc016a..39a7ea0752 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -85,7 +85,7 @@ bool update_operation_get_current_package_manifest_path(Storage* storage, FuriSt upd_file, UPDATE_FILE_POINTER_FN, FSAM_READ, FSOM_OPEN_EXISTING)) { break; } - uint16_t bytes_read = + size_t bytes_read = storage_file_read(upd_file, manifest_name_buffer, UPDATE_MANIFEST_MAX_PATH_LEN); if((bytes_read == 0) || (bytes_read == UPDATE_MANIFEST_MAX_PATH_LEN)) { break; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 8ed8f403c7..d861d85117 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.1,, +Version,+,46.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1376,7 +1376,7 @@ Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSu Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" Function,+,furi_record_close,void,const char* Function,+,furi_record_create,void,"const char*, void*" -Function,-,furi_record_destroy,_Bool,const char* +Function,+,furi_record_destroy,_Bool,const char* Function,+,furi_record_exists,_Bool,const char* Function,-,furi_record_init,void, Function,+,furi_record_open,void*,const char* @@ -2090,7 +2090,7 @@ Function,-,storage_dir_rewind,_Bool,File* Function,+,storage_error_get_desc,const char*,FS_Error Function,+,storage_file_alloc,File*,Storage* Function,+,storage_file_close,_Bool,File* -Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t" +Function,+,storage_file_copy_to_file,_Bool,"File*, File*, size_t" Function,+,storage_file_eof,_Bool,File* Function,+,storage_file_exists,_Bool,"Storage*, const char*" Function,+,storage_file_free,void,File* @@ -2100,13 +2100,13 @@ Function,-,storage_file_get_internal_error,int32_t,File* Function,+,storage_file_is_dir,_Bool,File* Function,+,storage_file_is_open,_Bool,File* Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMode" -Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" +Function,+,storage_file_read,size_t,"File*, void*, size_t" Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" Function,+,storage_file_size,uint64_t,File* Function,+,storage_file_sync,_Bool,File* Function,+,storage_file_tell,uint64_t,File* Function,+,storage_file_truncate,_Bool,File* -Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" +Function,+,storage_file_write,size_t,"File*, const void*, size_t" Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, FuriString*, uint8_t" Function,+,storage_get_pubsub,FuriPubSub*,Storage* Function,+,storage_int_backup,FS_Error,"Storage*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 64695c2dbc..c50db0c77c 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.1,, +Version,+,46.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1571,7 +1571,7 @@ Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSu Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" Function,+,furi_record_close,void,const char* Function,+,furi_record_create,void,"const char*, void*" -Function,-,furi_record_destroy,_Bool,const char* +Function,+,furi_record_destroy,_Bool,const char* Function,+,furi_record_exists,_Bool,const char* Function,-,furi_record_init,void, Function,+,furi_record_open,void*,const char* @@ -2717,7 +2717,7 @@ Function,-,storage_dir_rewind,_Bool,File* Function,+,storage_error_get_desc,const char*,FS_Error Function,+,storage_file_alloc,File*,Storage* Function,+,storage_file_close,_Bool,File* -Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t" +Function,+,storage_file_copy_to_file,_Bool,"File*, File*, size_t" Function,+,storage_file_eof,_Bool,File* Function,+,storage_file_exists,_Bool,"Storage*, const char*" Function,+,storage_file_free,void,File* @@ -2727,13 +2727,13 @@ Function,-,storage_file_get_internal_error,int32_t,File* Function,+,storage_file_is_dir,_Bool,File* Function,+,storage_file_is_open,_Bool,File* Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMode" -Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" +Function,+,storage_file_read,size_t,"File*, void*, size_t" Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" Function,+,storage_file_size,uint64_t,File* Function,+,storage_file_sync,_Bool,File* Function,+,storage_file_tell,uint64_t,File* Function,+,storage_file_truncate,_Bool,File* -Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" +Function,+,storage_file_write,size_t,"File*, const void*, size_t" Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, FuriString*, uint8_t" Function,+,storage_get_pubsub,FuriPubSub*,Storage* Function,+,storage_int_backup,FS_Error,"Storage*, const char*" From 12e736b2830767248d8dd460998baca2920c150f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 17 Nov 2023 02:35:27 +0300 Subject: [PATCH 049/111] fix ci, temp workaround for manifest --- .drone.yml | 8 ++++++++ scripts/fbt/appmanifest.py | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index d58733504b..db85f5d9d4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -46,6 +46,8 @@ steps: - 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 + - 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 @@ -66,6 +68,7 @@ steps: commands: - wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz - tar zxvf 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 @@ -118,6 +121,8 @@ steps: - ./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 + - 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 @@ -421,6 +426,8 @@ steps: - 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 + - 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 @@ -441,6 +448,7 @@ steps: commands: - wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz - tar zxvf 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 diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index d32869b106..04aafb1f99 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -166,7 +166,10 @@ def _validate_app_params(self, *args, **kw): f"App {kw.get('appid')} of type {apptype} cannot have '{app_property}' in manifest" ) else: - for app_property in ("fap_extbuild", "fap_private_libs", "fap_icon_assets"): + for app_property in ( + "fap_extbuild", + "fap_private_libs", + ): # , "fap_icon_assets"): TODO: Find a workaround for subghz_remote app if kw.get(app_property): raise FlipperManifestException( f"App {kw.get('appid')} of type {apptype} must not have '{app_property}' in manifest" From 9513ff530798931df85d495b87e7448f6411fe7e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 17 Nov 2023 02:43:47 +0300 Subject: [PATCH 050/111] fix rgb patch --- .ci_files/rgb.patch | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci_files/rgb.patch b/.ci_files/rgb.patch index 7011c9801d..d4f98aaec2 100644 --- a/.ci_files/rgb.patch +++ b/.ci_files/rgb.patch @@ -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 #include From ffd9d3c2189d3adad0863cb8beb60f16a670f03b Mon Sep 17 00:00:00 2001 From: assasinfil Date: Mon, 20 Nov 2023 14:10:50 +0300 Subject: [PATCH 051/111] Added msk transport parser --- applications/main/nfc/helpers/bit_lib.c | 398 +++++++ applications/main/nfc/helpers/bit_lib.h | 281 +++++ applications/main/nfc/helpers/transport.c | 985 ++++++++++++++++++ applications/main/nfc/helpers/transport.h | 7 + .../main/nfc/plugins/supported_cards/troika.c | 45 +- 5 files changed, 1700 insertions(+), 16 deletions(-) create mode 100644 applications/main/nfc/helpers/bit_lib.c create mode 100644 applications/main/nfc/helpers/bit_lib.h create mode 100644 applications/main/nfc/helpers/transport.c create mode 100644 applications/main/nfc/helpers/transport.h diff --git a/applications/main/nfc/helpers/bit_lib.c b/applications/main/nfc/helpers/bit_lib.c new file mode 100644 index 0000000000..9b7ac90375 --- /dev/null +++ b/applications/main/nfc/helpers/bit_lib.c @@ -0,0 +1,398 @@ +#include "bit_lib.h" +#include +#include + +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) { + size_t last_index = data_size - 1; + + for(size_t i = 0; i < last_index; ++i) { + data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1); + } + data[last_index] = (data[last_index] << 1) | bit; +} + +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = (length - 1) - i; + bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610 + } +} + +bool bit_lib_get_bit(const uint8_t* data, size_t position) { + return (data[position / 8] >> (7 - (position % 8))) & 1; +} + +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { + uint8_t shift = position % 8; + if(shift == 0) { + return data[position / 8] >> (8 - length); + } else { + // TODO fix read out of bounds + uint8_t value = (data[position / 8] << (shift)); + value |= data[position / 8 + 1] >> (8 - shift); + value = value >> (8 - length); + return value; + } +} + +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) { + uint16_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } + return value; +} + +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) { + uint32_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else { + value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } + + return value; +} + +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) { + uint64_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else if(length <= 32) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } else { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); + value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56); + value |= (uint64_t)bit_lib_get_bits(data, position + 56, 8) << (length - 64); + value |= bit_lib_get_bits(data, position + 64, length - 64); + } + + return value; +} + +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { +#if !defined __GNUC__ +#error Please, implement parity test for non-GCC compilers +#else + switch(parity) { + case BitLibParityEven: + return __builtin_parity(bits); + case BitLibParityOdd: + return !__builtin_parity(bits); + default: + furi_crash("Unknown parity"); + } +#endif +} + +bool bit_lib_test_parity( + const uint8_t* bits, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length) { + uint32_t parity_block; + bool result = true; + const size_t parity_blocks_count = length / parity_length; + + for(size_t i = 0; i < parity_blocks_count; ++i) { + switch(parity) { + case BitLibParityEven: + case BitLibParityOdd: + parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length); + if(!bit_lib_test_parity_32(parity_block, parity)) { + result = false; + } + break; + case BitLibParityAlways0: + if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + case BitLibParityAlways1: + if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + } + + if(!result) break; + } + return result; +} + +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity) { + uint32_t parity_word = 0; + size_t j = 0, bit_count = 0; + for(int word = 0; word < source_length; word += parity_length - 1) { + for(int bit = 0; bit < parity_length - 1; bit++) { + parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit); + bit_lib_set_bit( + dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit)); + } + // if parity fails then return 0 + switch(parity) { + case BitLibParityAlways0: + bit_lib_set_bit(dest, dest_position + j++, 0); + break; // marker bit which should be a 0 + case BitLibParityAlways1: + bit_lib_set_bit(dest, dest_position + j++, 1); + break; // marker bit which should be a 1 + default: + bit_lib_set_bit( + dest, + dest_position + j++, + (bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1); + break; + } + bit_count += parity_length; + parity_word = 0; + } + // if we got here then all the parities passed + // return bit count + return bit_count; +} + +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { + size_t counter = 0; + size_t result_counter = 0; + uint8_t bit_buffer = 0; + uint8_t bit_counter = 0; + + while(counter < length) { + if((counter + 1) % n != 0) { + bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter); + bit_counter++; + } + + if(bit_counter == 8) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, 8); + bit_counter = 0; + bit_buffer = 0; + result_counter += 8; + } + counter++; + } + + if(bit_counter != 0) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter); + result_counter += bit_counter; + } + return result_counter; +} + +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position) { + for(size_t i = 0; i < length; ++i) { + bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i)); + } +} + +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) { + size_t i = 0; + size_t j = length - 1; + + while(i < j) { + bool tmp = bit_lib_get_bit(data, position + i); + bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j)); + bit_lib_set_bit(data, position + j, tmp); + i++; + j--; + } +} + +uint8_t bit_lib_get_bit_count(uint32_t data) { +#if defined __GNUC__ + return __builtin_popcountl(data); +#else +#error Please, implement popcount for non-GCC compilers +#endif +} + +void bit_lib_print_bits(const uint8_t* data, size_t length) { + for(size_t i = 0; i < length; ++i) { + printf("%u", bit_lib_get_bit(data, i)); + } +} + +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length) { + // print data + bit_lib_print_bits(data, length); + printf("\r\n"); + + // print regions + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%c", regions[i].mark); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); + + // print regions data + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%u", bit_lib_get_bit(data, c)); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); +} + +uint16_t bit_lib_reverse_16_fast(uint16_t data) { + uint16_t result = 0; + result |= (data & 0x8000) >> 15; + result |= (data & 0x4000) >> 13; + result |= (data & 0x2000) >> 11; + result |= (data & 0x1000) >> 9; + result |= (data & 0x0800) >> 7; + result |= (data & 0x0400) >> 5; + result |= (data & 0x0200) >> 3; + result |= (data & 0x0100) >> 1; + result |= (data & 0x0080) << 1; + result |= (data & 0x0040) << 3; + result |= (data & 0x0020) << 5; + result |= (data & 0x0010) << 7; + result |= (data & 0x0008) << 9; + result |= (data & 0x0004) << 11; + result |= (data & 0x0002) << 13; + result |= (data & 0x0001) << 15; + return result; +} + +uint8_t bit_lib_reverse_8_fast(uint8_t byte) { + byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; + byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; + byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; + return byte; +} + +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out) { + uint8_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) bit_lib_reverse_bits(&byte, 0, 8); + crc ^= byte; + + for(size_t j = 8; j > 0; --j) { + if(crc & TOPBIT(8)) { + crc = (crc << 1) ^ polynom; + } else { + crc = (crc << 1); + } + } + } + + if(ref_out) bit_lib_reverse_bits(&crc, 0, 8); + crc ^= xor_out; + + return crc; +} + +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out) { + uint16_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8; + + for(size_t j = 0; j < 8; ++j) { + bool c15 = (crc >> 15 & 1); + bool bit = (byte >> (7 - j) & 1); + crc <<= 1; + if(c15 ^ bit) crc ^= polynom; + } + } + + if(ref_out) crc = bit_lib_reverse_16_fast(crc); + crc ^= xor_out; + + return crc; +} diff --git a/applications/main/nfc/helpers/bit_lib.h b/applications/main/nfc/helpers/bit_lib.h new file mode 100644 index 0000000000..92533993c9 --- /dev/null +++ b/applications/main/nfc/helpers/bit_lib.h @@ -0,0 +1,281 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TOPBIT(X) (1 << ((X)-1)) + +typedef enum { + BitLibParityEven, + BitLibParityOdd, + BitLibParityAlways0, + BitLibParityAlways1, +} BitLibParity; + +/** @brief Increment and wrap around a value. + * @param index value to increment + * @param length wrap-around range + */ +#define bit_lib_increment_index(index, length) (index = (((index) + 1) % (length))) + +/** @brief Test if a bit is set. + * @param data value to test + * @param index bit index to test + */ +#define bit_lib_bit_is_set(data, index) (((data) & (1 << (index))) != 0) + +/** @brief Test if a bit is not set. + * @param data value to test + * @param index bit index to test + */ +#define bit_lib_bit_is_not_set(data, index) (((data) & (1 << (index))) == 0) + +/** @brief Push a bit into a byte array. + * @param data array to push bit into + * @param data_size array size + * @param bit bit to push + */ +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit); + +/** @brief Set a bit in a byte array. + * @param data array to set bit in + * @param position The position of the bit to set. + * @param bit bit value to set + */ +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit); + +/** @brief Set the bit at the given position to the given value. + * @param data The data to set the bit in. + * @param position The position of the bit to set. + * @param byte The data to set the bit to. + * @param length The length of the data. + */ +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length); + +/** @brief Get the bit of a byte. + * @param data The byte to get the bits from. + * @param position The position of the bit. + * @return The bit. + */ +bool bit_lib_get_bit(const uint8_t* data, size_t position); + +/** + * @brief Get the bits of a data, as uint8_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Get the bits of a data, as uint16_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Get the bits of a data, as uint32_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Get the bits of a data, as uint64_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Test parity of given bits + * @param bits Bits to test parity of + * @param parity Parity to test against + * @return true if parity is correct, false otherwise + */ +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity); + +/** + * @brief Test parity of bit array, check parity for every parity_length block from start + * + * @param data Bit array + * @param position Start position + * @param length Bit count + * @param parity Parity to test against + * @param parity_length Parity block length + * @return true + * @return false + */ +bool bit_lib_test_parity( + const uint8_t* data, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length); + +/** + * @brief Add parity to bit array + * + * @param data Source bit array + * @param position Start position + * @param dest Destination bit array + * @param dest_position Destination position + * @param source_length Source bit count + * @param parity_length Parity block length + * @param parity Parity to test against + * @return size_t + */ +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity); + +/** + * @brief Remove bit every n in array and shift array left. Useful to remove parity. + * + * @param data Bit array + * @param position Start position + * @param length Bit count + * @param n every n bit will be removed + * @return size_t + */ +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n); + +/** + * @brief Copy bits from source to destination. + * + * @param data destination array + * @param position position in destination array + * @param length length of bits to copy + * @param source source array + * @param source_position position in source array + */ +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position); + +/** + * @brief Reverse bits in bit array + * + * @param data Bit array + * @param position start position + * @param length length of bits to reverse + */ +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Count 1 bits in data + * + * @param data + * @return uint8_t set bit count + */ +uint8_t bit_lib_get_bit_count(uint32_t data); + +/** + * @brief Print data as bit array + * + * @param data + * @param length + */ +void bit_lib_print_bits(const uint8_t* data, size_t length); + +typedef struct { + const char mark; + const size_t start; + const size_t length; +} BitLibRegion; + +/** + * @brief Print data as bit array and mark regions. Regions needs to be sorted by start position. + * + * @param regions + * @param region_count + * @param data + * @param length + */ +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length); + +/** + * @brief Reverse bits in uint16_t, faster than generic bit_lib_reverse_bits. + * + * @param data + * @return uint16_t + */ +uint16_t bit_lib_reverse_16_fast(uint16_t data); + +/** + * @brief Reverse bits in uint8_t, faster than generic bit_lib_reverse_bits. + * + * @param byte Byte + * @return uint8_t the reversed byte + */ +uint8_t bit_lib_reverse_8_fast(uint8_t byte); + +/** + * @brief Slow, but generic CRC8 implementation + * + * @param data + * @param data_size + * @param polynom CRC polynom + * @param init init value + * @param ref_in true if the right bit is older + * @param ref_out true to reverse output + * @param xor_out xor output with this value + * @return uint8_t + */ +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out); + +/** + * @brief Slow, but generic CRC16 implementation + * + * @param data + * @param data_size + * @param polynom CRC polynom + * @param init init value + * @param ref_in true if the right bit is older + * @param ref_out true to reverse output + * @param xor_out xor output with this value + * @return uint16_t + */ +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/transport.c b/applications/main/nfc/helpers/transport.c new file mode 100644 index 0000000000..7eab9b5495 --- /dev/null +++ b/applications/main/nfc/helpers/transport.c @@ -0,0 +1,985 @@ +#include "transport.h" +#include +#define TAG "Transport parser" + +void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { //Rewrite need + datetime->year = timestamp / (60 * 60 * 24 * 366) + FURI_HAL_RTC_EPOCH_START_YEAR; + uint16_t extra_days = (datetime->year - FURI_HAL_RTC_EPOCH_START_YEAR - 1) / 4; + uint16_t days_since_epoch = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; + uint16_t days_since_epoch_without_extra_days = days_since_epoch - extra_days; + uint16_t days_in_this_year = + days_since_epoch_without_extra_days % furi_hal_rtc_days_per_year[0] + 1; + while(days_in_this_year > furi_hal_rtc_get_days_per_month(0, datetime->month + 1)) { + days_in_this_year -= furi_hal_rtc_get_days_per_month(0, datetime->month + 1); + datetime->month++; + } + datetime->month++; + if(FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year)) { + datetime->day = days_in_this_year - 1; + } else { + datetime->day = days_in_this_year; + } + // uint16_t seconds_in_this_day = + // timestamp - (days_since_epoch * FURI_HAL_RTC_SECONDS_PER_DAY); + // datetime->hour = seconds_in_this_day / FURI_HAL_RTC_SECONDS_PER_HOUR; + // uint16_t minutes_in_this_day = + // seconds_in_this_day - (datetime->hour * FURI_HAL_RTC_SECONDS_PER_HOUR); + // datetime->minute = minutes_in_this_day / FURI_HAL_RTC_SECONDS_PER_MINUTE; + // datetime->second = minutes_in_this_day - (datetime->minute * FURI_HAL_RTC_SECONDS_PER_MINUTE); + datetime->second = timestamp % 60; + datetime->minute = timestamp / 60 % 60; + datetime->hour = timestamp / (60 * 60) % 24; +} + +void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = days * 24 * 60 * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { + uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); + + FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); + + uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); + if(layout_type == 0xE) { + layout_type = bit_lib_get_bits_16(block->data, 52, 9); + } else if(layout_type == 0xF) { + layout_type = bit_lib_get_bits_16(block->data, 52, 14); + } + + FURI_LOG_D(TAG, "Layout type %x", layout_type); + + uint16_t card_view = 0; + uint16_t card_type = 0; + uint32_t card_number = 0; + uint8_t card_layout = 0; + uint8_t card_layout2 = 0; + uint16_t card_use_before_date = 0; + uint16_t card_blank_type = 0; + uint32_t card_start_trip_minutes = 0; + uint8_t card_minutes_pass = 0; + uint32_t card_remaining_funds = 0; + uint16_t card_validator = 0; + uint8_t card_blocked = 0; + uint32_t card_hash = 0; + + switch(layout_type) { + case 0x02: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_benefit_code = bit_lib_get_bits(block->data, 72, 8); //124 + uint32_t card_rfu1 = bit_lib_get_bits_32(block->data, 80, 32); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 177, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 189, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint8_t card_start_trip_seconds = bit_lib_get_bits(block->data, 189, 6); //406 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_use_with_date = bit_lib_get_bits_16(block->data, 189, 16); //205 + uint8_t card_route = bit_lib_get_bits(block->data, 205, 1); //424 + uint16_t card_validator1 = bit_lib_get_bits_16(block->data, 206, 15); //422.1 + card_validator = bit_lib_get_bits_16(block->data, 205, 16); //422 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 221, 16); //331 + uint8_t card_write_enabled = bit_lib_get_bits(block->data, 237, 1); //write_enabled + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 238, 2); //rfu2 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_benefit_code, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_start_trip_seconds, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_use_with_date, + card_route, + card_validator1, + card_validator, + card_total_trips, + card_write_enabled, + card_rfu2, + card_crc16_2); + break; + } + case 0x06: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_geozone_a = bit_lib_get_bits(block->data, 72, 4); //GeoZoneA + uint8_t card_geozone_b = bit_lib_get_bits(block->data, 76, 4); //GeoZoneB + card_blank_type = bit_lib_get_bits_16(block->data, 80, 10); //121. + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 90, 10); //122 + uint32_t card_rfu1 = bit_lib_get_bits_16(block->data, 100, 12); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 129, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 141, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint16_t card_company = bit_lib_get_bits(block->data, 189, 4); //Company + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 4); //422.1 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 197, 10); //321 + uint8_t card_units = bit_lib_get_bits(block->data, 207, 6); //Units + uint16_t card_validator2 = bit_lib_get_bits_16(block->data, 213, 10); //422.2 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 223, 16); //331 + uint8_t card_extended = bit_lib_get_bits(block->data, 239, 1); //123 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_geozone_a, + card_geozone_b, + card_blank_type, + card_type_of_extended, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_company, + card_validator1, + card_remaining_trips, + card_units, + card_validator2, + card_total_trips, + card_extended, + card_crc16_2); + card_validator = card_validator1 * 1024 + card_validator2; + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_total_trips, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x08: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 153, 7); //rfu2 + uint8_t card_remaining_trips1 = bit_lib_get_bits(block->data, 160, 8); //321.1 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 168, 8); //321 + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 2); //422.1 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 177, 15); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint32_t card_rfu3 = bit_lib_get_bits_32(block->data, 224, 32); //rfu3 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips1, + card_remaining_trips, + card_validator1, + card_validator, + card_hash, + card_valid_from_date, + card_rfu3); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_validator); + break; + } + case 0x0A: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 64, 12); //311 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 76, 19); //314 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 95, 1); //301 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 96, 19); //405 + card_minutes_pass = bit_lib_get_bits(block->data, 119, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 126, 2); //421.0 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 128, 8); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 136, 16); //422 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 152, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 154, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 156, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 158, 2); //421.4 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_valid_from_date, + card_valid_for_minutes, + card_requires_activation, + card_start_trip_minutes, + card_minutes_pass, + card_transport_type_flag, + card_remaining_trips, + card_validator, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0C: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint16_t card_rfu2 = bit_lib_get_bits_16(block->data, 153, 13); //rfu2 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type = bit_lib_get_bits(block->data, 251, 2); //421 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 253, 2); //rfu3 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type, + card_rfu3, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0D: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint8_t card_rfu1 = bit_lib_get_bits(block->data, 56, 8); //rfu1 + card_use_before_date = bit_lib_get_bits_16(block->data, 64, 16); //202 + uint16_t card_valid_for_time = bit_lib_get_bits_16(block->data, 80, 11); //316 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 91, 5); //rfu2 + uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->data, 96, 16); //202.2 + uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->data, 123, 11); //316.2 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 123, 5); //rfu3 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu4 = bit_lib_get_bits(block->data, 153, 2); //rfu4 + uint8_t card_passage_5_minutes = bit_lib_get_bits(block->data, 155, 5); //413 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 160, 2); //421.1 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 162, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 163, 3); //433 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 251, 2); //421.2 + uint8_t card_rfu5 = bit_lib_get_bits(block->data, 253, 2); //rfu5 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_rfu1, + card_use_before_date, + card_valid_for_time, + card_rfu2, + card_use_before_date2, + card_valid_for_time2, + card_rfu3, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu4, + card_passage_5_minutes, + card_transport_type1, + card_passage_in_metro, + card_passages_ground_transport, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type2, + card_rfu5, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x1C1: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121. + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 144, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 160, 11); //403 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 171, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 173, 2); //421.2 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 177, 1); //432 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 178, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 179, 3); //433 + card_minutes_pass = bit_lib_get_bits(block->data, 185, 8); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 196, 19) / 100; //322 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 215, 2); //441 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint8_t card_zoo = bit_lib_get_bits(block->data, 218, 1); //zoo + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type1, + card_transport_type2, + card_transfer_in_metro, + card_passage_in_metro, + card_passages_ground_transport, + card_minutes_pass, + card_remaining_funds, + card_fare_trip, + card_blocked, + card_zoo, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C2: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 87, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 97, 16); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 113, 9); //302 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 131, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 154, 8); //412. + uint8_t card_transport_type = bit_lib_get_bits(block->data, 163, 2); //421 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 165, 1); //431 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 166, 1); //432 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 167, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 177, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 196, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 216, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 217, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 218, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type, + card_passage_in_metro, + card_transfer_in_metro, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C3: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121 + card_remaining_funds = bit_lib_get_bits_32(block->data, 188, 22) / 100; //322 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 144, 23); //405 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 210, 2); //441 + card_minutes_pass = bit_lib_get_bits(block->data, 171, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + card_blocked = bit_lib_get_bits(block->data, 212, 1); //303 + FURI_LOG_D( + TAG, + "Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_remaining_funds, + card_hash, + card_validator, + card_start_trip_minutes, + card_fare_trip, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_blocked); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C4: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 94, 13); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 107, 9); //302 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 116, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 169, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 179, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 195, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 215, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 216, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 217, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_extension_counter, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_use_before_date - 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C5: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 74, 10); //121. + uint32_t card_valid_to_time = bit_lib_get_bits_32(block->data, 84, 23); //317 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 107, 10); //304 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 128, 23); //405 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 151, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 167, 19) / 100; //322 + card_validator = bit_lib_get_bits_16(block->data, 186, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint16_t card_route = bit_lib_get_bits_16(block->data, 204, 12); //424 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 216, 7); //433 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_valid_to_time, + card_extension_counter, + card_start_trip_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_funds, + card_validator, + card_blocked, + card_route, + card_passages_ground_transport, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C6: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint32_t card_valid_from_date = bit_lib_get_bits_32(block->data, 94, 23); //311 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 117, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 148, 20); //404 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 168, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 175, 7); //412. + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 182, 7); //321 + card_validator = bit_lib_get_bits_16(block->data, 189, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 205, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 206, 1); //123 + uint16_t card_route = bit_lib_get_bits_16(block->data, 212, 12); //424 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_from_date, + card_extension_counter, + card_valid_for_minutes, + card_start_trip_neg_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_trips, + card_validator, + card_blocked, + card_extended, + card_route, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60, + &card_start_trip_minutes_s, + 2019); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x3CCB: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint8_t card_interval = bit_lib_get_bits(block->data, 98, 4); //interval + uint16_t card_app_code1 = bit_lib_get_bits_16(block->data, 102, 16); //app_code1 + uint16_t card_hash1 = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + uint16_t card_type1 = bit_lib_get_bits_16(block->data, 128, 10); //type1 + uint16_t card_app_code2 = bit_lib_get_bits_16(block->data, 138, 10); //app_code2 + uint16_t card_type2 = bit_lib_get_bits_16(block->data, 148, 10); //type2 + uint16_t card_app_code3 = bit_lib_get_bits_16(block->data, 158, 10); //app_code3 + uint16_t card_type3 = bit_lib_get_bits_16(block->data, 148, 10); //type3 + uint16_t card_app_code4 = bit_lib_get_bits_16(block->data, 168, 10); //app_code4 + uint16_t card_type4 = bit_lib_get_bits_16(block->data, 178, 10); //type4 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_interval, + card_app_code1, + card_hash1, + card_type1, + card_app_code2, + card_type2, + card_app_code3, + card_type3, + card_app_code4, + card_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + case 0x3C0B: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint16_t card_hash = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + default: + return false; + } + return true; +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/transport.h b/applications/main/nfc/helpers/transport.h new file mode 100644 index 0000000000..ae31a9e56b --- /dev/null +++ b/applications/main/nfc/helpers/transport.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include + +bool parse_transport_block(const MfClassicBlock* block, FuriString* result); \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 7cf1e4dd8c..7752b1744a 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include #define TAG "Troika" @@ -171,22 +173,33 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Parse data - const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); - - const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5]; - uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[start_block_num].data[2]; - - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; - } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance); + // // Parse data + // const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + + // const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5]; + // uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; + // temp_ptr = &data->block[start_block_num].data[2]; + + // uint32_t number = 0; + // for(size_t i = 1; i < 5; i++) { + // number <<= 8; + // number |= temp_ptr[i]; + // } + // number >>= 4; + // number |= (temp_ptr[0] & 0xf) << 28; + + // furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance); + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + parse_transport_block(&data->block[32], metro_result); + parse_transport_block(&data->block[28], ground_result); + furi_string_printf( + parsed_data, + "\e#Troika\n%s\n%s", + furi_string_get_cstr(metro_result), + furi_string_get_cstr(ground_result)); + furi_string_free(metro_result); + furi_string_free(ground_result); parsed = true; } while(false); From 51a2e638ed630efd1e34291d1040cbabd3a8bdf9 Mon Sep 17 00:00:00 2001 From: Methodius Date: Mon, 20 Nov 2023 22:14:49 +0900 Subject: [PATCH 052/111] trying to fix timestamp_to_datetime() --- applications/main/nfc/helpers/transport.c | 46 +++++++++++------------ 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/applications/main/nfc/helpers/transport.c b/applications/main/nfc/helpers/transport.c index 7eab9b5495..644b3e1a9a 100644 --- a/applications/main/nfc/helpers/transport.c +++ b/applications/main/nfc/helpers/transport.c @@ -1,34 +1,30 @@ #include "transport.h" #include +#include #define TAG "Transport parser" -void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { //Rewrite need - datetime->year = timestamp / (60 * 60 * 24 * 366) + FURI_HAL_RTC_EPOCH_START_YEAR; - uint16_t extra_days = (datetime->year - FURI_HAL_RTC_EPOCH_START_YEAR - 1) / 4; - uint16_t days_since_epoch = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; - uint16_t days_since_epoch_without_extra_days = days_since_epoch - extra_days; - uint16_t days_in_this_year = - days_since_epoch_without_extra_days % furi_hal_rtc_days_per_year[0] + 1; - while(days_in_this_year > furi_hal_rtc_get_days_per_month(0, datetime->month + 1)) { - days_in_this_year -= furi_hal_rtc_get_days_per_month(0, datetime->month + 1); - datetime->month++; +void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { + uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; + uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; + + uint16_t year = FURI_HAL_RTC_EPOCH_START_YEAR; + + while(days >= furi_hal_rtc_get_days_per_year(year)) { + days -= furi_hal_rtc_get_days_per_year(year); + (year)++; } - datetime->month++; - if(FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year)) { - datetime->day = days_in_this_year - 1; - } else { - datetime->day = days_in_this_year; + + uint8_t month = 1; + while(days >= furi_hal_rtc_get_days_per_month(FURI_HAL_RTC_IS_LEAP_YEAR(year), month)) { + days -= furi_hal_rtc_get_days_per_month(FURI_HAL_RTC_IS_LEAP_YEAR(year), month); + (month)++; } - // uint16_t seconds_in_this_day = - // timestamp - (days_since_epoch * FURI_HAL_RTC_SECONDS_PER_DAY); - // datetime->hour = seconds_in_this_day / FURI_HAL_RTC_SECONDS_PER_HOUR; - // uint16_t minutes_in_this_day = - // seconds_in_this_day - (datetime->hour * FURI_HAL_RTC_SECONDS_PER_HOUR); - // datetime->minute = minutes_in_this_day / FURI_HAL_RTC_SECONDS_PER_MINUTE; - // datetime->second = minutes_in_this_day - (datetime->minute * FURI_HAL_RTC_SECONDS_PER_MINUTE); - datetime->second = timestamp % 60; - datetime->minute = timestamp / 60 % 60; - datetime->hour = timestamp / (60 * 60) % 24; + + datetime->day = days + 1; + datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; + datetime->minute = + (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; + datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; } void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) { From bbdda5a3d7ad8095487a2c216a6f2ad68591633d Mon Sep 17 00:00:00 2001 From: Methodius Date: Mon, 20 Nov 2023 22:19:29 +0900 Subject: [PATCH 053/111] trying to fix previous fix of timestamp_to_datetime() --- applications/main/nfc/helpers/transport.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/helpers/transport.c b/applications/main/nfc/helpers/transport.c index 644b3e1a9a..636dfe8b26 100644 --- a/applications/main/nfc/helpers/transport.c +++ b/applications/main/nfc/helpers/transport.c @@ -7,17 +7,19 @@ void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; - uint16_t year = FURI_HAL_RTC_EPOCH_START_YEAR; + datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; - while(days >= furi_hal_rtc_get_days_per_year(year)) { - days -= furi_hal_rtc_get_days_per_year(year); - (year)++; + while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { + days -= furi_hal_rtc_get_days_per_year(datetime->year); + (datetime->year)++; } - uint8_t month = 1; - while(days >= furi_hal_rtc_get_days_per_month(FURI_HAL_RTC_IS_LEAP_YEAR(year), month)) { - days -= furi_hal_rtc_get_days_per_month(FURI_HAL_RTC_IS_LEAP_YEAR(year), month); - (month)++; + datetime->month = 1; + while(days >= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month)) { + days -= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month); + (datetime->month)++; } datetime->day = days + 1; From 156948ec5886bebbe468376b0f4a99feea2f7e6c Mon Sep 17 00:00:00 2001 From: assasinfil Date: Mon, 20 Nov 2023 17:42:19 +0300 Subject: [PATCH 054/111] All code added to parser --- .../main/nfc/plugins/supported_cards/troika.c | 1418 ++++++++++++++++- 1 file changed, 1400 insertions(+), 18 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 7752b1744a..df90867da8 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1,12 +1,14 @@ #include "nfc_supported_card_plugin.h" +#include #include #include #include #include -#include #include +#include +#include #define TAG "Troika" @@ -62,6 +64,1402 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0x518dc6eea089, .b = 0x97c64ac98ca4}, {.a = 0xbb52f8cce07f, .b = 0x6b6119752c70}, }; +#define TOPBIT(X) (1 << ((X)-1)) + +typedef enum { + BitLibParityEven, + BitLibParityOdd, + BitLibParityAlways0, + BitLibParityAlways1, +} BitLibParity; + +typedef struct { + const char mark; + const size_t start; + const size_t length; +} BitLibRegion; + +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) { + size_t last_index = data_size - 1; + + for(size_t i = 0; i < last_index; ++i) { + data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1); + } + data[last_index] = (data[last_index] << 1) | bit; +} + +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = (length - 1) - i; + bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610 + } +} + +bool bit_lib_get_bit(const uint8_t* data, size_t position) { + return (data[position / 8] >> (7 - (position % 8))) & 1; +} + +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { + uint8_t shift = position % 8; + if(shift == 0) { + return data[position / 8] >> (8 - length); + } else { + // TODO fix read out of bounds + uint8_t value = (data[position / 8] << (shift)); + value |= data[position / 8 + 1] >> (8 - shift); + value = value >> (8 - length); + return value; + } +} + +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) { + uint16_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } + return value; +} + +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) { + uint32_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else { + value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } + + return value; +} + +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) { + uint64_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else if(length <= 32) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } else { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); + value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56); + value |= (uint64_t)bit_lib_get_bits(data, position + 56, 8) << (length - 64); + value |= bit_lib_get_bits(data, position + 64, length - 64); + } + + return value; +} + +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { +#if !defined __GNUC__ +#error Please, implement parity test for non-GCC compilers +#else + switch(parity) { + case BitLibParityEven: + return __builtin_parity(bits); + case BitLibParityOdd: + return !__builtin_parity(bits); + default: + furi_crash("Unknown parity"); + } +#endif +} + +bool bit_lib_test_parity( + const uint8_t* bits, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length) { + uint32_t parity_block; + bool result = true; + const size_t parity_blocks_count = length / parity_length; + + for(size_t i = 0; i < parity_blocks_count; ++i) { + switch(parity) { + case BitLibParityEven: + case BitLibParityOdd: + parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length); + if(!bit_lib_test_parity_32(parity_block, parity)) { + result = false; + } + break; + case BitLibParityAlways0: + if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + case BitLibParityAlways1: + if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + } + + if(!result) break; + } + return result; +} + +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity) { + uint32_t parity_word = 0; + size_t j = 0, bit_count = 0; + for(int word = 0; word < source_length; word += parity_length - 1) { + for(int bit = 0; bit < parity_length - 1; bit++) { + parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit); + bit_lib_set_bit( + dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit)); + } + // if parity fails then return 0 + switch(parity) { + case BitLibParityAlways0: + bit_lib_set_bit(dest, dest_position + j++, 0); + break; // marker bit which should be a 0 + case BitLibParityAlways1: + bit_lib_set_bit(dest, dest_position + j++, 1); + break; // marker bit which should be a 1 + default: + bit_lib_set_bit( + dest, + dest_position + j++, + (bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1); + break; + } + bit_count += parity_length; + parity_word = 0; + } + // if we got here then all the parities passed + // return bit count + return bit_count; +} + +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { + size_t counter = 0; + size_t result_counter = 0; + uint8_t bit_buffer = 0; + uint8_t bit_counter = 0; + + while(counter < length) { + if((counter + 1) % n != 0) { + bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter); + bit_counter++; + } + + if(bit_counter == 8) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, 8); + bit_counter = 0; + bit_buffer = 0; + result_counter += 8; + } + counter++; + } + + if(bit_counter != 0) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter); + result_counter += bit_counter; + } + return result_counter; +} + +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position) { + for(size_t i = 0; i < length; ++i) { + bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i)); + } +} + +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) { + size_t i = 0; + size_t j = length - 1; + + while(i < j) { + bool tmp = bit_lib_get_bit(data, position + i); + bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j)); + bit_lib_set_bit(data, position + j, tmp); + i++; + j--; + } +} + +uint8_t bit_lib_get_bit_count(uint32_t data) { +#if defined __GNUC__ + return __builtin_popcountl(data); +#else +#error Please, implement popcount for non-GCC compilers +#endif +} + +void bit_lib_print_bits(const uint8_t* data, size_t length) { + for(size_t i = 0; i < length; ++i) { + printf("%u", bit_lib_get_bit(data, i)); + } +} + +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length) { + // print data + bit_lib_print_bits(data, length); + printf("\r\n"); + + // print regions + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%c", regions[i].mark); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); + + // print regions data + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%u", bit_lib_get_bit(data, c)); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); +} + +uint16_t bit_lib_reverse_16_fast(uint16_t data) { + uint16_t result = 0; + result |= (data & 0x8000) >> 15; + result |= (data & 0x4000) >> 13; + result |= (data & 0x2000) >> 11; + result |= (data & 0x1000) >> 9; + result |= (data & 0x0800) >> 7; + result |= (data & 0x0400) >> 5; + result |= (data & 0x0200) >> 3; + result |= (data & 0x0100) >> 1; + result |= (data & 0x0080) << 1; + result |= (data & 0x0040) << 3; + result |= (data & 0x0020) << 5; + result |= (data & 0x0010) << 7; + result |= (data & 0x0008) << 9; + result |= (data & 0x0004) << 11; + result |= (data & 0x0002) << 13; + result |= (data & 0x0001) << 15; + return result; +} + +uint8_t bit_lib_reverse_8_fast(uint8_t byte) { + byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; + byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; + byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; + return byte; +} + +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out) { + uint8_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) bit_lib_reverse_bits(&byte, 0, 8); + crc ^= byte; + + for(size_t j = 8; j > 0; --j) { + if(crc & TOPBIT(8)) { + crc = (crc << 1) ^ polynom; + } else { + crc = (crc << 1); + } + } + } + + if(ref_out) bit_lib_reverse_bits(&crc, 0, 8); + crc ^= xor_out; + + return crc; +} + +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out) { + uint16_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8; + + for(size_t j = 0; j < 8; ++j) { + bool c15 = (crc >> 15 & 1); + bool bit = (byte >> (7 - j) & 1); + crc <<= 1; + if(c15 ^ bit) crc ^= polynom; + } + } + + if(ref_out) crc = bit_lib_reverse_16_fast(crc); + crc ^= xor_out; + + return crc; +} + +#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 +#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) +#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) +#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 +#define FURI_HAL_RTC_IS_LEAP_YEAR(year) \ + ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) + +void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { + uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; + uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; + + datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; + + while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { + days -= furi_hal_rtc_get_days_per_year(datetime->year); + (datetime->year)++; + } + + datetime->month = 1; + while(days >= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month)) { + days -= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month); + (datetime->month)++; + } + + datetime->day = days + 1; + datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; + datetime->minute = + (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; + datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; +} + +void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = days * 24 * 60 * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { + uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); + + FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); + + uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); + if(layout_type == 0xE) { + layout_type = bit_lib_get_bits_16(block->data, 52, 9); + } else if(layout_type == 0xF) { + layout_type = bit_lib_get_bits_16(block->data, 52, 14); + } + + FURI_LOG_D(TAG, "Layout type %x", layout_type); + + uint16_t card_view = 0; + uint16_t card_type = 0; + uint32_t card_number = 0; + uint8_t card_layout = 0; + uint8_t card_layout2 = 0; + uint16_t card_use_before_date = 0; + uint16_t card_blank_type = 0; + uint32_t card_start_trip_minutes = 0; + uint8_t card_minutes_pass = 0; + uint32_t card_remaining_funds = 0; + uint16_t card_validator = 0; + uint8_t card_blocked = 0; + uint32_t card_hash = 0; + + switch(layout_type) { + case 0x02: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_benefit_code = bit_lib_get_bits(block->data, 72, 8); //124 + uint32_t card_rfu1 = bit_lib_get_bits_32(block->data, 80, 32); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 177, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 189, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint8_t card_start_trip_seconds = bit_lib_get_bits(block->data, 189, 6); //406 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_use_with_date = bit_lib_get_bits_16(block->data, 189, 16); //205 + uint8_t card_route = bit_lib_get_bits(block->data, 205, 1); //424 + uint16_t card_validator1 = bit_lib_get_bits_16(block->data, 206, 15); //422.1 + card_validator = bit_lib_get_bits_16(block->data, 205, 16); //422 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 221, 16); //331 + uint8_t card_write_enabled = bit_lib_get_bits(block->data, 237, 1); //write_enabled + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 238, 2); //rfu2 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_benefit_code, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_start_trip_seconds, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_use_with_date, + card_route, + card_validator1, + card_validator, + card_total_trips, + card_write_enabled, + card_rfu2, + card_crc16_2); + break; + } + case 0x06: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_geozone_a = bit_lib_get_bits(block->data, 72, 4); //GeoZoneA + uint8_t card_geozone_b = bit_lib_get_bits(block->data, 76, 4); //GeoZoneB + card_blank_type = bit_lib_get_bits_16(block->data, 80, 10); //121. + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 90, 10); //122 + uint32_t card_rfu1 = bit_lib_get_bits_16(block->data, 100, 12); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 129, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 141, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint16_t card_company = bit_lib_get_bits(block->data, 189, 4); //Company + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 4); //422.1 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 197, 10); //321 + uint8_t card_units = bit_lib_get_bits(block->data, 207, 6); //Units + uint16_t card_validator2 = bit_lib_get_bits_16(block->data, 213, 10); //422.2 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 223, 16); //331 + uint8_t card_extended = bit_lib_get_bits(block->data, 239, 1); //123 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_geozone_a, + card_geozone_b, + card_blank_type, + card_type_of_extended, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_company, + card_validator1, + card_remaining_trips, + card_units, + card_validator2, + card_total_trips, + card_extended, + card_crc16_2); + card_validator = card_validator1 * 1024 + card_validator2; + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_total_trips, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x08: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 153, 7); //rfu2 + uint8_t card_remaining_trips1 = bit_lib_get_bits(block->data, 160, 8); //321.1 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 168, 8); //321 + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 2); //422.1 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 177, 15); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint32_t card_rfu3 = bit_lib_get_bits_32(block->data, 224, 32); //rfu3 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips1, + card_remaining_trips, + card_validator1, + card_validator, + card_hash, + card_valid_from_date, + card_rfu3); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_validator); + break; + } + case 0x0A: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 64, 12); //311 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 76, 19); //314 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 95, 1); //301 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 96, 19); //405 + card_minutes_pass = bit_lib_get_bits(block->data, 119, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 126, 2); //421.0 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 128, 8); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 136, 16); //422 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 152, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 154, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 156, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 158, 2); //421.4 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_valid_from_date, + card_valid_for_minutes, + card_requires_activation, + card_start_trip_minutes, + card_minutes_pass, + card_transport_type_flag, + card_remaining_trips, + card_validator, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0C: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint16_t card_rfu2 = bit_lib_get_bits_16(block->data, 153, 13); //rfu2 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type = bit_lib_get_bits(block->data, 251, 2); //421 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 253, 2); //rfu3 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type, + card_rfu3, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0D: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint8_t card_rfu1 = bit_lib_get_bits(block->data, 56, 8); //rfu1 + card_use_before_date = bit_lib_get_bits_16(block->data, 64, 16); //202 + uint16_t card_valid_for_time = bit_lib_get_bits_16(block->data, 80, 11); //316 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 91, 5); //rfu2 + uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->data, 96, 16); //202.2 + uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->data, 123, 11); //316.2 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 123, 5); //rfu3 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu4 = bit_lib_get_bits(block->data, 153, 2); //rfu4 + uint8_t card_passage_5_minutes = bit_lib_get_bits(block->data, 155, 5); //413 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 160, 2); //421.1 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 162, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 163, 3); //433 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 251, 2); //421.2 + uint8_t card_rfu5 = bit_lib_get_bits(block->data, 253, 2); //rfu5 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_rfu1, + card_use_before_date, + card_valid_for_time, + card_rfu2, + card_use_before_date2, + card_valid_for_time2, + card_rfu3, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu4, + card_passage_5_minutes, + card_transport_type1, + card_passage_in_metro, + card_passages_ground_transport, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type2, + card_rfu5, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x1C1: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121. + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 144, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 160, 11); //403 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 171, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 173, 2); //421.2 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 177, 1); //432 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 178, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 179, 3); //433 + card_minutes_pass = bit_lib_get_bits(block->data, 185, 8); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 196, 19) / 100; //322 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 215, 2); //441 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint8_t card_zoo = bit_lib_get_bits(block->data, 218, 1); //zoo + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type1, + card_transport_type2, + card_transfer_in_metro, + card_passage_in_metro, + card_passages_ground_transport, + card_minutes_pass, + card_remaining_funds, + card_fare_trip, + card_blocked, + card_zoo, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C2: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 87, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 97, 16); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 113, 9); //302 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 131, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 154, 8); //412. + uint8_t card_transport_type = bit_lib_get_bits(block->data, 163, 2); //421 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 165, 1); //431 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 166, 1); //432 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 167, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 177, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 196, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 216, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 217, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 218, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type, + card_passage_in_metro, + card_transfer_in_metro, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C3: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121 + card_remaining_funds = bit_lib_get_bits_32(block->data, 188, 22) / 100; //322 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 144, 23); //405 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 210, 2); //441 + card_minutes_pass = bit_lib_get_bits(block->data, 171, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + card_blocked = bit_lib_get_bits(block->data, 212, 1); //303 + FURI_LOG_D( + TAG, + "Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_remaining_funds, + card_hash, + card_validator, + card_start_trip_minutes, + card_fare_trip, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_blocked); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C4: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 94, 13); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 107, 9); //302 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 116, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 169, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 179, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 195, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 215, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 216, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 217, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_extension_counter, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_use_before_date - 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C5: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 74, 10); //121. + uint32_t card_valid_to_time = bit_lib_get_bits_32(block->data, 84, 23); //317 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 107, 10); //304 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 128, 23); //405 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 151, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 167, 19) / 100; //322 + card_validator = bit_lib_get_bits_16(block->data, 186, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint16_t card_route = bit_lib_get_bits_16(block->data, 204, 12); //424 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 216, 7); //433 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_valid_to_time, + card_extension_counter, + card_start_trip_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_funds, + card_validator, + card_blocked, + card_route, + card_passages_ground_transport, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C6: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint32_t card_valid_from_date = bit_lib_get_bits_32(block->data, 94, 23); //311 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 117, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 148, 20); //404 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 168, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 175, 7); //412. + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 182, 7); //321 + card_validator = bit_lib_get_bits_16(block->data, 189, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 205, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 206, 1); //123 + uint16_t card_route = bit_lib_get_bits_16(block->data, 212, 12); //424 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_from_date, + card_extension_counter, + card_valid_for_minutes, + card_start_trip_neg_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_trips, + card_validator, + card_blocked, + card_extended, + card_route, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60, + &card_start_trip_minutes_s, + 2019); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x3CCB: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint8_t card_interval = bit_lib_get_bits(block->data, 98, 4); //interval + uint16_t card_app_code1 = bit_lib_get_bits_16(block->data, 102, 16); //app_code1 + uint16_t card_hash1 = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + uint16_t card_type1 = bit_lib_get_bits_16(block->data, 128, 10); //type1 + uint16_t card_app_code2 = bit_lib_get_bits_16(block->data, 138, 10); //app_code2 + uint16_t card_type2 = bit_lib_get_bits_16(block->data, 148, 10); //type2 + uint16_t card_app_code3 = bit_lib_get_bits_16(block->data, 158, 10); //app_code3 + uint16_t card_type3 = bit_lib_get_bits_16(block->data, 148, 10); //type3 + uint16_t card_app_code4 = bit_lib_get_bits_16(block->data, 168, 10); //app_code4 + uint16_t card_type4 = bit_lib_get_bits_16(block->data, 178, 10); //type4 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_interval, + card_app_code1, + card_hash1, + card_type1, + card_app_code2, + card_type2, + card_app_code3, + card_type3, + card_app_code4, + card_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + case 0x3C0B: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint16_t card_hash = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + default: + return false; + } + return true; +} + static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; @@ -173,22 +1571,6 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // // Parse data - // const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); - - // const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5]; - // uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - // temp_ptr = &data->block[start_block_num].data[2]; - - // uint32_t number = 0; - // for(size_t i = 1; i < 5; i++) { - // number <<= 8; - // number |= temp_ptr[i]; - // } - // number >>= 4; - // number |= (temp_ptr[0] & 0xf) << 28; - - // furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance); FuriString* metro_result = furi_string_alloc(); FuriString* ground_result = furi_string_alloc(); parse_transport_block(&data->block[32], metro_result); @@ -224,4 +1606,4 @@ static const FlipperAppPluginDescriptor troika_plugin_descriptor = { /* Plugin entry point - must return a pointer to const descriptor */ const FlipperAppPluginDescriptor* troika_plugin_ep() { return &troika_plugin_descriptor; -} +} \ No newline at end of file From 0e172b67eb73d631baba8847f7a0811df72a742e Mon Sep 17 00:00:00 2001 From: assasinfil Date: Mon, 20 Nov 2023 18:22:17 +0300 Subject: [PATCH 055/111] Layout 1c4 fix --- applications/main/nfc/plugins/supported_cards/troika.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index df90867da8..5d0d6d0c0a 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1207,14 +1207,14 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_extended, card_hash); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_use_before_date - 1) * 24 * 60 + card_valid_for_minutes - + (card_use_before_date + 1) * 24 * 60 + card_valid_for_minutes - card_start_trip_neg_minutes, &card_start_trip_minutes_s, - 2016); //-time + 2011); //-time furi_string_printf( result, "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", @@ -1457,6 +1457,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { default: return false; } + return true; } From 8188b63522f1a2b59c687f0d975ad0f17c554af9 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Mon, 20 Nov 2023 18:43:23 +0300 Subject: [PATCH 056/111] cleanup --- applications/main/nfc/helpers/bit_lib.c | 398 --------- applications/main/nfc/helpers/bit_lib.h | 281 ------- applications/main/nfc/helpers/transport.c | 983 ---------------------- applications/main/nfc/helpers/transport.h | 7 - 4 files changed, 1669 deletions(-) delete mode 100644 applications/main/nfc/helpers/bit_lib.c delete mode 100644 applications/main/nfc/helpers/bit_lib.h delete mode 100644 applications/main/nfc/helpers/transport.c delete mode 100644 applications/main/nfc/helpers/transport.h diff --git a/applications/main/nfc/helpers/bit_lib.c b/applications/main/nfc/helpers/bit_lib.c deleted file mode 100644 index 9b7ac90375..0000000000 --- a/applications/main/nfc/helpers/bit_lib.c +++ /dev/null @@ -1,398 +0,0 @@ -#include "bit_lib.h" -#include -#include - -void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) { - size_t last_index = data_size - 1; - - for(size_t i = 0; i < last_index; ++i) { - data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1); - } - data[last_index] = (data[last_index] << 1) | bit; -} - -void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) { - if(bit) { - data[position / 8] |= 1UL << (7 - (position % 8)); - } else { - data[position / 8] &= ~(1UL << (7 - (position % 8))); - } -} - -void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) { - furi_check(length <= 8); - furi_check(length > 0); - - for(uint8_t i = 0; i < length; ++i) { - uint8_t shift = (length - 1) - i; - bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610 - } -} - -bool bit_lib_get_bit(const uint8_t* data, size_t position) { - return (data[position / 8] >> (7 - (position % 8))) & 1; -} - -uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { - uint8_t shift = position % 8; - if(shift == 0) { - return data[position / 8] >> (8 - length); - } else { - // TODO fix read out of bounds - uint8_t value = (data[position / 8] << (shift)); - value |= data[position / 8 + 1] >> (8 - shift); - value = value >> (8 - length); - return value; - } -} - -uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) { - uint16_t value = 0; - if(length <= 8) { - value = bit_lib_get_bits(data, position, length); - } else { - value = bit_lib_get_bits(data, position, 8) << (length - 8); - value |= bit_lib_get_bits(data, position + 8, length - 8); - } - return value; -} - -uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) { - uint32_t value = 0; - if(length <= 8) { - value = bit_lib_get_bits(data, position, length); - } else if(length <= 16) { - value = bit_lib_get_bits(data, position, 8) << (length - 8); - value |= bit_lib_get_bits(data, position + 8, length - 8); - } else if(length <= 24) { - value = bit_lib_get_bits(data, position, 8) << (length - 8); - value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); - value |= bit_lib_get_bits(data, position + 16, length - 16); - } else { - value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8); - value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); - value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); - value |= bit_lib_get_bits(data, position + 24, length - 24); - } - - return value; -} - -uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) { - uint64_t value = 0; - if(length <= 8) { - value = bit_lib_get_bits(data, position, length); - } else if(length <= 16) { - value = bit_lib_get_bits(data, position, 8) << (length - 8); - value |= bit_lib_get_bits(data, position + 8, length - 8); - } else if(length <= 24) { - value = bit_lib_get_bits(data, position, 8) << (length - 8); - value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); - value |= bit_lib_get_bits(data, position + 16, length - 16); - } else if(length <= 32) { - value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); - value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); - value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); - value |= bit_lib_get_bits(data, position + 24, length - 24); - } else { - value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); - value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); - value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); - value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); - value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); - value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); - value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56); - value |= (uint64_t)bit_lib_get_bits(data, position + 56, 8) << (length - 64); - value |= bit_lib_get_bits(data, position + 64, length - 64); - } - - return value; -} - -bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { -#if !defined __GNUC__ -#error Please, implement parity test for non-GCC compilers -#else - switch(parity) { - case BitLibParityEven: - return __builtin_parity(bits); - case BitLibParityOdd: - return !__builtin_parity(bits); - default: - furi_crash("Unknown parity"); - } -#endif -} - -bool bit_lib_test_parity( - const uint8_t* bits, - size_t position, - uint8_t length, - BitLibParity parity, - uint8_t parity_length) { - uint32_t parity_block; - bool result = true; - const size_t parity_blocks_count = length / parity_length; - - for(size_t i = 0; i < parity_blocks_count; ++i) { - switch(parity) { - case BitLibParityEven: - case BitLibParityOdd: - parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length); - if(!bit_lib_test_parity_32(parity_block, parity)) { - result = false; - } - break; - case BitLibParityAlways0: - if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { - result = false; - } - break; - case BitLibParityAlways1: - if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { - result = false; - } - break; - } - - if(!result) break; - } - return result; -} - -size_t bit_lib_add_parity( - const uint8_t* data, - size_t position, - uint8_t* dest, - size_t dest_position, - uint8_t source_length, - uint8_t parity_length, - BitLibParity parity) { - uint32_t parity_word = 0; - size_t j = 0, bit_count = 0; - for(int word = 0; word < source_length; word += parity_length - 1) { - for(int bit = 0; bit < parity_length - 1; bit++) { - parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit); - bit_lib_set_bit( - dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit)); - } - // if parity fails then return 0 - switch(parity) { - case BitLibParityAlways0: - bit_lib_set_bit(dest, dest_position + j++, 0); - break; // marker bit which should be a 0 - case BitLibParityAlways1: - bit_lib_set_bit(dest, dest_position + j++, 1); - break; // marker bit which should be a 1 - default: - bit_lib_set_bit( - dest, - dest_position + j++, - (bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1); - break; - } - bit_count += parity_length; - parity_word = 0; - } - // if we got here then all the parities passed - // return bit count - return bit_count; -} - -size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { - size_t counter = 0; - size_t result_counter = 0; - uint8_t bit_buffer = 0; - uint8_t bit_counter = 0; - - while(counter < length) { - if((counter + 1) % n != 0) { - bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter); - bit_counter++; - } - - if(bit_counter == 8) { - bit_lib_set_bits(data, position + result_counter, bit_buffer, 8); - bit_counter = 0; - bit_buffer = 0; - result_counter += 8; - } - counter++; - } - - if(bit_counter != 0) { - bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter); - result_counter += bit_counter; - } - return result_counter; -} - -void bit_lib_copy_bits( - uint8_t* data, - size_t position, - size_t length, - const uint8_t* source, - size_t source_position) { - for(size_t i = 0; i < length; ++i) { - bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i)); - } -} - -void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) { - size_t i = 0; - size_t j = length - 1; - - while(i < j) { - bool tmp = bit_lib_get_bit(data, position + i); - bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j)); - bit_lib_set_bit(data, position + j, tmp); - i++; - j--; - } -} - -uint8_t bit_lib_get_bit_count(uint32_t data) { -#if defined __GNUC__ - return __builtin_popcountl(data); -#else -#error Please, implement popcount for non-GCC compilers -#endif -} - -void bit_lib_print_bits(const uint8_t* data, size_t length) { - for(size_t i = 0; i < length; ++i) { - printf("%u", bit_lib_get_bit(data, i)); - } -} - -void bit_lib_print_regions( - const BitLibRegion* regions, - size_t region_count, - const uint8_t* data, - size_t length) { - // print data - bit_lib_print_bits(data, length); - printf("\r\n"); - - // print regions - for(size_t c = 0; c < length; ++c) { - bool print = false; - - for(size_t i = 0; i < region_count; i++) { - if(regions[i].start <= c && c < regions[i].start + regions[i].length) { - print = true; - printf("%c", regions[i].mark); - break; - } - } - - if(!print) { - printf(" "); - } - } - printf("\r\n"); - - // print regions data - for(size_t c = 0; c < length; ++c) { - bool print = false; - - for(size_t i = 0; i < region_count; i++) { - if(regions[i].start <= c && c < regions[i].start + regions[i].length) { - print = true; - printf("%u", bit_lib_get_bit(data, c)); - break; - } - } - - if(!print) { - printf(" "); - } - } - printf("\r\n"); -} - -uint16_t bit_lib_reverse_16_fast(uint16_t data) { - uint16_t result = 0; - result |= (data & 0x8000) >> 15; - result |= (data & 0x4000) >> 13; - result |= (data & 0x2000) >> 11; - result |= (data & 0x1000) >> 9; - result |= (data & 0x0800) >> 7; - result |= (data & 0x0400) >> 5; - result |= (data & 0x0200) >> 3; - result |= (data & 0x0100) >> 1; - result |= (data & 0x0080) << 1; - result |= (data & 0x0040) << 3; - result |= (data & 0x0020) << 5; - result |= (data & 0x0010) << 7; - result |= (data & 0x0008) << 9; - result |= (data & 0x0004) << 11; - result |= (data & 0x0002) << 13; - result |= (data & 0x0001) << 15; - return result; -} - -uint8_t bit_lib_reverse_8_fast(uint8_t byte) { - byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; - byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; - byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; - return byte; -} - -uint16_t bit_lib_crc8( - uint8_t const* data, - size_t data_size, - uint8_t polynom, - uint8_t init, - bool ref_in, - bool ref_out, - uint8_t xor_out) { - uint8_t crc = init; - - for(size_t i = 0; i < data_size; ++i) { - uint8_t byte = data[i]; - if(ref_in) bit_lib_reverse_bits(&byte, 0, 8); - crc ^= byte; - - for(size_t j = 8; j > 0; --j) { - if(crc & TOPBIT(8)) { - crc = (crc << 1) ^ polynom; - } else { - crc = (crc << 1); - } - } - } - - if(ref_out) bit_lib_reverse_bits(&crc, 0, 8); - crc ^= xor_out; - - return crc; -} - -uint16_t bit_lib_crc16( - uint8_t const* data, - size_t data_size, - uint16_t polynom, - uint16_t init, - bool ref_in, - bool ref_out, - uint16_t xor_out) { - uint16_t crc = init; - - for(size_t i = 0; i < data_size; ++i) { - uint8_t byte = data[i]; - if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8; - - for(size_t j = 0; j < 8; ++j) { - bool c15 = (crc >> 15 & 1); - bool bit = (byte >> (7 - j) & 1); - crc <<= 1; - if(c15 ^ bit) crc ^= polynom; - } - } - - if(ref_out) crc = bit_lib_reverse_16_fast(crc); - crc ^= xor_out; - - return crc; -} diff --git a/applications/main/nfc/helpers/bit_lib.h b/applications/main/nfc/helpers/bit_lib.h deleted file mode 100644 index 92533993c9..0000000000 --- a/applications/main/nfc/helpers/bit_lib.h +++ /dev/null @@ -1,281 +0,0 @@ -#pragma once -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define TOPBIT(X) (1 << ((X)-1)) - -typedef enum { - BitLibParityEven, - BitLibParityOdd, - BitLibParityAlways0, - BitLibParityAlways1, -} BitLibParity; - -/** @brief Increment and wrap around a value. - * @param index value to increment - * @param length wrap-around range - */ -#define bit_lib_increment_index(index, length) (index = (((index) + 1) % (length))) - -/** @brief Test if a bit is set. - * @param data value to test - * @param index bit index to test - */ -#define bit_lib_bit_is_set(data, index) (((data) & (1 << (index))) != 0) - -/** @brief Test if a bit is not set. - * @param data value to test - * @param index bit index to test - */ -#define bit_lib_bit_is_not_set(data, index) (((data) & (1 << (index))) == 0) - -/** @brief Push a bit into a byte array. - * @param data array to push bit into - * @param data_size array size - * @param bit bit to push - */ -void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit); - -/** @brief Set a bit in a byte array. - * @param data array to set bit in - * @param position The position of the bit to set. - * @param bit bit value to set - */ -void bit_lib_set_bit(uint8_t* data, size_t position, bool bit); - -/** @brief Set the bit at the given position to the given value. - * @param data The data to set the bit in. - * @param position The position of the bit to set. - * @param byte The data to set the bit to. - * @param length The length of the data. - */ -void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length); - -/** @brief Get the bit of a byte. - * @param data The byte to get the bits from. - * @param position The position of the bit. - * @return The bit. - */ -bool bit_lib_get_bit(const uint8_t* data, size_t position); - -/** - * @brief Get the bits of a data, as uint8_t. - * @param data The data to get the bits from. - * @param position The position of the first bit. - * @param length The length of the bits. - * @return The bits. - */ -uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length); - -/** - * @brief Get the bits of a data, as uint16_t. - * @param data The data to get the bits from. - * @param position The position of the first bit. - * @param length The length of the bits. - * @return The bits. - */ -uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length); - -/** - * @brief Get the bits of a data, as uint32_t. - * @param data The data to get the bits from. - * @param position The position of the first bit. - * @param length The length of the bits. - * @return The bits. - */ -uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length); - -/** - * @brief Get the bits of a data, as uint64_t. - * @param data The data to get the bits from. - * @param position The position of the first bit. - * @param length The length of the bits. - * @return The bits. - */ -uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length); - -/** - * @brief Test parity of given bits - * @param bits Bits to test parity of - * @param parity Parity to test against - * @return true if parity is correct, false otherwise - */ -bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity); - -/** - * @brief Test parity of bit array, check parity for every parity_length block from start - * - * @param data Bit array - * @param position Start position - * @param length Bit count - * @param parity Parity to test against - * @param parity_length Parity block length - * @return true - * @return false - */ -bool bit_lib_test_parity( - const uint8_t* data, - size_t position, - uint8_t length, - BitLibParity parity, - uint8_t parity_length); - -/** - * @brief Add parity to bit array - * - * @param data Source bit array - * @param position Start position - * @param dest Destination bit array - * @param dest_position Destination position - * @param source_length Source bit count - * @param parity_length Parity block length - * @param parity Parity to test against - * @return size_t - */ -size_t bit_lib_add_parity( - const uint8_t* data, - size_t position, - uint8_t* dest, - size_t dest_position, - uint8_t source_length, - uint8_t parity_length, - BitLibParity parity); - -/** - * @brief Remove bit every n in array and shift array left. Useful to remove parity. - * - * @param data Bit array - * @param position Start position - * @param length Bit count - * @param n every n bit will be removed - * @return size_t - */ -size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n); - -/** - * @brief Copy bits from source to destination. - * - * @param data destination array - * @param position position in destination array - * @param length length of bits to copy - * @param source source array - * @param source_position position in source array - */ -void bit_lib_copy_bits( - uint8_t* data, - size_t position, - size_t length, - const uint8_t* source, - size_t source_position); - -/** - * @brief Reverse bits in bit array - * - * @param data Bit array - * @param position start position - * @param length length of bits to reverse - */ -void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length); - -/** - * @brief Count 1 bits in data - * - * @param data - * @return uint8_t set bit count - */ -uint8_t bit_lib_get_bit_count(uint32_t data); - -/** - * @brief Print data as bit array - * - * @param data - * @param length - */ -void bit_lib_print_bits(const uint8_t* data, size_t length); - -typedef struct { - const char mark; - const size_t start; - const size_t length; -} BitLibRegion; - -/** - * @brief Print data as bit array and mark regions. Regions needs to be sorted by start position. - * - * @param regions - * @param region_count - * @param data - * @param length - */ -void bit_lib_print_regions( - const BitLibRegion* regions, - size_t region_count, - const uint8_t* data, - size_t length); - -/** - * @brief Reverse bits in uint16_t, faster than generic bit_lib_reverse_bits. - * - * @param data - * @return uint16_t - */ -uint16_t bit_lib_reverse_16_fast(uint16_t data); - -/** - * @brief Reverse bits in uint8_t, faster than generic bit_lib_reverse_bits. - * - * @param byte Byte - * @return uint8_t the reversed byte - */ -uint8_t bit_lib_reverse_8_fast(uint8_t byte); - -/** - * @brief Slow, but generic CRC8 implementation - * - * @param data - * @param data_size - * @param polynom CRC polynom - * @param init init value - * @param ref_in true if the right bit is older - * @param ref_out true to reverse output - * @param xor_out xor output with this value - * @return uint8_t - */ -uint16_t bit_lib_crc8( - uint8_t const* data, - size_t data_size, - uint8_t polynom, - uint8_t init, - bool ref_in, - bool ref_out, - uint8_t xor_out); - -/** - * @brief Slow, but generic CRC16 implementation - * - * @param data - * @param data_size - * @param polynom CRC polynom - * @param init init value - * @param ref_in true if the right bit is older - * @param ref_out true to reverse output - * @param xor_out xor output with this value - * @return uint16_t - */ -uint16_t bit_lib_crc16( - uint8_t const* data, - size_t data_size, - uint16_t polynom, - uint16_t init, - bool ref_in, - bool ref_out, - uint16_t xor_out); - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/nfc/helpers/transport.c b/applications/main/nfc/helpers/transport.c deleted file mode 100644 index 636dfe8b26..0000000000 --- a/applications/main/nfc/helpers/transport.c +++ /dev/null @@ -1,983 +0,0 @@ -#include "transport.h" -#include -#include -#define TAG "Transport parser" - -void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { - uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; - uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; - - datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; - - while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { - days -= furi_hal_rtc_get_days_per_year(datetime->year); - (datetime->year)++; - } - - datetime->month = 1; - while(days >= furi_hal_rtc_get_days_per_month( - FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month)) { - days -= furi_hal_rtc_get_days_per_month( - FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month); - (datetime->month)++; - } - - datetime->day = days + 1; - datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; - datetime->minute = - (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; - datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; -} - -void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) { - uint32_t timestamp = days * 24 * 60 * 60; - FuriHalRtcDateTime start_datetime = {0}; - start_datetime.year = start_year - 1; - start_datetime.month = 12; - start_datetime.day = 31; - timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); - timestamp_to_datetime(timestamp, datetime); -} - -void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, uint16_t start_year) { - uint32_t timestamp = minutes * 60; - FuriHalRtcDateTime start_datetime = {0}; - start_datetime.year = start_year - 1; - start_datetime.month = 12; - start_datetime.day = 31; - timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); - timestamp_to_datetime(timestamp, datetime); -} - -bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { - uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); - - FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); - - uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); - if(layout_type == 0xE) { - layout_type = bit_lib_get_bits_16(block->data, 52, 9); - } else if(layout_type == 0xF) { - layout_type = bit_lib_get_bits_16(block->data, 52, 14); - } - - FURI_LOG_D(TAG, "Layout type %x", layout_type); - - uint16_t card_view = 0; - uint16_t card_type = 0; - uint32_t card_number = 0; - uint8_t card_layout = 0; - uint8_t card_layout2 = 0; - uint16_t card_use_before_date = 0; - uint16_t card_blank_type = 0; - uint32_t card_start_trip_minutes = 0; - uint8_t card_minutes_pass = 0; - uint32_t card_remaining_funds = 0; - uint16_t card_validator = 0; - uint8_t card_blocked = 0; - uint32_t card_hash = 0; - - switch(layout_type) { - case 0x02: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint8_t card_benefit_code = bit_lib_get_bits(block->data, 72, 8); //124 - uint32_t card_rfu1 = bit_lib_get_bits_32(block->data, 80, 32); //rfu1 - uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 - card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 177, 12); //403 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 189, 16); //402 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 - uint8_t card_start_trip_seconds = bit_lib_get_bits(block->data, 189, 6); //406 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 - uint16_t card_use_with_date = bit_lib_get_bits_16(block->data, 189, 16); //205 - uint8_t card_route = bit_lib_get_bits(block->data, 205, 1); //424 - uint16_t card_validator1 = bit_lib_get_bits_16(block->data, 206, 15); //422.1 - card_validator = bit_lib_get_bits_16(block->data, 205, 16); //422 - uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 221, 16); //331 - uint8_t card_write_enabled = bit_lib_get_bits(block->data, 237, 1); //write_enabled - uint8_t card_rfu2 = bit_lib_get_bits(block->data, 238, 2); //rfu2 - uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_use_before_date, - card_benefit_code, - card_rfu1, - card_crc16, - card_blocked, - card_start_trip_time, - card_start_trip_date, - card_valid_from_date, - card_valid_by_date, - card_start_trip_seconds, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_use_with_date, - card_route, - card_validator1, - card_validator, - card_total_trips, - card_write_enabled, - card_rfu2, - card_crc16_2); - break; - } - case 0x06: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint8_t card_geozone_a = bit_lib_get_bits(block->data, 72, 4); //GeoZoneA - uint8_t card_geozone_b = bit_lib_get_bits(block->data, 76, 4); //GeoZoneB - card_blank_type = bit_lib_get_bits_16(block->data, 80, 10); //121. - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 90, 10); //122 - uint32_t card_rfu1 = bit_lib_get_bits_16(block->data, 100, 12); //rfu1 - uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 - card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 129, 12); //403 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 141, 16); //402 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 - uint16_t card_company = bit_lib_get_bits(block->data, 189, 4); //Company - uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 4); //422.1 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 197, 10); //321 - uint8_t card_units = bit_lib_get_bits(block->data, 207, 6); //Units - uint16_t card_validator2 = bit_lib_get_bits_16(block->data, 213, 10); //422.2 - uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 223, 16); //331 - uint8_t card_extended = bit_lib_get_bits(block->data, 239, 1); //123 - uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_use_before_date, - card_geozone_a, - card_geozone_b, - card_blank_type, - card_type_of_extended, - card_rfu1, - card_crc16, - card_blocked, - card_start_trip_time, - card_start_trip_date, - card_valid_from_date, - card_valid_by_date, - card_company, - card_validator1, - card_remaining_trips, - card_units, - card_validator2, - card_total_trips, - card_extended, - card_crc16_2); - card_validator = card_validator1 * 1024 + card_validator2; - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, - &card_start_trip_minutes_s, - 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_trips, - card_total_trips, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x08: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 - uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 - uint8_t card_rfu2 = bit_lib_get_bits(block->data, 153, 7); //rfu2 - uint8_t card_remaining_trips1 = bit_lib_get_bits(block->data, 160, 8); //321.1 - uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 168, 8); //321 - uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 2); //422.1 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 177, 15); //422 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - uint32_t card_rfu3 = bit_lib_get_bits_32(block->data, 224, 32); //rfu3 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx", - card_view, - card_type, - card_number, - card_use_before_date, - card_rfu1, - card_valid_from_date, - card_valid_for_days, - card_requires_activation, - card_rfu2, - card_remaining_trips1, - card_remaining_trips, - card_validator1, - card_validator, - card_hash, - card_valid_from_date, - card_rfu3); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); - - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_trips, - card_validator); - break; - } - case 0x0A: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 64, 12); //311 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 76, 19); //314 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 95, 1); //301 - card_start_trip_minutes = bit_lib_get_bits_32(block->data, 96, 19); //405 - card_minutes_pass = bit_lib_get_bits(block->data, 119, 7); //412 - uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 126, 2); //421.0 - uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 128, 8); //321 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 136, 16); //422 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 152, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 154, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 156, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 158, 2); //421.4 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_use_before_date, - card_valid_from_date, - card_valid_for_minutes, - card_requires_activation, - card_start_trip_minutes, - card_minutes_pass, - card_transport_type_flag, - card_remaining_trips, - card_validator, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_remaining_trips, - card_validator); - break; - } - case 0x0C: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 - uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 - uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 - uint16_t card_rfu2 = bit_lib_get_bits_16(block->data, 153, 13); //rfu2 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 - uint8_t card_transport_type = bit_lib_get_bits(block->data, 251, 2); //421 - uint8_t card_rfu3 = bit_lib_get_bits(block->data, 253, 2); //rfu3 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_use_before_date, - card_rfu1, - card_valid_from_date, - card_valid_for_days, - card_requires_activation, - card_rfu2, - card_remaining_trips, - card_validator, - card_start_trip_date, - card_start_trip_time, - card_transport_type, - card_rfu3, - card_transfer_in_metro); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, - &card_start_trip_minutes_s, - 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_remaining_trips, - card_validator); - break; - } - case 0x0D: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint8_t card_rfu1 = bit_lib_get_bits(block->data, 56, 8); //rfu1 - card_use_before_date = bit_lib_get_bits_16(block->data, 64, 16); //202 - uint16_t card_valid_for_time = bit_lib_get_bits_16(block->data, 80, 11); //316 - uint8_t card_rfu2 = bit_lib_get_bits(block->data, 91, 5); //rfu2 - uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->data, 96, 16); //202.2 - uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->data, 123, 11); //316.2 - uint8_t card_rfu3 = bit_lib_get_bits(block->data, 123, 5); //rfu3 - uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 - uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 - uint8_t card_rfu4 = bit_lib_get_bits(block->data, 153, 2); //rfu4 - uint8_t card_passage_5_minutes = bit_lib_get_bits(block->data, 155, 5); //413 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 160, 2); //421.1 - uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 162, 1); //431 - uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 163, 3); //433 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 - uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 - card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 251, 2); //421.2 - uint8_t card_rfu5 = bit_lib_get_bits(block->data, 253, 2); //rfu5 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_layout, - card_rfu1, - card_use_before_date, - card_valid_for_time, - card_rfu2, - card_use_before_date2, - card_valid_for_time2, - card_rfu3, - card_valid_from_date, - card_valid_for_days, - card_requires_activation, - card_rfu4, - card_passage_5_minutes, - card_transport_type1, - card_passage_in_metro, - card_passages_ground_transport, - card_remaining_trips, - card_validator, - card_start_trip_date, - card_start_trip_time, - card_transport_type2, - card_rfu5, - card_transfer_in_metro); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, - &card_start_trip_minutes_s, - 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_remaining_trips, - card_validator); - break; - } - case 0x1C1: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121. - card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 - uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 144, 16); //402 - uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 160, 11); //403 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 171, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 173, 2); //421.2 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 177, 1); //432 - uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 178, 1); //431 - uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 179, 3); //433 - card_minutes_pass = bit_lib_get_bits(block->data, 185, 8); //412. - card_remaining_funds = bit_lib_get_bits_32(block->data, 196, 19) / 100; //322 - uint8_t card_fare_trip = bit_lib_get_bits(block->data, 215, 2); //441 - card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 - uint8_t card_zoo = bit_lib_get_bits(block->data, 218, 1); //zoo - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_use_before_date, - card_blank_type, - card_validator, - card_start_trip_date, - card_start_trip_time, - card_transport_type1, - card_transport_type2, - card_transfer_in_metro, - card_passage_in_metro, - card_passages_ground_transport, - card_minutes_pass, - card_remaining_funds, - card_fare_trip, - card_blocked, - card_zoo, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C2: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 - card_use_before_date = bit_lib_get_bits_16(block->data, 71, 16); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 87, 10); //121. - uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 97, 16); //311 - uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 113, 9); //302 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 131, 20); //314 - card_minutes_pass = bit_lib_get_bits(block->data, 154, 8); //412. - uint8_t card_transport_type = bit_lib_get_bits(block->data, 163, 2); //421 - uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 165, 1); //431 - uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 166, 1); //432 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 167, 10); //321 - card_validator = bit_lib_get_bits_16(block->data, 177, 16); //422 - uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 196, 20); //404 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 216, 1); //301 - card_blocked = bit_lib_get_bits(block->data, 217, 1); //303 - uint8_t card_extended = bit_lib_get_bits(block->data, 218, 1); //123 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_type_of_extended, - card_use_before_date, - card_blank_type, - card_valid_to_date, - card_activate_during, - card_valid_for_minutes, - card_minutes_pass, - card_transport_type, - card_passage_in_metro, - card_transfer_in_metro, - card_remaining_trips, - card_validator, - card_start_trip_neg_minutes, - card_requires_activation, - card_blocked, - card_extended, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes - - card_start_trip_neg_minutes, - &card_start_trip_minutes_s, - 2016); //-time - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C3: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 - card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121 - card_remaining_funds = bit_lib_get_bits_32(block->data, 188, 22) / 100; //322 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 - card_start_trip_minutes = bit_lib_get_bits_32(block->data, 144, 23); //405 - uint8_t card_fare_trip = bit_lib_get_bits(block->data, 210, 2); //441 - card_minutes_pass = bit_lib_get_bits(block->data, 171, 7); //412 - uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 - card_blocked = bit_lib_get_bits(block->data, 212, 1); //303 - FURI_LOG_D( - TAG, - "Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_use_before_date, - card_blank_type, - card_remaining_funds, - card_hash, - card_validator, - card_start_trip_minutes, - card_fare_trip, - card_minutes_pass, - card_transport_type_flag, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_blocked); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_funds, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C4: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 - card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. - uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 94, 13); //311 - uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 107, 9); //302 - uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 116, 10); //304 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 - card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. - uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 - uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 - uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 - uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 - uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 169, 10); //321 - card_validator = bit_lib_get_bits_16(block->data, 179, 16); //422 - uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 195, 20); //404 - uint8_t card_requires_activation = bit_lib_get_bits(block->data, 215, 1); //301 - card_blocked = bit_lib_get_bits(block->data, 216, 1); //303 - uint8_t card_extended = bit_lib_get_bits(block->data, 217, 1); //123 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_type_of_extended, - card_use_before_date, - card_blank_type, - card_valid_to_date, - card_activate_during, - card_extension_counter, - card_valid_for_minutes, - card_minutes_pass, - card_transport_type_flag, - card_transport_type1, - card_transport_type2, - card_transport_type3, - card_transport_type4, - card_remaining_trips, - card_validator, - card_start_trip_neg_minutes, - card_requires_activation, - card_blocked, - card_extended, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - (card_use_before_date - 1) * 24 * 60 + card_valid_for_minutes - - card_start_trip_neg_minutes, - &card_start_trip_minutes_s, - 2016); //-time - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C5: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - card_use_before_date = bit_lib_get_bits_16(block->data, 61, 13); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 74, 10); //121. - uint32_t card_valid_to_time = bit_lib_get_bits_32(block->data, 84, 23); //317 - uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 107, 10); //304 - card_start_trip_minutes = bit_lib_get_bits_32(block->data, 128, 23); //405 - uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 151, 7); //414 - card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. - card_remaining_funds = bit_lib_get_bits_32(block->data, 167, 19) / 100; //322 - card_validator = bit_lib_get_bits_16(block->data, 186, 16); //422 - card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 - uint16_t card_route = bit_lib_get_bits_16(block->data, 204, 12); //424 - uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 216, 7); //433 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_use_before_date, - card_blank_type, - card_valid_to_time, - card_extension_counter, - card_start_trip_minutes, - card_metro_ride_with, - card_minutes_pass, - card_remaining_funds, - card_validator, - card_blocked, - card_route, - card_passages_ground_transport, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - - from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019); - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_remaining_funds, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x1C6: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 - uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 - card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. - card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. - uint32_t card_valid_from_date = bit_lib_get_bits_32(block->data, 94, 23); //311 - uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 117, 10); //304 - uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 - uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 148, 20); //404 - uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 168, 7); //414 - card_minutes_pass = bit_lib_get_bits(block->data, 175, 7); //412. - uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 182, 7); //321 - card_validator = bit_lib_get_bits_16(block->data, 189, 16); //422 - card_blocked = bit_lib_get_bits(block->data, 205, 1); //303 - uint8_t card_extended = bit_lib_get_bits(block->data, 206, 1); //123 - uint16_t card_route = bit_lib_get_bits_16(block->data, 212, 12); //424 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_layout2, - card_type_of_extended, - card_use_before_date, - card_blank_type, - card_valid_from_date, - card_extension_counter, - card_valid_for_minutes, - card_start_trip_neg_minutes, - card_metro_ride_with, - card_minutes_pass, - card_remaining_trips, - card_validator, - card_blocked, - card_extended, - card_route, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019); - - FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60, - &card_start_trip_minutes_s, - 2019); //-time - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_start_trip_minutes_s.day, - card_start_trip_minutes_s.month, - card_start_trip_minutes_s.year, - card_start_trip_minutes_s.hour, - card_start_trip_minutes_s.minute, - card_validator); - break; - } - case 0x3CCB: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code - uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 - uint8_t card_interval = bit_lib_get_bits(block->data, 98, 4); //interval - uint16_t card_app_code1 = bit_lib_get_bits_16(block->data, 102, 16); //app_code1 - uint16_t card_hash1 = bit_lib_get_bits_16(block->data, 112, 16); //502.1 - uint16_t card_type1 = bit_lib_get_bits_16(block->data, 128, 10); //type1 - uint16_t card_app_code2 = bit_lib_get_bits_16(block->data, 138, 10); //app_code2 - uint16_t card_type2 = bit_lib_get_bits_16(block->data, 148, 10); //type2 - uint16_t card_app_code3 = bit_lib_get_bits_16(block->data, 158, 10); //app_code3 - uint16_t card_type3 = bit_lib_get_bits_16(block->data, 148, 10); //type3 - uint16_t card_app_code4 = bit_lib_get_bits_16(block->data, 168, 10); //app_code4 - uint16_t card_type4 = bit_lib_get_bits_16(block->data, 178, 10); //type4 - card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502.2 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx", - card_view, - card_type, - card_number, - card_layout, - card_tech_code, - card_use_before_date, - card_blank_type, - card_valid_to_minutes, - card_valid_by_date, - card_interval, - card_app_code1, - card_hash1, - card_type1, - card_app_code2, - card_type2, - card_app_code3, - card_type3, - card_app_code4, - card_type4, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); - - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_validator); - break; - } - case 0x3C0B: { - card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 - card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 - card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 - card_layout = bit_lib_get_bits(block->data, 52, 4); //111 - uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code - uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 - uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 - uint16_t card_hash = bit_lib_get_bits_16(block->data, 112, 16); //502.1 - - FURI_LOG_D( - TAG, - "%x %x %lx %x %x %x %x %x %x %x", - card_view, - card_type, - card_number, - card_layout, - card_tech_code, - card_use_before_date, - card_blank_type, - card_valid_to_minutes, - card_valid_by_date, - card_hash); - FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); - - furi_string_printf( - result, - "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", - card_number, - card_use_before_date_s.day, - card_use_before_date_s.month, - card_use_before_date_s.year, - card_validator); - break; - } - default: - return false; - } - return true; -} \ No newline at end of file diff --git a/applications/main/nfc/helpers/transport.h b/applications/main/nfc/helpers/transport.h deleted file mode 100644 index ae31a9e56b..0000000000 --- a/applications/main/nfc/helpers/transport.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include -#include -#include - -bool parse_transport_block(const MfClassicBlock* block, FuriString* result); \ No newline at end of file From 9c92338ddfca9d1e8c1936882d25de2a39be9e4a Mon Sep 17 00:00:00 2001 From: assasinfil Date: Mon, 20 Nov 2023 19:11:30 +0300 Subject: [PATCH 057/111] Added social card parser --- applications/main/nfc/application.fam | 9 + .../plugins/supported_cards/social_moscow.c | 1593 +++++++++++++++++ 2 files changed, 1602 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/social_moscow.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 33a2011a70..c49170d081 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -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, diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c new file mode 100644 index 0000000000..c8858dcf6b --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -0,0 +1,1593 @@ +#include "nfc_supported_card_plugin.h" +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "Social Moscow" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} SocialMoscowCardConfig; + +static const MfClassicKeyPair social_moscow_4k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, + {.a = 0x40ead80721ce, .b = 0x100533b89331}, {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, +}; + +#define TOPBIT(X) (1 << ((X)-1)) + +typedef enum { + BitLibParityEven, + BitLibParityOdd, + BitLibParityAlways0, + BitLibParityAlways1, +} BitLibParity; + +typedef struct { + const char mark; + const size_t start; + const size_t length; +} BitLibRegion; + +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) { + size_t last_index = data_size - 1; + + for(size_t i = 0; i < last_index; ++i) { + data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1); + } + data[last_index] = (data[last_index] << 1) | bit; +} + +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = (length - 1) - i; + bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610 + } +} + +bool bit_lib_get_bit(const uint8_t* data, size_t position) { + return (data[position / 8] >> (7 - (position % 8))) & 1; +} + +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { + uint8_t shift = position % 8; + if(shift == 0) { + return data[position / 8] >> (8 - length); + } else { + // TODO fix read out of bounds + uint8_t value = (data[position / 8] << (shift)); + value |= data[position / 8 + 1] >> (8 - shift); + value = value >> (8 - length); + return value; + } +} + +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) { + uint16_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } + return value; +} + +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) { + uint32_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else { + value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } + + return value; +} + +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) { + uint64_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else if(length <= 32) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } else { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); + value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56); + value |= (uint64_t)bit_lib_get_bits(data, position + 56, 8) << (length - 64); + value |= bit_lib_get_bits(data, position + 64, length - 64); + } + + return value; +} + +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { +#if !defined __GNUC__ +#error Please, implement parity test for non-GCC compilers +#else + switch(parity) { + case BitLibParityEven: + return __builtin_parity(bits); + case BitLibParityOdd: + return !__builtin_parity(bits); + default: + furi_crash("Unknown parity"); + } +#endif +} + +bool bit_lib_test_parity( + const uint8_t* bits, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length) { + uint32_t parity_block; + bool result = true; + const size_t parity_blocks_count = length / parity_length; + + for(size_t i = 0; i < parity_blocks_count; ++i) { + switch(parity) { + case BitLibParityEven: + case BitLibParityOdd: + parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length); + if(!bit_lib_test_parity_32(parity_block, parity)) { + result = false; + } + break; + case BitLibParityAlways0: + if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + case BitLibParityAlways1: + if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + } + + if(!result) break; + } + return result; +} + +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity) { + uint32_t parity_word = 0; + size_t j = 0, bit_count = 0; + for(int word = 0; word < source_length; word += parity_length - 1) { + for(int bit = 0; bit < parity_length - 1; bit++) { + parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit); + bit_lib_set_bit( + dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit)); + } + // if parity fails then return 0 + switch(parity) { + case BitLibParityAlways0: + bit_lib_set_bit(dest, dest_position + j++, 0); + break; // marker bit which should be a 0 + case BitLibParityAlways1: + bit_lib_set_bit(dest, dest_position + j++, 1); + break; // marker bit which should be a 1 + default: + bit_lib_set_bit( + dest, + dest_position + j++, + (bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1); + break; + } + bit_count += parity_length; + parity_word = 0; + } + // if we got here then all the parities passed + // return bit count + return bit_count; +} + +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { + size_t counter = 0; + size_t result_counter = 0; + uint8_t bit_buffer = 0; + uint8_t bit_counter = 0; + + while(counter < length) { + if((counter + 1) % n != 0) { + bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter); + bit_counter++; + } + + if(bit_counter == 8) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, 8); + bit_counter = 0; + bit_buffer = 0; + result_counter += 8; + } + counter++; + } + + if(bit_counter != 0) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter); + result_counter += bit_counter; + } + return result_counter; +} + +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position) { + for(size_t i = 0; i < length; ++i) { + bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i)); + } +} + +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) { + size_t i = 0; + size_t j = length - 1; + + while(i < j) { + bool tmp = bit_lib_get_bit(data, position + i); + bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j)); + bit_lib_set_bit(data, position + j, tmp); + i++; + j--; + } +} + +uint8_t bit_lib_get_bit_count(uint32_t data) { +#if defined __GNUC__ + return __builtin_popcountl(data); +#else +#error Please, implement popcount for non-GCC compilers +#endif +} + +void bit_lib_print_bits(const uint8_t* data, size_t length) { + for(size_t i = 0; i < length; ++i) { + printf("%u", bit_lib_get_bit(data, i)); + } +} + +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length) { + // print data + bit_lib_print_bits(data, length); + printf("\r\n"); + + // print regions + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%c", regions[i].mark); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); + + // print regions data + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%u", bit_lib_get_bit(data, c)); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); +} + +uint16_t bit_lib_reverse_16_fast(uint16_t data) { + uint16_t result = 0; + result |= (data & 0x8000) >> 15; + result |= (data & 0x4000) >> 13; + result |= (data & 0x2000) >> 11; + result |= (data & 0x1000) >> 9; + result |= (data & 0x0800) >> 7; + result |= (data & 0x0400) >> 5; + result |= (data & 0x0200) >> 3; + result |= (data & 0x0100) >> 1; + result |= (data & 0x0080) << 1; + result |= (data & 0x0040) << 3; + result |= (data & 0x0020) << 5; + result |= (data & 0x0010) << 7; + result |= (data & 0x0008) << 9; + result |= (data & 0x0004) << 11; + result |= (data & 0x0002) << 13; + result |= (data & 0x0001) << 15; + return result; +} + +uint8_t bit_lib_reverse_8_fast(uint8_t byte) { + byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; + byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; + byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; + return byte; +} + +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out) { + uint8_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) bit_lib_reverse_bits(&byte, 0, 8); + crc ^= byte; + + for(size_t j = 8; j > 0; --j) { + if(crc & TOPBIT(8)) { + crc = (crc << 1) ^ polynom; + } else { + crc = (crc << 1); + } + } + } + + if(ref_out) bit_lib_reverse_bits(&crc, 0, 8); + crc ^= xor_out; + + return crc; +} + +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out) { + uint16_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8; + + for(size_t j = 0; j < 8; ++j) { + bool c15 = (crc >> 15 & 1); + bool bit = (byte >> (7 - j) & 1); + crc <<= 1; + if(c15 ^ bit) crc ^= polynom; + } + } + + if(ref_out) crc = bit_lib_reverse_16_fast(crc); + crc ^= xor_out; + + return crc; +} + +#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 +#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) +#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) +#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 +#define FURI_HAL_RTC_IS_LEAP_YEAR(year) \ + ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) + +void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { + uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; + uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; + + datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; + + while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { + days -= furi_hal_rtc_get_days_per_year(datetime->year); + (datetime->year)++; + } + + datetime->month = 1; + while(days >= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month)) { + days -= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month); + (datetime->month)++; + } + + datetime->day = days + 1; + datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; + datetime->minute = + (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; + datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; +} + +void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = days * 24 * 60 * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { + uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); + + FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); + + uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); + if(layout_type == 0xE) { + layout_type = bit_lib_get_bits_16(block->data, 52, 9); + } else if(layout_type == 0xF) { + layout_type = bit_lib_get_bits_16(block->data, 52, 14); + } + + FURI_LOG_D(TAG, "Layout type %x", layout_type); + + uint16_t card_view = 0; + uint16_t card_type = 0; + uint32_t card_number = 0; + uint8_t card_layout = 0; + uint8_t card_layout2 = 0; + uint16_t card_use_before_date = 0; + uint16_t card_blank_type = 0; + uint32_t card_start_trip_minutes = 0; + uint8_t card_minutes_pass = 0; + uint32_t card_remaining_funds = 0; + uint16_t card_validator = 0; + uint8_t card_blocked = 0; + uint32_t card_hash = 0; + + switch(layout_type) { + case 0x02: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_benefit_code = bit_lib_get_bits(block->data, 72, 8); //124 + uint32_t card_rfu1 = bit_lib_get_bits_32(block->data, 80, 32); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 177, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 189, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint8_t card_start_trip_seconds = bit_lib_get_bits(block->data, 189, 6); //406 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_use_with_date = bit_lib_get_bits_16(block->data, 189, 16); //205 + uint8_t card_route = bit_lib_get_bits(block->data, 205, 1); //424 + uint16_t card_validator1 = bit_lib_get_bits_16(block->data, 206, 15); //422.1 + card_validator = bit_lib_get_bits_16(block->data, 205, 16); //422 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 221, 16); //331 + uint8_t card_write_enabled = bit_lib_get_bits(block->data, 237, 1); //write_enabled + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 238, 2); //rfu2 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_benefit_code, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_start_trip_seconds, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_use_with_date, + card_route, + card_validator1, + card_validator, + card_total_trips, + card_write_enabled, + card_rfu2, + card_crc16_2); + break; + } + case 0x06: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_geozone_a = bit_lib_get_bits(block->data, 72, 4); //GeoZoneA + uint8_t card_geozone_b = bit_lib_get_bits(block->data, 76, 4); //GeoZoneB + card_blank_type = bit_lib_get_bits_16(block->data, 80, 10); //121. + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 90, 10); //122 + uint32_t card_rfu1 = bit_lib_get_bits_16(block->data, 100, 12); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 129, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 141, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint16_t card_company = bit_lib_get_bits(block->data, 189, 4); //Company + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 4); //422.1 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 197, 10); //321 + uint8_t card_units = bit_lib_get_bits(block->data, 207, 6); //Units + uint16_t card_validator2 = bit_lib_get_bits_16(block->data, 213, 10); //422.2 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 223, 16); //331 + uint8_t card_extended = bit_lib_get_bits(block->data, 239, 1); //123 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_geozone_a, + card_geozone_b, + card_blank_type, + card_type_of_extended, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_company, + card_validator1, + card_remaining_trips, + card_units, + card_validator2, + card_total_trips, + card_extended, + card_crc16_2); + card_validator = card_validator1 * 1024 + card_validator2; + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_total_trips, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x08: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 153, 7); //rfu2 + uint8_t card_remaining_trips1 = bit_lib_get_bits(block->data, 160, 8); //321.1 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 168, 8); //321 + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 2); //422.1 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 177, 15); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint32_t card_rfu3 = bit_lib_get_bits_32(block->data, 224, 32); //rfu3 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips1, + card_remaining_trips, + card_validator1, + card_validator, + card_hash, + card_valid_from_date, + card_rfu3); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_validator); + break; + } + case 0x0A: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 64, 12); //311 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 76, 19); //314 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 95, 1); //301 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 96, 19); //405 + card_minutes_pass = bit_lib_get_bits(block->data, 119, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 126, 2); //421.0 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 128, 8); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 136, 16); //422 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 152, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 154, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 156, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 158, 2); //421.4 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_valid_from_date, + card_valid_for_minutes, + card_requires_activation, + card_start_trip_minutes, + card_minutes_pass, + card_transport_type_flag, + card_remaining_trips, + card_validator, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0C: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint16_t card_rfu2 = bit_lib_get_bits_16(block->data, 153, 13); //rfu2 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type = bit_lib_get_bits(block->data, 251, 2); //421 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 253, 2); //rfu3 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type, + card_rfu3, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0D: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint8_t card_rfu1 = bit_lib_get_bits(block->data, 56, 8); //rfu1 + card_use_before_date = bit_lib_get_bits_16(block->data, 64, 16); //202 + uint16_t card_valid_for_time = bit_lib_get_bits_16(block->data, 80, 11); //316 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 91, 5); //rfu2 + uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->data, 96, 16); //202.2 + uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->data, 123, 11); //316.2 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 123, 5); //rfu3 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu4 = bit_lib_get_bits(block->data, 153, 2); //rfu4 + uint8_t card_passage_5_minutes = bit_lib_get_bits(block->data, 155, 5); //413 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 160, 2); //421.1 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 162, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 163, 3); //433 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 251, 2); //421.2 + uint8_t card_rfu5 = bit_lib_get_bits(block->data, 253, 2); //rfu5 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_rfu1, + card_use_before_date, + card_valid_for_time, + card_rfu2, + card_use_before_date2, + card_valid_for_time2, + card_rfu3, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu4, + card_passage_5_minutes, + card_transport_type1, + card_passage_in_metro, + card_passages_ground_transport, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type2, + card_rfu5, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x1C1: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121. + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 144, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 160, 11); //403 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 171, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 173, 2); //421.2 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 177, 1); //432 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 178, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 179, 3); //433 + card_minutes_pass = bit_lib_get_bits(block->data, 185, 8); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 196, 19) / 100; //322 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 215, 2); //441 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint8_t card_zoo = bit_lib_get_bits(block->data, 218, 1); //zoo + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type1, + card_transport_type2, + card_transfer_in_metro, + card_passage_in_metro, + card_passages_ground_transport, + card_minutes_pass, + card_remaining_funds, + card_fare_trip, + card_blocked, + card_zoo, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C2: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 87, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 97, 16); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 113, 9); //302 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 131, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 154, 8); //412. + uint8_t card_transport_type = bit_lib_get_bits(block->data, 163, 2); //421 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 165, 1); //431 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 166, 1); //432 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 167, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 177, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 196, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 216, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 217, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 218, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type, + card_passage_in_metro, + card_transfer_in_metro, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C3: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121 + card_remaining_funds = bit_lib_get_bits_32(block->data, 188, 22) / 100; //322 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 144, 23); //405 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 210, 2); //441 + card_minutes_pass = bit_lib_get_bits(block->data, 171, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + card_blocked = bit_lib_get_bits(block->data, 212, 1); //303 + FURI_LOG_D( + TAG, + "Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_remaining_funds, + card_hash, + card_validator, + card_start_trip_minutes, + card_fare_trip, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_blocked); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C4: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 94, 13); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 107, 9); //302 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 116, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 169, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 179, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 195, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 215, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 216, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 217, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_extension_counter, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_use_before_date + 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2011); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C5: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 74, 10); //121. + uint32_t card_valid_to_time = bit_lib_get_bits_32(block->data, 84, 23); //317 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 107, 10); //304 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 128, 23); //405 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 151, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 167, 19) / 100; //322 + card_validator = bit_lib_get_bits_16(block->data, 186, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint16_t card_route = bit_lib_get_bits_16(block->data, 204, 12); //424 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 216, 7); //433 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_valid_to_time, + card_extension_counter, + card_start_trip_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_funds, + card_validator, + card_blocked, + card_route, + card_passages_ground_transport, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C6: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint32_t card_valid_from_date = bit_lib_get_bits_32(block->data, 94, 23); //311 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 117, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 148, 20); //404 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 168, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 175, 7); //412. + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 182, 7); //321 + card_validator = bit_lib_get_bits_16(block->data, 189, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 205, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 206, 1); //123 + uint16_t card_route = bit_lib_get_bits_16(block->data, 212, 12); //424 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_from_date, + card_extension_counter, + card_valid_for_minutes, + card_start_trip_neg_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_trips, + card_validator, + card_blocked, + card_extended, + card_route, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60, + &card_start_trip_minutes_s, + 2019); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x3CCB: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint8_t card_interval = bit_lib_get_bits(block->data, 98, 4); //interval + uint16_t card_app_code1 = bit_lib_get_bits_16(block->data, 102, 16); //app_code1 + uint16_t card_hash1 = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + uint16_t card_type1 = bit_lib_get_bits_16(block->data, 128, 10); //type1 + uint16_t card_app_code2 = bit_lib_get_bits_16(block->data, 138, 10); //app_code2 + uint16_t card_type2 = bit_lib_get_bits_16(block->data, 148, 10); //type2 + uint16_t card_app_code3 = bit_lib_get_bits_16(block->data, 158, 10); //app_code3 + uint16_t card_type3 = bit_lib_get_bits_16(block->data, 148, 10); //type3 + uint16_t card_app_code4 = bit_lib_get_bits_16(block->data, 168, 10); //app_code4 + uint16_t card_type4 = bit_lib_get_bits_16(block->data, 178, 10); //type4 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_interval, + card_app_code1, + card_hash1, + card_type1, + card_app_code2, + card_type2, + card_app_code3, + card_type3, + card_app_code4, + card_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + case 0x3C0B: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint16_t card_hash = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + default: + return false; + } + + return true; +} + +static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType4k) { + config->data_sector = 15; + config->keys = social_moscow_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.data_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, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool social_moscow_verify(Nfc* nfc) { + return social_moscow_verify_type(nfc, MfClassicType4k); +} + +static bool social_moscow_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; + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24); + uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8); + uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40); + uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4); + uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64); + uint8_t year = data->block[60].data[11]; + uint8_t month = data->block[60].data[12]; + + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + + parse_transport_block(&data->block[4], metro_result); + parse_transport_block(&data->block[16], ground_result); + + furi_string_printf( + parsed_data, + "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n%s\n%s", + card_code, + card_region, + card_number, + card_control, + omc_number, + month, + year, + data->block[60].data[13], + data->block[60].data[14], + furi_string_get_cstr(metro_result), + furi_string_get_cstr(ground_result)); + furi_string_free(metro_result); + furi_string_free(ground_result); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin social_moscow_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = social_moscow_verify, + .read = social_moscow_read, + .parse = social_moscow_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &social_moscow_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* social_moscow_plugin_ep() { + return &social_moscow_plugin_descriptor; +} From 00b1018e9ea8bf052ab9b96b152e007d04a69808 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Mon, 20 Nov 2023 20:46:51 +0300 Subject: [PATCH 058/111] Fixed validating --- .../plugins/supported_cards/social_moscow.c | 53 ++++++++++--------- .../main/nfc/plugins/supported_cards/troika.c | 35 ++++++------ 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index c8858dcf6b..56cb88d45c 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -649,11 +649,11 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_crc16_2); card_validator = card_validator1 * 1024 + card_validator2; FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + (card_start_trip_date) * 24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -711,7 +711,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_valid_from_date, card_rfu3); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); furi_string_printf( result, @@ -1483,7 +1483,8 @@ static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { } static bool social_moscow_verify(Nfc* nfc) { - return social_moscow_verify_type(nfc, MfClassicType4k); + return social_moscow_verify_type(nfc, MfClassicType1k) || + social_moscow_verify_type(nfc, MfClassicType4k); } static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { @@ -1546,27 +1547,29 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data FuriString* metro_result = furi_string_alloc(); FuriString* ground_result = furi_string_alloc(); - - parse_transport_block(&data->block[4], metro_result); - parse_transport_block(&data->block[16], ground_result); - - furi_string_printf( - parsed_data, - "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n%s\n%s", - card_code, - card_region, - card_number, - card_control, - omc_number, - month, - year, - data->block[60].data[13], - data->block[60].data[14], - furi_string_get_cstr(metro_result), - furi_string_get_cstr(ground_result)); - furi_string_free(metro_result); - furi_string_free(ground_result); - parsed = true; + bool result1 = parse_transport_block(&data->block[4], metro_result); + bool result2 = parse_transport_block(&data->block[16], ground_result); + if(result1 || result2) { + furi_string_printf( + parsed_data, + "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n\e#Metro\n%s\n\e#Ground\n%s", + card_code, + card_region, + card_number, + card_control, + omc_number, + month, + year, + data->block[60].data[13], + data->block[60].data[14], + furi_string_get_cstr(metro_result), + furi_string_get_cstr(ground_result)); + furi_string_free(metro_result); + furi_string_free(ground_result); + parsed = true; + } else { + return false; + } } while(false); return parsed; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 5d0d6d0c0a..24101ed8e3 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1276,8 +1276,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019); + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2019); furi_string_printf( result, "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", @@ -1340,11 +1339,11 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_route, card_hash); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60, + card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes, &card_start_trip_minutes_s, 2019); //-time furi_string_printf( @@ -1465,10 +1464,10 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) bool success = true; if(type == MfClassicType1k) { - config->data_sector = 8; + config->data_sector = 11; config->keys = troika_1k_keys; } else if(type == MfClassicType4k) { - config->data_sector = 4; + config->data_sector = 11; config->keys = troika_4k_keys; } else { success = false; @@ -1574,16 +1573,20 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { FuriString* metro_result = furi_string_alloc(); FuriString* ground_result = furi_string_alloc(); - parse_transport_block(&data->block[32], metro_result); - parse_transport_block(&data->block[28], ground_result); - furi_string_printf( - parsed_data, - "\e#Troika\n%s\n%s", - furi_string_get_cstr(metro_result), - furi_string_get_cstr(ground_result)); - furi_string_free(metro_result); - furi_string_free(ground_result); - parsed = true; + bool result1 = parse_transport_block(&data->block[32], metro_result); + bool result2 = parse_transport_block(&data->block[28], ground_result); + if(result1 || result2) { + furi_string_printf( + parsed_data, + "\e#Troika\n%s\n\e#Ediniy\n%s\n\e#TAT\n", + furi_string_get_cstr(metro_result), + furi_string_get_cstr(ground_result)); + furi_string_free(metro_result); + furi_string_free(ground_result); + parsed = true; + } else { + return false; + } } while(false); return parsed; From 4eb40ce948085da51bcda439bcd02ff377f23652 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 20 Nov 2023 23:25:29 +0300 Subject: [PATCH 059/111] fix ibutton info display by @krolchonok + added missing furi_string_free https://github.com/krolchonok/unlsh/blob/a378db66630f1a16806df6e10bc21f93aa1509f7/applications/main/ibutton/scenes/ibutton_scene_info.c --- .../main/ibutton/scenes/ibutton_scene_info.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index dcdee9557a..50dc328a5f 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -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\n[%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, 40, 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) { From 91d614dd259b0f1721065a502e4133df52650afc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Nov 2023 01:00:15 +0300 Subject: [PATCH 060/111] add subghz honeywell protocol by htotoo https://github.com/Flipper-XFW/Xtreme-Firmware/commits?author=htotoo --- lib/subghz/protocols/honeywell.c | 357 ++++++++++++++++++++++++++ lib/subghz/protocols/honeywell.h | 61 +++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 4 files changed, 420 insertions(+) create mode 100644 lib/subghz/protocols/honeywell.c create mode 100644 lib/subghz/protocols/honeywell.h diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c new file mode 100644 index 0000000000..5fdc7f45c8 --- /dev/null +++ b/lib/subghz/protocols/honeywell.c @@ -0,0 +1,357 @@ +#include "honeywell.h" +#include + +//Created by HTotoo 2023-10-30 +//Got a lot of help from LiQuiDz. +//Protocol decoding help from: https://github.com/merbanan/rtl_433/blob/master/src/devices/honeywell.c + +/* +64 bit packets, repeated multiple times per open/close event. + +Protocol whitepaper: "DEFCON 22: Home Insecurity" by Logan Lamb. + +Data layout: + + PP PP C IIIII EE SS SS + +- P: 16bit Preamble and sync bit (always ff fe) +- C: 4bit Channel +- I: 20bit Device serial number / or counter value +- E: 8bit Event, where 0x80 = Open/Close, 0x04 = Heartbeat / or id +- S: 16bit CRC +*/ + +#define TAG "SubGhzProtocolHoneywell" + +uint16_t subghz_protocol_honeywell_crc16( + uint8_t const message[], + unsigned nBytes, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + unsigned byte, bit; + + for(byte = 0; byte < nBytes; ++byte) { + remainder ^= message[byte] << 8; + for(bit = 0; bit < 8; ++bit) { + if(remainder & 0x8000) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +void subghz_protocol_decoder_honeywell_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + free(instance); +} + +void subghz_protocol_decoder_honeywell_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; +} + +void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { + SubGhzProtocolDecoderHoneywell* instance = context; + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + + uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFFFF; + //can be multiple, since flipper can't read it well.. + if(preamble == 0b0011111111111110 || preamble == 0b0111111111111110 || + preamble == 0b1111111111111110) { + uint8_t datatocrc[4]; + datatocrc[0] = (instance->decoder.decode_data >> 40) & 0xFFFF; + datatocrc[1] = (instance->decoder.decode_data >> 32) & 0xFFFF; + datatocrc[2] = (instance->decoder.decode_data >> 24) & 0xFFFF; + datatocrc[3] = (instance->decoder.decode_data >> 16) & 0xFFFF; + uint8_t channel = (instance->decoder.decode_data >> 44) & 0xF; + uint16_t crc_calc = 0; + if(channel == 0x2 || channel == 0x4 || channel == 0xA) { + // 2GIG brand + crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8050, 0); + } else { // channel == 0x8 + crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8005, 0); + } + uint16_t crc = instance->decoder.decode_data & 0xFFFF; + if(crc == crc_calc) { + //the data is good. process it. + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + instance->decoder + .decode_count_bit; //maybe set it to 64, and hack the first 2 bits to 1! will see if replay needs it + instance->generic.serial = (instance->decoder.decode_data >> 24) & 0xFFFFF; + instance->generic.btn = (instance->decoder.decode_data >> 16) & + 0xFF; //not exactly button, but can contain btn data too. + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + return; + } + } +} + +void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + + ManchesterEvent event = ManchesterEventReset; + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < + subghz_protocol_honeywell_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < + subghz_protocol_honeywell_const.te_delta * 2) { + event = ManchesterEventLongLow; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < + subghz_protocol_honeywell_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < + subghz_protocol_honeywell_const.te_delta * 2) { + event = ManchesterEventLongHigh; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + if(data_ok) { + subghz_protocol_decoder_honeywell_addbit(instance, data); + } + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } +} + +uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_honeywell_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + + uint8_t channel = (instance->generic.data >> 44) & 0xF; + uint8_t contact = (instance->generic.btn & 0x80) >> 7; + uint8_t tamper = (instance->generic.btn & 0x40) >> 6; + uint8_t reed = (instance->generic.btn & 0x20) >> 5; + uint8_t alarm = (instance->generic.btn & 0x10) >> 4; + uint8_t battery_low = (instance->generic.btn & 0x08) >> 3; + uint8_t heartbeat = (instance->generic.btn & 0x04) >> 2; + + furi_string_cat_printf( + output, + "%s\r\n%dbit " + "Sn:%07lu\r\nCh:%u Bat:%d Hb: %d\r\n" + "L1: %u, L2: %u, L3: %u, L4: %u\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.serial, + channel, + battery_low, + heartbeat, + contact, + reed, + alarm, + tamper); +} + +void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHoneywell* instance = malloc(sizeof(SubGhzProtocolDecoderHoneywell)); + instance->base.protocol = &subghz_protocol_honeywell; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void* subghz_protocol_encoder_honeywell_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHoneywell* instance = malloc(sizeof(SubGhzProtocolEncoderHoneywell)); + + instance->base.protocol = &subghz_protocol_honeywell; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 3; + instance->encoder.size_upload = 64 * 2 + 10; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_honeywell_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHoneywell* instance = context; + free(instance->encoder.upload); + free(instance); +} +static LevelDuration + subghz_protocol_encoder_honeywell_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_honeywell_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_honeywell_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_honeywell_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_honeywell_const.te_short; + data.level = true; + break; + + default: + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); + break; + } + return level_duration_make(data.level, data.duration); +} + +static void + subghz_protocol_encoder_honeywell_get_upload(SubGhzProtocolEncoderHoneywell* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + for(uint8_t i = 63; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_honeywell_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_honeywell_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_honeywell_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.size_upload = index; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHoneywell* instance = context; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + do { + if(SubGhzProtocolStatusOk != + subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_honeywell_get_upload(instance); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + instance->encoder.is_running = true; + + res = SubGhzProtocolStatusOk; + } while(false); + + return res; +} + +void subghz_protocol_encoder_honeywell_stop(void* context) { + SubGhzProtocolEncoderHoneywell* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_honeywell_yield(void* context) { + SubGhzProtocolEncoderHoneywell* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + return ret; +} + +const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder = { + .alloc = subghz_protocol_decoder_honeywell_alloc, + .free = subghz_protocol_decoder_honeywell_free, + .feed = subghz_protocol_decoder_honeywell_feed, + .reset = subghz_protocol_decoder_honeywell_reset, + .get_hash_data = subghz_protocol_decoder_honeywell_get_hash_data, + .serialize = subghz_protocol_decoder_honeywell_serialize, + .deserialize = subghz_protocol_decoder_honeywell_deserialize, + .get_string = subghz_protocol_decoder_honeywell_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder = { + .alloc = subghz_protocol_encoder_honeywell_alloc, + .free = subghz_protocol_encoder_honeywell_free, + .deserialize = subghz_protocol_encoder_honeywell_deserialize, + .stop = subghz_protocol_encoder_honeywell_stop, + .yield = subghz_protocol_encoder_honeywell_yield, +}; + +const SubGhzProtocol subghz_protocol_honeywell = { + .name = SUBGHZ_PROTOCOL_HONEYWELL_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + .encoder = &subghz_protocol_honeywell_encoder, + .decoder = &subghz_protocol_honeywell_decoder, + +}; diff --git a/lib/subghz/protocols/honeywell.h b/lib/subghz/protocols/honeywell.h new file mode 100644 index 0000000000..8aa35ce6cc --- /dev/null +++ b/lib/subghz/protocols/honeywell.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include +#include +#include +#include "base.h" +#include "../blocks/generic.h" +#include +#include +#include + +#define SUBGHZ_PROTOCOL_HONEYWELL_NAME "Honeywell Sec" + +typedef struct SubGhzProtocolDecoderHoneywell SubGhzProtocolDecoderHoneywell; +typedef struct SubGhzProtocolEncoderHoneywell SubGhzProtocolEncoderHoneywell; + +extern const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder; +extern const SubGhzProtocol subghz_protocol_honeywell; + +void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment); + +void subghz_protocol_decoder_honeywell_free(void* context); + +void subghz_protocol_decoder_honeywell_reset(void* context); + +void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration); + +uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context); + +SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +SubGhzProtocolStatus + subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format); + +void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output); + +static const SubGhzBlockConst subghz_protocol_honeywell_const = { + .te_long = 280, + .te_short = 143, + .te_delta = 51, + .min_count_bit_for_found = 62, +}; + +struct SubGhzProtocolDecoderHoneywell { + SubGhzProtocolDecoderBase base; + SubGhzBlockGeneric generic; + SubGhzBlockDecoder decoder; + ManchesterState manchester_saved_state; +}; + +struct SubGhzProtocolEncoderHoneywell { + SubGhzProtocolEncoderBase base; + SubGhzBlockGeneric generic; + SubGhzProtocolBlockEncoder encoder; +}; diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 472a354e38..ca66d09111 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -44,6 +44,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_kinggates_stylo_4k, &subghz_protocol_bin_raw, &subghz_protocol_mastercode, + &subghz_protocol_honeywell, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index c5a090e993..53c479637f 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -45,3 +45,4 @@ #include "kinggates_stylo_4k.h" #include "bin_raw.h" #include "mastercode.h" +#include "honeywell.h" From c6663684462f2a093a799a8801c34053673c34d6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Nov 2023 01:53:20 +0300 Subject: [PATCH 061/111] add simple nfc file name display temp fix for UI --- .../main/nfc/helpers/protocol_support/nfc_protocol_support.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 87fbe9f082..6ad4a22998 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -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( From 1a21f0e3c99d53a3f29ab271faa4dcbf174ea785 Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 21 Nov 2023 12:21:22 +0900 Subject: [PATCH 062/111] Umarsh transport cards parser added --- applications/main/nfc/application.fam | 9 ++ .../main/nfc/plugins/supported_cards/umarsh.c | 131 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/umarsh.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index c49170d081..dcfde7c363 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -83,6 +83,15 @@ 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="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c new file mode 100644 index 0000000000..080b4bcbc0 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -0,0 +1,131 @@ +/* + * Parser for Umarsh card (Russia). + * + * Copyright 2023 Leptoptilos + * 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 . + */ + +#include "core/core_defines.h" +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "Umarsh" + +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 uint32_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]; + + uint32_t header = block_start_ptr[0] << 24 | block_start_ptr[1] << 16 | + block_start_ptr[2] << 8 | block_start_ptr[3]; + if(header != 0xFFFFFF7F && header != 0xFEFFFF7F && header != 0xE3FFFF7F) break; + + // Data parsing from block 1 + block_start_ptr = &data->block[ticket_sector_start_block_number + 1].data[0]; + uint8_t region_number = (((block_start_ptr[8] >> 5) & 0x07) << 4) | + (block_start_ptr[12] & 0x0f); + uint32_t card_number = (block_start_ptr[8] << 24 | block_start_ptr[9] << 16 | + block_start_ptr[10] << 8 | block_start_ptr[11]) & + 0x3FFFFFFF; + uint8_t refill_counter = (block_start_ptr[7]); + + if(card_number == 0) break; + + // Data parsing from block 2 + block_start_ptr = &data->block[ticket_sector_start_block_number + 2].data[0]; + uint16_t expiry_date = (block_start_ptr[0] << 8 | block_start_ptr[1]); + uint32_t terminal_number = + (block_start_ptr[3] << 16 | block_start_ptr[4] << 8 | block_start_ptr[5]); + uint16_t last_refill_date = (block_start_ptr[6] << 8 | block_start_ptr[7]); + uint16_t balance = (block_start_ptr[8] << 8 | block_start_ptr[9]) & 0x7FFF; + + FuriHalRtcDateTime expiry_datetime; + expiry_datetime.year = 2000 + (expiry_date >> 9); + expiry_datetime.month = expiry_date >> 5 & 0x0F; + expiry_datetime.day = expiry_date & 0x1F; + + FuriHalRtcDateTime last_refill_datetime; + last_refill_datetime.year = 2000 + (last_refill_date >> 9); + last_refill_datetime.month = last_refill_date >> 5 & 0x0F; + last_refill_datetime.day = last_refill_date & 0x1F; + + furi_string_printf( + parsed_data, + "\e#Umarsh\nCard number: %lu\nRegion: %02u\nBalance: %u RUR\nTerminal number: %lu\nRefill counter: %u\nLast refill: %02u.%02u.%u\nExpires: %02u.%02u.%u", + card_number, + region_number, + balance, + terminal_number, + refill_counter, + last_refill_datetime.day, + last_refill_datetime.month, + last_refill_datetime.year, + expiry_datetime.day, + expiry_datetime.month, + expiry_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; +} From b6ad07b47cc17c8e0c391d065454ad20cfc498fb Mon Sep 17 00:00:00 2001 From: Methodius Date: Tue, 21 Nov 2023 13:18:18 +0900 Subject: [PATCH 063/111] Umarsh parser: kopecks support added --- applications/main/nfc/plugins/supported_cards/umarsh.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index 080b4bcbc0..5e29f5c03c 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -64,7 +64,7 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { // Data parsing from block 1 block_start_ptr = &data->block[ticket_sector_start_block_number + 1].data[0]; uint8_t region_number = (((block_start_ptr[8] >> 5) & 0x07) << 4) | - (block_start_ptr[12] & 0x0f); + (block_start_ptr[12] & 0x0F); uint32_t card_number = (block_start_ptr[8] << 24 | block_start_ptr[9] << 16 | block_start_ptr[10] << 8 | block_start_ptr[11]) & 0x3FFFFFFF; @@ -78,7 +78,8 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { uint32_t terminal_number = (block_start_ptr[3] << 16 | block_start_ptr[4] << 8 | block_start_ptr[5]); uint16_t last_refill_date = (block_start_ptr[6] << 8 | block_start_ptr[7]); - uint16_t balance = (block_start_ptr[8] << 8 | block_start_ptr[9]) & 0x7FFF; + uint16_t balance_rub = (block_start_ptr[8] << 8 | block_start_ptr[9]) & 0x7FFF; + uint8_t balance_kop = block_start_ptr[10] & 0x7F; FuriHalRtcDateTime expiry_datetime; expiry_datetime.year = 2000 + (expiry_date >> 9); @@ -92,10 +93,11 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, - "\e#Umarsh\nCard number: %lu\nRegion: %02u\nBalance: %u RUR\nTerminal number: %lu\nRefill counter: %u\nLast refill: %02u.%02u.%u\nExpires: %02u.%02u.%u", + "\e#Umarsh\nCard number: %lu\nRegion: %02u\nBalance: %u.%u RUR\nTerminal number: %lu\nRefill counter: %u\nLast refill: %02u.%02u.%u\nExpires: %02u.%02u.%u", card_number, region_number, - balance, + balance_rub, + balance_kop, terminal_number, refill_counter, last_refill_datetime.day, From 7fd921227cfc498a1124d883109660f8ddd57bdd Mon Sep 17 00:00:00 2001 From: assasinfil Date: Tue, 21 Nov 2023 11:54:06 +0300 Subject: [PATCH 064/111] Fixes --- .../plugins/supported_cards/social_moscow.c | 14 +-- .../main/nfc/plugins/supported_cards/troika.c | 101 ++++++++++++------ 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index 56cb88d45c..79704ee548 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -511,7 +511,7 @@ void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, ui bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); - FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); + FURI_LOG_I(TAG, "Transport departament: %x", transport_departament); uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); if(layout_type == 0xE) { @@ -520,7 +520,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { layout_type = bit_lib_get_bits_16(block->data, 52, 14); } - FURI_LOG_D(TAG, "Layout type %x", layout_type); + FURI_LOG_I(TAG, "Layout type %x", layout_type); uint16_t card_view = 0; uint16_t card_type = 0; @@ -1483,8 +1483,7 @@ static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { } static bool social_moscow_verify(Nfc* nfc) { - return social_moscow_verify_type(nfc, MfClassicType1k) || - social_moscow_verify_type(nfc, MfClassicType4k); + return social_moscow_verify_type(nfc, MfClassicType4k); } static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { @@ -1564,12 +1563,13 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data data->block[60].data[14], furi_string_get_cstr(metro_result), furi_string_get_cstr(ground_result)); - furi_string_free(metro_result); - furi_string_free(ground_result); + parsed = true; } else { - return false; + parsed = false; } + furi_string_free(ground_result); + furi_string_free(metro_result); } while(false); return parsed; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 24101ed8e3..5c44f086f4 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -530,7 +530,7 @@ void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, ui bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); - FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); + FURI_LOG_I(TAG, "Transport departament: %x", transport_departament); uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); if(layout_type == 0xE) { @@ -539,7 +539,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { layout_type = bit_lib_get_bits_16(block->data, 52, 14); } - FURI_LOG_D(TAG, "Layout type %x", layout_type); + FURI_LOG_I(TAG, "Layout type %x", layout_type); uint16_t card_view = 0; uint16_t card_type = 0; @@ -612,6 +612,31 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_write_enabled, card_rfu2, card_crc16_2); + if(card_valid_by_date == 0) { + return false; + } + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips: %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_total_trips, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); break; } case 0x06: { @@ -668,11 +693,11 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_crc16_2); card_validator = card_validator1 * 1024 + card_validator2; FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + (card_start_trip_date) * 24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -730,7 +755,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_valid_from_date, card_rfu3); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); furi_string_printf( result, @@ -783,11 +808,10 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_transport_type4, card_hash); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016); + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); furi_string_printf( result, "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", @@ -844,10 +868,10 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_rfu3, card_transfer_in_metro); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + (card_start_trip_date) * 24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -925,10 +949,10 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_rfu5, card_transfer_in_metro); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + (card_start_trip_date) * 24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -995,11 +1019,10 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_zoo, card_hash); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; - from_minutes_to_datetime( - card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992); + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 1992); furi_string_printf( result, "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", @@ -1065,12 +1088,11 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_extended, card_hash); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes - - card_start_trip_neg_minutes, + (card_valid_to_date) * 24 * 60 + card_valid_for_minutes - card_start_trip_neg_minutes, &card_start_trip_minutes_s, 2016); //-time furi_string_printf( @@ -1405,7 +1427,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_type4, card_hash); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); furi_string_printf( result, @@ -1441,7 +1463,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { card_valid_by_date, card_hash); FuriHalRtcDateTime card_use_before_date_s = {0}; - from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); furi_string_printf( result, @@ -1464,10 +1486,10 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) bool success = true; if(type == MfClassicType1k) { - config->data_sector = 11; + config->data_sector = 4; config->keys = troika_1k_keys; } else if(type == MfClassicType4k) { - config->data_sector = 11; + config->data_sector = 8; config->keys = troika_4k_keys; } else { success = false; @@ -1573,20 +1595,39 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { FuriString* metro_result = furi_string_alloc(); FuriString* ground_result = furi_string_alloc(); + FuriString* tat_result = furi_string_alloc(); bool result1 = parse_transport_block(&data->block[32], metro_result); bool result2 = parse_transport_block(&data->block[28], ground_result); - if(result1 || result2) { + bool result3 = parse_transport_block(&data->block[16], tat_result); + furi_string_printf(parsed_data, "#Troyka\n"); + if(result1) { + furi_string_printf( + parsed_data, + "%s\n\e#Metro\n%s\n", + furi_string_get_cstr(parsed_data), + furi_string_get_cstr(metro_result)); + FURI_LOG_D(TAG, "Metro branch"); + } + if(result2) { furi_string_printf( parsed_data, - "\e#Troika\n%s\n\e#Ediniy\n%s\n\e#TAT\n", - furi_string_get_cstr(metro_result), + "%s\e#Ediniy\n%s\n", + furi_string_get_cstr(parsed_data), furi_string_get_cstr(ground_result)); - furi_string_free(metro_result); - furi_string_free(ground_result); - parsed = true; - } else { - return false; + FURI_LOG_D(TAG, "Ediny branch"); + } + if(result3) { + furi_string_printf( + parsed_data, + "%s\e#TAT\n%s\n", + furi_string_get_cstr(parsed_data), + furi_string_get_cstr(tat_result)); + FURI_LOG_D(TAG, "TAT branch"); } + furi_string_free(tat_result); + furi_string_free(ground_result); + furi_string_free(metro_result); + parsed = result1 || result2 || result3; } while(false); return parsed; From 4a84fbc6e3045beff4c02f00bf889d3fcb8a6ab3 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Tue, 21 Nov 2023 13:12:24 +0300 Subject: [PATCH 065/111] UI printing bugfix --- .../plugins/supported_cards/social_moscow.c | 52 ++++++++++++------- .../main/nfc/plugins/supported_cards/troika.c | 25 +++------ 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index 79704ee548..4899cbf6a2 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -1446,7 +1446,7 @@ static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClas bool success = true; if(type == MfClassicType4k) { - config->data_sector = 15; + config->data_sector = 32; config->keys = social_moscow_4k_keys; } else { success = false; @@ -1536,6 +1536,17 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data bool parsed = false; do { + // Verify card type + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != cfg.keys[cfg.data_sector].a) break; + uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24); uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8); uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40); @@ -1548,28 +1559,29 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data FuriString* ground_result = furi_string_alloc(); bool result1 = parse_transport_block(&data->block[4], metro_result); bool result2 = parse_transport_block(&data->block[16], ground_result); - if(result1 || result2) { - furi_string_printf( - parsed_data, - "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n\e#Metro\n%s\n\e#Ground\n%s", - card_code, - card_region, - card_number, - card_control, - omc_number, - month, - year, - data->block[60].data[13], - data->block[60].data[14], - furi_string_get_cstr(metro_result), - furi_string_get_cstr(ground_result)); - - parsed = true; - } else { - parsed = false; + furi_string_cat_printf( + parsed_data, + "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n", + card_code, + card_region, + card_number, + card_control, + omc_number, + month, + year, + data->block[60].data[13], + data->block[60].data[14]); + if(result1) { + furi_string_cat_printf( + parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); + } + if(result2) { + furi_string_cat_printf( + parsed_data, "\e#Ground\n%s\n", furi_string_get_cstr(ground_result)); } furi_string_free(ground_result); furi_string_free(metro_result); + parsed = result1 || result2; } while(false); return parsed; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 5c44f086f4..b714739cc9 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1599,30 +1599,17 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { bool result1 = parse_transport_block(&data->block[32], metro_result); bool result2 = parse_transport_block(&data->block[28], ground_result); bool result3 = parse_transport_block(&data->block[16], tat_result); - furi_string_printf(parsed_data, "#Troyka\n"); + furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); if(result1) { - furi_string_printf( - parsed_data, - "%s\n\e#Metro\n%s\n", - furi_string_get_cstr(parsed_data), - furi_string_get_cstr(metro_result)); - FURI_LOG_D(TAG, "Metro branch"); + furi_string_cat_printf( + parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); } if(result2) { - furi_string_printf( - parsed_data, - "%s\e#Ediniy\n%s\n", - furi_string_get_cstr(parsed_data), - furi_string_get_cstr(ground_result)); - FURI_LOG_D(TAG, "Ediny branch"); + furi_string_cat_printf( + parsed_data, "\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); } if(result3) { - furi_string_printf( - parsed_data, - "%s\e#TAT\n%s\n", - furi_string_get_cstr(parsed_data), - furi_string_get_cstr(tat_result)); - FURI_LOG_D(TAG, "TAT branch"); + furi_string_cat_printf(parsed_data, "\e#TAT\n%s\n", furi_string_get_cstr(tat_result)); } furi_string_free(tat_result); furi_string_free(ground_result); From 3daaea6ecfcd6b69278dc92130e25961dfd054a5 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Tue, 21 Nov 2023 15:03:46 +0300 Subject: [PATCH 066/111] Added new social card --- .../plugins/supported_cards/social_moscow.c | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index 4899cbf6a2..3ccdadc1f6 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -22,6 +22,24 @@ typedef struct { uint32_t data_sector; } SocialMoscowCardConfig; +static const MfClassicKeyPair social_moscow_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, + {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, + {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, + {.a = 0x40ead80721ce, .b = 0x100533b89331}, + {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}}; + static const MfClassicKeyPair social_moscow_4k_keys[] = { {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, @@ -1444,8 +1462,10 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) { bool success = true; - - if(type == MfClassicType4k) { + if(type == MfClassicType1k) { + config->data_sector = 32; + config->keys = social_moscow_1k_keys; + } else if(type == MfClassicType4k) { config->data_sector = 32; config->keys = social_moscow_4k_keys; } else { @@ -1483,7 +1503,8 @@ static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { } static bool social_moscow_verify(Nfc* nfc) { - return social_moscow_verify_type(nfc, MfClassicType4k); + return social_moscow_verify_type(nfc, MfClassicType1k) || + social_moscow_verify_type(nfc, MfClassicType4k); } static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { From f8546937c0fc6be5f1d94b8ba95f3adc486ed950 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:59:38 +0300 Subject: [PATCH 067/111] fix readme and feature name --- ReadMe.md | 8 ++++---- .../main/subghz/scenes/subghz_scene_radio_settings.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 203fa055d8..05c3cd3520 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -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 diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 1b83510a9a..ec4fee4a1c 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -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); From 4261063c99e6d35fbd0dde22a6f5c4db765068cf Mon Sep 17 00:00:00 2001 From: Methodius Date: Wed, 22 Nov 2023 00:39:23 +0900 Subject: [PATCH 068/111] Metromoney transport card parser added --- applications/main/nfc/application.fam | 9 + .../nfc/plugins/supported_cards/metromoney.c | 191 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/metromoney.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index dcfde7c363..44e3d842f8 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -92,6 +92,15 @@ App( 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="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c new file mode 100644 index 0000000000..263bbc44eb --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -0,0 +1,191 @@ +/* + * Parser for Metromoney card (Georgia). + * + * Copyright 2023 Leptoptilos + * + * 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 . + */ +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include + +#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; +} \ No newline at end of file From c145cad65356244cb073c6ef896e4e742dbed79b Mon Sep 17 00:00:00 2001 From: assasinfil Date: Tue, 21 Nov 2023 22:34:29 +0300 Subject: [PATCH 069/111] Verify card bugfix --- .../main/nfc/plugins/supported_cards/social_moscow.c | 10 +++++----- applications/main/nfc/plugins/supported_cards/troika.c | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index 3ccdadc1f6..ca52661b2f 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -10,7 +10,7 @@ #include #include -#define TAG "Social Moscow" +#define TAG "Social_Moscow" typedef struct { uint64_t a; @@ -1463,10 +1463,10 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) { bool success = true; if(type == MfClassicType1k) { - config->data_sector = 32; + config->data_sector = 15; config->keys = social_moscow_1k_keys; } else if(type == MfClassicType4k) { - config->data_sector = 32; + config->data_sector = 15; config->keys = social_moscow_4k_keys; } else { success = false; @@ -1495,7 +1495,7 @@ static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; } - + FURI_LOG_D(TAG, "Verify success!"); verified = true; } while(false); @@ -1517,7 +1517,7 @@ static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { nfc_device_copy_data(device, NfcProtocolMfClassic, data); do { - MfClassicType type = MfClassicTypeMini; + MfClassicType type = MfClassicType4k; MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); if(error != MfClassicErrorNone) break; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index b714739cc9..3f252390f6 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1486,10 +1486,10 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) bool success = true; if(type == MfClassicType1k) { - config->data_sector = 4; + config->data_sector = 11; config->keys = troika_1k_keys; } else if(type == MfClassicType4k) { - config->data_sector = 8; + config->data_sector = 11; config->keys = troika_4k_keys; } else { success = false; @@ -1518,7 +1518,7 @@ static bool troika_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; } - + FURI_LOG_D(TAG, "Verify success!"); verified = true; } while(false); From e1c89834df690f32d92f1664f4070187e61751ce Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 22 Nov 2023 01:48:22 +0300 Subject: [PATCH 070/111] no verbose --- .drone.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index db85f5d9d4..6bc9b3ebeb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -45,7 +45,7 @@ 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/ @@ -67,7 +67,7 @@ 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 @@ -120,7 +120,7 @@ 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/ @@ -425,7 +425,7 @@ 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/ @@ -447,7 +447,7 @@ 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 From df1d6503c0e6426d20c5db0e9774ad7f94251875 Mon Sep 17 00:00:00 2001 From: Methodius Date: Wed, 22 Nov 2023 16:15:53 +0900 Subject: [PATCH 071/111] Umarsh header check rework --- applications/main/nfc/plugins/supported_cards/umarsh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index 5e29f5c03c..c2a35ab33e 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -59,7 +59,7 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { uint32_t header = block_start_ptr[0] << 24 | block_start_ptr[1] << 16 | block_start_ptr[2] << 8 | block_start_ptr[3]; - if(header != 0xFFFFFF7F && header != 0xFEFFFF7F && header != 0xE3FFFF7F) break; + if((header & 0xFFFFFF) != 0xFFFF7F) break; // Data parsing from block 1 block_start_ptr = &data->block[ticket_sector_start_block_number + 1].data[0]; From 63f072a819f2514a84ccbfe8674b996fc9d7f92c Mon Sep 17 00:00:00 2001 From: Methodius Date: Wed, 22 Nov 2023 17:26:10 +0900 Subject: [PATCH 072/111] Umarsh header check rework reworked. Thanks J for idea! --- applications/main/nfc/plugins/supported_cards/umarsh.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index c2a35ab33e..5e5523467b 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -57,9 +57,11 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { //Validate specific for Umarsh ticket sector header const uint8_t* block_start_ptr = &data->block[ticket_sector_start_block_number].data[0]; - uint32_t header = block_start_ptr[0] << 24 | block_start_ptr[1] << 16 | - block_start_ptr[2] << 8 | block_start_ptr[3]; - if((header & 0xFFFFFF) != 0xFFFF7F) break; + uint32_t header_part_0 = block_start_ptr[0] << 24 | block_start_ptr[1] << 16 | + block_start_ptr[2] << 8 | block_start_ptr[3]; + uint32_t header_part_1 = block_start_ptr[4] << 24 | block_start_ptr[5] << 16 | + block_start_ptr[6] << 8 | block_start_ptr[7]; + 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]; From 5f18532a59a95587920d9a8f532e92243cd7ead9 Mon Sep 17 00:00:00 2001 From: Methodius Date: Thu, 23 Nov 2023 22:11:12 +0900 Subject: [PATCH 073/111] Umarsh code cleanup --- .../main/nfc/plugins/supported_cards/umarsh.c | 88 ++++++++++++------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index 5e5523467b..a85c056f6b 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -38,6 +38,13 @@ #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); @@ -49,65 +56,78 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { // Verify card type if(data->type != MfClassicType1k) break; - const uint32_t ticket_sector = 8; + 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 + // Validate specific for Umarsh ticket sector header const uint8_t* block_start_ptr = &data->block[ticket_sector_start_block_number].data[0]; - uint32_t header_part_0 = block_start_ptr[0] << 24 | block_start_ptr[1] << 16 | - block_start_ptr[2] << 8 | block_start_ptr[3]; - uint32_t header_part_1 = block_start_ptr[4] << 24 | block_start_ptr[5] << 16 | - block_start_ptr[6] << 8 | block_start_ptr[7]; + 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]; - uint8_t region_number = (((block_start_ptr[8] >> 5) & 0x07) << 4) | - (block_start_ptr[12] & 0x0F); - uint32_t card_number = (block_start_ptr[8] << 24 | block_start_ptr[9] << 16 | - block_start_ptr[10] << 8 | block_start_ptr[11]) & - 0x3FFFFFFF; - uint8_t refill_counter = (block_start_ptr[7]); + 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]; - uint16_t expiry_date = (block_start_ptr[0] << 8 | block_start_ptr[1]); - uint32_t terminal_number = - (block_start_ptr[3] << 16 | block_start_ptr[4] << 8 | block_start_ptr[5]); - uint16_t last_refill_date = (block_start_ptr[6] << 8 | block_start_ptr[7]); - uint16_t balance_rub = (block_start_ptr[8] << 8 | block_start_ptr[9]) & 0x7FFF; - uint8_t balance_kop = block_start_ptr[10] & 0x7F; + 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; - expiry_datetime.year = 2000 + (expiry_date >> 9); - expiry_datetime.month = expiry_date >> 5 & 0x0F; - expiry_datetime.day = expiry_date & 0x1F; + 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; - last_refill_datetime.year = 2000 + (last_refill_date >> 9); - last_refill_datetime.month = last_refill_date >> 5 & 0x0F; - last_refill_datetime.day = last_refill_date & 0x1F; + bool is_last_refill_datetime_valid = + parse_datetime(last_refill_date, &last_refill_datetime); - furi_string_printf( + furi_string_cat_printf( parsed_data, - "\e#Umarsh\nCard number: %lu\nRegion: %02u\nBalance: %u.%u RUR\nTerminal number: %lu\nRefill counter: %u\nLast refill: %02u.%02u.%u\nExpires: %02u.%02u.%u", + "\e#Umarsh\nCard number: %lu\nRegion: %02u\nTerminal number: %lu\nRefill counter: %u\nBalance: %u.%02u RUR", card_number, region_number, - balance_rub, - balance_kop, terminal_number, refill_counter, - last_refill_datetime.day, - last_refill_datetime.month, - last_refill_datetime.year, - expiry_datetime.day, - expiry_datetime.month, - expiry_datetime.year); + 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); From 6c2e33263874419b04669aba0c1ae3bb50abb055 Mon Sep 17 00:00:00 2001 From: Methodius Date: Thu, 23 Nov 2023 22:19:26 +0900 Subject: [PATCH 074/111] Kazan transport cards parser added --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/kazan.c | 287 ++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/kazan.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 44e3d842f8..77eaef7f77 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -101,6 +101,15 @@ App( 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="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c new file mode 100644 index 0000000000..8fc9e03e8a --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -0,0 +1,287 @@ +/* + * Parser for Kazan transport card (Kazan, Russia). + * + * Copyright 2023 Leptoptilos + * + * 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 . + */ +#include "core/log.h" +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include +#include +#include +#include "md5.h" + +#define TAG "Kazan" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair kazan_1k_keys[] = { + {.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}, +}; + +enum SubscriptionType { + SUBSCRIPTION_TYPE_UNKNOWN, + SUBSCRIPTION_TYPE_PURSE, + SUBSCRIPTION_TYPE_UNLIMITED, +}; + +enum SubscriptionType get_subscription_type(uint8_t value) { + switch(value) { + case 0: + case 0x60: + case 0x67: + case 0x0F: + return SUBSCRIPTION_TYPE_UNLIMITED; + case 0x53: + return SUBSCRIPTION_TYPE_PURSE; + default: + return SUBSCRIPTION_TYPE_UNKNOWN; + } +} + +static bool kazan_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t ticket_sector_number = 8; + 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(kazan_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 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, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(kazan_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(kazan_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 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 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, ticket_sector_number); + + keys.a = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + keys.b = nfc_util_bytes2num(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); + + if((keys.a != 0xE954024EE754) && (keys.b != 0x0CD464CDC100)) 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]; + + enum SubscriptionType subscription_type = get_subscription_type(block_start_ptr[0]); + + 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_UNLIMITED) { + furi_string_cat_printf( + parsed_data, + "Type: unlimited\nTrips left: %lu\nCard 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_UNKNOWN) { + furi_string_cat_printf( + parsed_data, + "Type: Unknown\nBalance: %lu RUR\nValid from: %02u.%02u.%u\nValid to: %02u.%02u.%u", + 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; +} \ No newline at end of file From 8c5f28d6a002de98a64c3c0d414237d3fb9c0502 Mon Sep 17 00:00:00 2001 From: Methodius Date: Thu, 23 Nov 2023 23:59:06 +0900 Subject: [PATCH 075/111] Kazan parser: Type: abonnement fix --- applications/main/nfc/plugins/supported_cards/kazan.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index 8fc9e03e8a..68ef6c16f6 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -59,7 +59,7 @@ static const MfClassicKeyPair kazan_1k_keys[] = { enum SubscriptionType { SUBSCRIPTION_TYPE_UNKNOWN, SUBSCRIPTION_TYPE_PURSE, - SUBSCRIPTION_TYPE_UNLIMITED, + SUBSCRIPTION_TYPE_ABONNEMENT, }; enum SubscriptionType get_subscription_type(uint8_t value) { @@ -68,7 +68,7 @@ enum SubscriptionType get_subscription_type(uint8_t value) { case 0x60: case 0x67: case 0x0F: - return SUBSCRIPTION_TYPE_UNLIMITED; + return SUBSCRIPTION_TYPE_ABONNEMENT; case 0x53: return SUBSCRIPTION_TYPE_PURSE; default: @@ -223,10 +223,10 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { valid_to.year); } - if(subscription_type == SUBSCRIPTION_TYPE_UNLIMITED) { + if(subscription_type == SUBSCRIPTION_TYPE_ABONNEMENT) { furi_string_cat_printf( parsed_data, - "Type: unlimited\nTrips left: %lu\nCard valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u", + "Type: abonnement\nTrips left: %lu\nCard valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u", trip_counter, valid_from.day, valid_from.month, @@ -239,7 +239,7 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { if(subscription_type == SUBSCRIPTION_TYPE_UNKNOWN) { furi_string_cat_printf( parsed_data, - "Type: Unknown\nBalance: %lu RUR\nValid from: %02u.%02u.%u\nValid to: %02u.%02u.%u", + "Type: unknown\nBalance: %lu RUR\nValid from: %02u.%02u.%u\nValid to: %02u.%02u.%u", trip_counter, valid_from.day, valid_from.month, From 1c3cbec661b735b08f8401f93a50a772aff48a62 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Sun, 26 Nov 2023 11:10:33 +0300 Subject: [PATCH 076/111] [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 --- .../debug/unit_tests/nfc/nfc_transport.c | 15 ++ .../helpers/protocol_support/felica/felica.c | 10 +- lib/nfc/nfc.c | 18 ++- lib/nfc/nfc.h | 19 +++ lib/nfc/protocols/felica/felica.h | 2 + lib/nfc/protocols/felica/felica_listener.c | 79 ++++++++++ lib/nfc/protocols/felica/felica_listener.h | 14 ++ .../protocols/felica/felica_listener_defs.h | 13 ++ lib/nfc/protocols/felica/felica_listener_i.h | 21 +++ lib/nfc/protocols/nfc_listener_defs.c | 2 + targets/f7/api_symbols.csv | 2 + targets/f7/furi_hal/furi_hal_nfc_felica.c | 138 +++++++++++++++++- targets/furi_hal_include/furi_hal_nfc.h | 17 +++ 13 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 lib/nfc/protocols/felica/felica_listener.c create mode 100644 lib/nfc/protocols/felica/felica_listener.h create mode 100644 lib/nfc/protocols/felica/felica_listener_defs.h create mode 100644 lib/nfc/protocols/felica/felica_listener_i.h diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index e2e313fdef..e9f4e21341 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -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 diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index b3e629f4a5..f9c8491216 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -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, }, }; diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index a7c4fe1a6d..6475cce435 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -76,7 +76,7 @@ static const FuriHalNfcTech nfc_tech_table[NfcModeNum][NfcTechNum] = { [NfcTechIso14443a] = FuriHalNfcTechIso14443a, [NfcTechIso14443b] = FuriHalNfcTechInvalid, [NfcTechIso15693] = FuriHalNfcTechIso15693, - [NfcTechFelica] = FuriHalNfcTechInvalid, + [NfcTechFelica] = FuriHalNfcTechFelica, }, }; @@ -646,4 +646,20 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return ret; } +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); + + FuriHalNfcError error = + furi_hal_nfc_felica_listener_set_sensf_res_data(idm, idm_len, pmm, pmm_len); + instance->comm_state = NfcCommStateIdle; + return nfc_process_hal_error(error); +} + #endif // APP_UNIT_TESTS diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 1e8f315a50..4f7980b026 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -351,6 +351,25 @@ NfcError nfc_iso14443a_listener_set_col_res_data( uint8_t* atqa, uint8_t sak); +/** + * @brief Set FeliCa collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in,out] instance pointer to the instance to be configured. + * @param[in] idm pointer to a byte array containing the IDm. + * @param[in] idm_len IDm length in bytes. + * @param[in] pmm pointer to a byte array containing the PMm. + * @param[in] pmm_len PMm length in bytes. + * @returns NfcErrorNone on success, any other error code on failure. +*/ +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); + /** * @brief Send ISO15693 Start of Frame pattern in listener mode * diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index da9d2294ee..31e040b8a1 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -14,6 +14,8 @@ extern "C" { #define FELICA_FDT_POLL_FC (10000U) #define FELICA_POLL_POLL_MIN_US (1280U) +#define FELICA_FDT_LISTEN_FC (1172) + #define FELICA_SYSTEM_CODE_CODE (0xFFFFU) #define FELICA_TIME_SLOT_1 (0x00U) #define FELICA_TIME_SLOT_2 (0x01U) diff --git a/lib/nfc/protocols/felica/felica_listener.c b/lib/nfc/protocols/felica/felica_listener.c new file mode 100644 index 0000000000..4e6c057854 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener.c @@ -0,0 +1,79 @@ +#include "felica_listener_i.h" + +#include "nfc/protocols/nfc_listener_base.h" + +#define FELICA_LISTENER_MAX_BUFFER_SIZE (64) +#define TAG "Felica" + +FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) { + furi_assert(nfc); + furi_assert(data); + + FelicaListener* instance = malloc(sizeof(FelicaListener)); + instance->nfc = nfc; + instance->data = data; + instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); + + nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC); + + nfc_config(instance->nfc, NfcModeListener, NfcTechFelica); + nfc_felica_listener_set_sensf_res_data( + nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm)); + + return instance; +} + +void felica_listener_free(FelicaListener* instance) { + furi_assert(instance); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +void felica_listener_set_callback( + FelicaListener* listener, + NfcGenericCallback callback, + void* context) { + UNUSED(listener); + UNUSED(callback); + UNUSED(context); +} + +const FelicaData* felica_listener_get_data(const FelicaListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + FelicaListener* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypeListenerActivated) { + instance->state = Felica_ListenerStateActivated; + FURI_LOG_D(TAG, "Activated"); + } else if(nfc_event->type == NfcEventTypeFieldOff) { + instance->state = Felica_ListenerStateIdle; + FURI_LOG_D(TAG, "Field Off"); + } else if(nfc_event->type == NfcEventTypeRxEnd) { + FURI_LOG_D(TAG, "Rx Done"); + } + return command; +} + +const NfcListenerBase nfc_listener_felica = { + .alloc = (NfcListenerAlloc)felica_listener_alloc, + .free = (NfcListenerFree)felica_listener_free, + .set_callback = (NfcListenerSetCallback)felica_listener_set_callback, + .get_data = (NfcListenerGetData)felica_listener_get_data, + .run = (NfcListenerRun)felica_listener_run, +}; \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_listener.h b/lib/nfc/protocols/felica/felica_listener.h new file mode 100644 index 0000000000..d210befa57 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener.h @@ -0,0 +1,14 @@ +#pragma once + +#include "felica.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FelicaListener FelicaListener; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_listener_defs.h b/lib/nfc/protocols/felica/felica_listener_defs.h new file mode 100644 index 0000000000..19b252be59 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase nfc_listener_felica; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_listener_i.h b/lib/nfc/protocols/felica/felica_listener_i.h new file mode 100644 index 0000000000..4fa25a1620 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener_i.h @@ -0,0 +1,21 @@ +#include "felica_listener.h" + +#include + +typedef enum { + Felica_ListenerStateIdle, + Felica_ListenerStateActivated, +} FelicaListenerState; + +struct FelicaListener { + Nfc* nfc; + FelicaData* data; + FelicaListenerState state; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent generic_event; + NfcGenericCallback callback; + void* context; +}; \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 31f9bc16c6..2a6167e9cb 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -6,6 +6,7 @@ #include #include #include +#include const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, @@ -18,4 +19,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolMfDesfire] = NULL, [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, + [NfcProtocolFelica] = &nfc_listener_felica, }; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c50db0c77c..9431028abe 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1287,6 +1287,7 @@ Function,+,furi_hal_nfc_abort,FuriHalNfcError, Function,+,furi_hal_nfc_acquire,FuriHalNfcError, Function,+,furi_hal_nfc_event_start,FuriHalNfcError, Function,+,furi_hal_nfc_event_stop,FuriHalNfcError, +Function,+,furi_hal_nfc_felica_listener_set_sensf_res_data,FuriHalNfcError,"const uint8_t*, const uint8_t, const uint8_t*, const uint8_t" Function,+,furi_hal_nfc_field_detect_start,FuriHalNfcError, Function,+,furi_hal_nfc_field_detect_stop,FuriHalNfcError, Function,+,furi_hal_nfc_field_is_present,_Bool, @@ -2316,6 +2317,7 @@ Function,+,nfc_dict_get_next_key,_Bool,"NfcDict*, uint8_t*, size_t" Function,+,nfc_dict_get_total_keys,uint32_t,NfcDict* Function,+,nfc_dict_is_key_present,_Bool,"NfcDict*, const uint8_t*, size_t" Function,+,nfc_dict_rewind,_Bool,NfcDict* +Function,+,nfc_felica_listener_set_sensf_res_data,NfcError,"Nfc*, const uint8_t*, const uint8_t, const uint8_t*, const uint8_t" Function,+,nfc_free,void,Nfc* Function,+,nfc_iso14443a_listener_set_col_res_data,NfcError,"Nfc*, uint8_t*, uint8_t, uint8_t*, uint8_t" Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuffer*" diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index e4b8ac0ee6..f762a0ed32 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -1,6 +1,9 @@ #include "furi_hal_nfc_i.h" #include "furi_hal_nfc_tech_i.h" +// Prevent FDT timer from starting +#define FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC (INT32_MAX) + static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) { // Enable Felica mode, AM modulation st25r3916_change_reg_bits( @@ -50,6 +53,126 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* ha return FuriHalNfcErrorNone; } +static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) { + furi_assert(handle); + st25r3916_write_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + + // Enable Target Felica mode, AM modulation + st25r3916_write_reg( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om2 | ST25R3916_REG_MODE_tr_am); + + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_BIT_RATE, + ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask, + ST25R3916_REG_BIT_RATE_txrate_212 | ST25R3916_REG_BIT_RATE_rxrate_212); + + // Receive configuration + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF1, + ST25R3916_REG_RX_CONF1_lp0 | ST25R3916_REG_RX_CONF1_hz_12_80khz); + + // AGC enabled, ratio 3:1, squelch after TX + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m | + ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn); + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + // 10% ASK modulation + st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_10percent); + + // Correlator setup + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s6 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s2); + + // Sleep mode disable, 424kHz mode off + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00); + + st25r3916_write_reg(handle, ST25R3916_REG_MASK_RX_TIMER, 0x02); + + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE | + ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X | + ST25R3916_IRQ_MASK_WU_A); + // Clear interrupts + st25r3916_get_irq(handle); + + st25r3916_write_reg( + handle, + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | + ST25R3916_REG_PASSIVE_TARGET_fdel_1); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_felica_listener_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +static FuriHalNfcEvent furi_hal_nfc_felica_listener_wait_event(uint32_t timeout_ms) { + UNUSED(timeout_ms); + FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms); + + return event; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + UNUSED(handle); + UNUSED(tx_data); + UNUSED(tx_bits); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_sleep(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_idle(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len) { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + // Write PT Memory + uint8_t pt_memory[19] = {}; + pt_memory[2] = 0x01; + memcpy(pt_memory + 3, idm, idm_len); + memcpy(pt_memory + 3 + idm_len, pmm, pmm_len); + st25r3916_write_ptf_mem(handle, pt_memory, sizeof(pt_memory)); + return FuriHalNfcErrorNone; +} + const FuriHalNfcTechBase furi_hal_nfc_felica = { .poller = { @@ -65,5 +188,18 @@ const FuriHalNfcTechBase furi_hal_nfc_felica = { .rx = furi_hal_nfc_common_fifo_rx, }, - .listener = {}, + .listener = + { + .compensation = + { + .fdt = FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC, + }, + .init = furi_hal_nfc_felica_listener_init, + .deinit = furi_hal_nfc_felica_listener_deinit, + .wait_event = furi_hal_nfc_felica_listener_wait_event, + .tx = furi_hal_nfc_felica_listener_tx, + .rx = furi_hal_nfc_common_fifo_rx, + .sleep = furi_hal_nfc_felica_listener_sleep, + .idle = furi_hal_nfc_felica_listener_idle, + }, }; diff --git a/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h index ad4080e264..3d145d1002 100644 --- a/targets/furi_hal_include/furi_hal_nfc.h +++ b/targets/furi_hal_include/furi_hal_nfc.h @@ -452,6 +452,23 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( */ FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(); +/** + * @brief Set FeliCa collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in] idm pointer to a byte array containing the IDm. + * @param[in] idm_len IDm length in bytes. + * @param[in] pmm pointer to a byte array containing the PMm. + * @param[in] pmm_len PMm length in bytes. + * @returns NfcErrorNone on success, any other error code on failure. +*/ +FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len); + #ifdef __cplusplus } #endif From f9101d80840b4b7ef7146e41bba99aac881bbfca Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sun, 26 Nov 2023 12:20:49 +0400 Subject: [PATCH 077/111] [FL-3686] Mifare Classic fixes (#3221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: あく --- lib/nfc/helpers/nfc_data_generator.c | 41 ++++++++++++------- lib/nfc/protocols/mf_classic/mf_classic.c | 13 +++--- lib/nfc/protocols/mf_classic/mf_classic.h | 2 + .../mf_classic/mf_classic_listener.c | 25 ++++++++++- .../mf_classic/mf_classic_listener_i.h | 1 + 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 011f9f6db7..21f062605b 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -329,9 +329,23 @@ static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { static void nfc_generate_mf_classic_common(MfClassicData* data, uint8_t uid_len, MfClassicType type) { data->iso14443_3a_data->uid_len = uid_len; - data->iso14443_3a_data->atqa[0] = 0x44; + data->iso14443_3a_data->atqa[0] = 0x00; data->iso14443_3a_data->atqa[1] = 0x00; - data->iso14443_3a_data->sak = 0x08; + data->iso14443_3a_data->sak = 0x00; + // Calculate the proper ATQA and SAK + if(uid_len == 7) { + data->iso14443_3a_data->atqa[0] |= 0x40; + } + if(type == MfClassicType1k) { + data->iso14443_3a_data->atqa[0] |= 0x04; + data->iso14443_3a_data->sak = 0x08; + } else if(type == MfClassicType4k) { + data->iso14443_3a_data->atqa[0] |= 0x02; + data->iso14443_3a_data->sak = 0x18; + } else if(type == MfClassicTypeMini) { + data->iso14443_3a_data->atqa[0] |= 0x08; + data->iso14443_3a_data->sak = 0x09; + } data->type = type; } @@ -343,6 +357,11 @@ static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t sec_tr->access_bits.data[2] = 0x80; sec_tr->access_bits.data[3] = 0x69; // Nice + for(int i = 0; i < 6; i++) { + sec_tr->key_a.data[i] = 0xFF; + sec_tr->key_b.data[i] = 0xFF; + } + mf_classic_set_block_read(data, block, &data->block[block]); mf_classic_set_key_found( data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeA, 0xFFFFFFFFFFFF); @@ -396,41 +415,35 @@ static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfCl uint16_t block_num = mf_classic_get_total_block_num(type); if(type == MfClassicType4k) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 18 - mfc_data->iso14443_3a_data->sak = 0x18; } else if(type == MfClassicType1k) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 08 - mfc_data->iso14443_3a_data->sak = 0x08; } else if(type == MfClassicTypeMini) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 09 - mfc_data->iso14443_3a_data->sak = 0x09; } nfc_generate_mf_classic_block_0( diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index 400cf0d7fb..e68e8c7187 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -606,6 +606,7 @@ static bool mf_classic_is_allowed_access_sector_trailer( uint8_t* access_bits_arr = sec_tr->access_bits.data; uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) | ((access_bits_arr[2] >> 7) & 0x01); + FURI_LOG_T("NFC", "AC: %02X", AC); switch(action) { case MfClassicActionKeyARead: { @@ -615,20 +616,20 @@ static bool mf_classic_is_allowed_access_sector_trailer( case MfClassicActionKeyBWrite: { return ( (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) || - (key_type == MfClassicKeyTypeB && (AC == 0x04 || AC == 0x03))); + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x03 || AC == 0x01))); } case MfClassicActionKeyBRead: { - return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x02 || AC == 0x01)); } case MfClassicActionACRead: { - return ( - (key_type == MfClassicKeyTypeA) || - (key_type == MfClassicKeyTypeB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + return ((key_type == MfClassicKeyTypeA) || (key_type == MfClassicKeyTypeB)); } case MfClassicActionACWrite: { return ( (key_type == MfClassicKeyTypeA && (AC == 0x01)) || - (key_type == MfClassicKeyTypeB && (AC == 0x03 || AC == 0x05))); + (key_type == MfClassicKeyTypeB && (AC == 0x01 || AC == 0x03 || AC == 0x05))); } default: return false; diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index f180411df5..146e6a6f15 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -19,6 +19,8 @@ extern "C" { #define MF_CLASSIC_CMD_HALT_LSB (0x00) #define MF_CLASSIC_CMD_ACK (0x0A) #define MF_CLASSIC_CMD_NACK (0x00) +#define MF_CLASSIC_CMD_NACK_TRANSFER_INVALID (0x04) +#define MF_CLASSIC_CMD_NACK_TRANSFER_CRC_ERROR (0x01) #define MF_CLASSIC_TOTAL_SECTORS_MAX (40) #define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index f7bd5b3f45..fb12ba8a95 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -34,6 +34,7 @@ static void mf_classic_listener_reset_state(MfClassicListener* instance) { instance->cmd_in_progress = false; instance->current_cmd_handler_idx = 0; instance->transfer_value = 0; + instance->transfer_valid = false; instance->value_cmd = MfClassicValueCommandInvalid; } @@ -154,7 +155,7 @@ static MfClassicListenerCommand uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); if(secret_poller != prng_successor(nt_num, 64)) { - FURI_LOG_D( + FURI_LOG_T( TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); mf_classic_listener_reset_state(instance); break; @@ -272,6 +273,17 @@ static MfClassicListenerCommand mf_classic_listener_write_block_second_part_hand if(mf_classic_is_sector_trailer(block_num)) { MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)█ + + // Check if any writing is allowed + if(!mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyAWrite) && + !mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyBWrite) && + !mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionACWrite)) { + break; + } + if(mf_classic_is_allowed_access( instance->data, block_num, key_type, MfClassicActionKeyAWrite)) { bit_buffer_write_bytes_mid(buff, sec_tr->key_a.data, 0, sizeof(MfClassicKey)); @@ -338,6 +350,7 @@ static MfClassicListenerCommand break; } + instance->transfer_valid = true; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; command = MfClassicListenerCommandAck; @@ -382,6 +395,7 @@ static MfClassicListenerCommand } instance->transfer_value += data; + instance->transfer_valid = true; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; @@ -411,6 +425,7 @@ static MfClassicListenerCommand mf_classic_value_to_block( instance->transfer_value, block_num, &instance->data->block[block_num]); instance->transfer_value = 0; + instance->transfer_valid = false; command = MfClassicListenerCommandAck; } while(false); @@ -581,7 +596,13 @@ NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) { if(mfc_command == MfClassicListenerCommandAck) { mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_ACK); } else if(mfc_command == MfClassicListenerCommandNack) { - mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_NACK); + // Calculate nack based on the transfer buffer validity + uint8_t nack = MF_CLASSIC_CMD_NACK; + if(!instance->transfer_valid) { + nack += MF_CLASSIC_CMD_NACK_TRANSFER_INVALID; + } + + mf_classic_listener_send_short_frame(instance, nack); } else if(mfc_command == MfClassicListenerCommandSilent) { command = NfcCommandReset; } else if(mfc_command == MfClassicListenerCommandSleep) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 4b040bec12..52273be9c2 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -42,6 +42,7 @@ struct MfClassicListener { // Value operation data int32_t transfer_value; + bool transfer_valid; MfClassicValueCommand value_cmd; NfcGenericEvent generic_event; From 1946aaf5e188c67ceef5efede0b46d7edf19825a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 28 Nov 2023 04:21:43 +0300 Subject: [PATCH 078/111] upd discord notify --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 6bc9b3ebeb..0e2314ad49 100644 --- a/.drone.yml +++ b/.drone.yml @@ -341,7 +341,7 @@ 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)' @@ -665,7 +665,7 @@ 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}')' From c4bf1fe71772af0fe4477c6a167a61516c08042b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 28 Nov 2023 04:39:46 +0300 Subject: [PATCH 079/111] use char escaping --- .drone.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0e2314ad49..0a225e722f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -343,7 +343,8 @@ steps: commands: - 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 . 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 . Date: Wed, 29 Nov 2023 00:20:15 +0300 Subject: [PATCH 080/111] ibutton FIX crash!!! why?????? --- applications/main/ibutton/scenes/ibutton_scene_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 180f53e598..80bf13447a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -42,7 +42,7 @@ void ibutton_scene_write_on_enter(void* context) { furi_string_printf( tmp, - "[%s]\n%s", + "[%s]\n%s ", ibutton_protocols_get_name(ibutton->protocols, protocol_id), ibutton->key_name); From 8628a2c6a2ffb9a32b4a83e4b7736a6bcd9bc924 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 29 Nov 2023 03:26:05 +0300 Subject: [PATCH 081/111] update changelog, release planned for next week --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc228c8da4..dc6d5813b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,57 @@ +## Warning!!! Please read this before installing!!! +**This release has some unresolved issues:** +### Known NFC app regressions and issues: +- Mifare Classic with custom UID add manually option was temporarily removed (Unleashed) +- Mifare Mini clones reading is broken (OFW) +- Mifare Classic dict attack fast skip causes glitches/incorrect reading (OFW) +- EMV simple data parser was removed with protocol 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 +**API was updated to v46.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 +* 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) * 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: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* 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,7 +75,8 @@ |XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`| |TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`| -#### Thanks to our sponsors: +#### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis: +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 +95,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! From ff41d262dc1dab11763dd8ba436e2abbb83aed54 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:18:20 +0300 Subject: [PATCH 082/111] add 303.9 frequency --- .../main/subghz/views/subghz_frequency_analyzer.c | 14 +++++++------- documentation/SubGHzSettings.md | 1 + lib/subghz/subghz_setting.c | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 9f39bb0d64..bc472f2339 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -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, diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index a15f72b987..1988dcbd8a 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -13,6 +13,7 @@ if you need your custom one, make sure it doesn't listed here 300000000, 302757000, 303875000, + 303900000, 304250000, 307000000, 307500000, diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 0277e4aedd..a9f6b7c6b8 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -20,6 +20,7 @@ static const uint32_t subghz_frequency_list[] = { 300000000, 302757000, 303875000, + 303900000, 304250000, 307000000, 307500000, From 05479103a6441ba39aea45a3efc76a2d7107c370 Mon Sep 17 00:00:00 2001 From: Cory Parker Date: Wed, 29 Nov 2023 21:41:10 -1000 Subject: [PATCH 083/111] Update mf_classic_dict.nfc Added in new mf classic keys --- .../main/nfc/resources/nfc/assets/mf_classic_dict.nfc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index a85d434035..9d8739f448 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -3823,3 +3823,8 @@ F72CD208FDF9 2158E314C3DF # 1k WALDORF ASTORIA 011C6CF459E8 +# 1k HAWAII HOTEL +2CAD8A83DF28 +555D8BBC2D3E +78DF1176C8FD +ADC169F922CB From ff129e524a89fddaed7b84a4a5af5928daa97c42 Mon Sep 17 00:00:00 2001 From: Evgeny Stepanischev Date: Thu, 30 Nov 2023 12:38:48 +0300 Subject: [PATCH 084/111] Allows you to use UCS-2 in canvas_glyph_width (#3226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * allows you to use UCS-2 in canvas_glyph_width * Sync API Symbols Co-authored-by: あく --- applications/services/gui/canvas.c | 2 +- applications/services/gui/canvas.h | 2 +- targets/f18/api_symbols.csv | 4 ++-- targets/f7/api_symbols.csv | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 85c0528530..44adcd9395 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -202,7 +202,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); } diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index f34d02bfba..a369e213bd 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -214,7 +214,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. * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index d861d85117..7835718def 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,46.0,, +Version,+,47.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -633,7 +633,7 @@ Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" -Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" +Function,+,canvas_glyph_width,uint8_t,"Canvas*, uint16_t" Function,+,canvas_height,uint8_t,const Canvas* Function,+,canvas_invert_color,void,Canvas* Function,+,canvas_reset,void,Canvas* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9431028abe..f31349f9c9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,46.0,, +Version,+,47.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -722,7 +722,7 @@ Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" -Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" +Function,+,canvas_glyph_width,uint8_t,"Canvas*, uint16_t" Function,+,canvas_height,uint8_t,const Canvas* Function,+,canvas_invert_color,void,Canvas* Function,+,canvas_reset,void,Canvas* From a849d49c92ff6d015d558717c9bb5c72ad074d86 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:51:38 +0400 Subject: [PATCH 085/111] [FL-3682] Add the secret door animation (#3233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../external/L2_Secret_door_128x64/frame_0.png | Bin 0 -> 1648 bytes .../external/L2_Secret_door_128x64/frame_1.png | Bin 0 -> 1655 bytes .../external/L2_Secret_door_128x64/frame_10.png | Bin 0 -> 1552 bytes .../external/L2_Secret_door_128x64/frame_11.png | Bin 0 -> 1548 bytes .../external/L2_Secret_door_128x64/frame_12.png | Bin 0 -> 1577 bytes .../external/L2_Secret_door_128x64/frame_13.png | Bin 0 -> 1541 bytes .../external/L2_Secret_door_128x64/frame_14.png | Bin 0 -> 1578 bytes .../external/L2_Secret_door_128x64/frame_15.png | Bin 0 -> 1897 bytes .../external/L2_Secret_door_128x64/frame_16.png | Bin 0 -> 1918 bytes .../external/L2_Secret_door_128x64/frame_17.png | Bin 0 -> 1882 bytes .../external/L2_Secret_door_128x64/frame_18.png | Bin 0 -> 1907 bytes .../external/L2_Secret_door_128x64/frame_19.png | Bin 0 -> 1974 bytes .../external/L2_Secret_door_128x64/frame_2.png | Bin 0 -> 1636 bytes .../external/L2_Secret_door_128x64/frame_20.png | Bin 0 -> 1937 bytes .../external/L2_Secret_door_128x64/frame_21.png | Bin 0 -> 1894 bytes .../external/L2_Secret_door_128x64/frame_22.png | Bin 0 -> 1923 bytes .../external/L2_Secret_door_128x64/frame_23.png | Bin 0 -> 1966 bytes .../external/L2_Secret_door_128x64/frame_24.png | Bin 0 -> 1958 bytes .../external/L2_Secret_door_128x64/frame_25.png | Bin 0 -> 1946 bytes .../external/L2_Secret_door_128x64/frame_26.png | Bin 0 -> 1881 bytes .../external/L2_Secret_door_128x64/frame_27.png | Bin 0 -> 1902 bytes .../external/L2_Secret_door_128x64/frame_28.png | Bin 0 -> 1969 bytes .../external/L2_Secret_door_128x64/frame_29.png | Bin 0 -> 2094 bytes .../external/L2_Secret_door_128x64/frame_3.png | Bin 0 -> 1599 bytes .../external/L2_Secret_door_128x64/frame_30.png | Bin 0 -> 2028 bytes .../external/L2_Secret_door_128x64/frame_31.png | Bin 0 -> 1922 bytes .../external/L2_Secret_door_128x64/frame_32.png | Bin 0 -> 1900 bytes .../external/L2_Secret_door_128x64/frame_33.png | Bin 0 -> 1938 bytes .../external/L2_Secret_door_128x64/frame_34.png | Bin 0 -> 1923 bytes .../external/L2_Secret_door_128x64/frame_35.png | Bin 0 -> 1913 bytes .../external/L2_Secret_door_128x64/frame_36.png | Bin 0 -> 1937 bytes .../external/L2_Secret_door_128x64/frame_37.png | Bin 0 -> 1944 bytes .../external/L2_Secret_door_128x64/frame_38.png | Bin 0 -> 1683 bytes .../external/L2_Secret_door_128x64/frame_39.png | Bin 0 -> 1662 bytes .../external/L2_Secret_door_128x64/frame_4.png | Bin 0 -> 1618 bytes .../external/L2_Secret_door_128x64/frame_40.png | Bin 0 -> 1725 bytes .../external/L2_Secret_door_128x64/frame_41.png | Bin 0 -> 1654 bytes .../external/L2_Secret_door_128x64/frame_42.png | Bin 0 -> 1495 bytes .../external/L2_Secret_door_128x64/frame_43.png | Bin 0 -> 1440 bytes .../external/L2_Secret_door_128x64/frame_44.png | Bin 0 -> 1445 bytes .../external/L2_Secret_door_128x64/frame_45.png | Bin 0 -> 1464 bytes .../external/L2_Secret_door_128x64/frame_46.png | Bin 0 -> 1446 bytes .../external/L2_Secret_door_128x64/frame_47.png | Bin 0 -> 1369 bytes .../external/L2_Secret_door_128x64/frame_48.png | Bin 0 -> 1529 bytes .../external/L2_Secret_door_128x64/frame_49.png | Bin 0 -> 1851 bytes .../external/L2_Secret_door_128x64/frame_5.png | Bin 0 -> 1625 bytes .../external/L2_Secret_door_128x64/frame_50.png | Bin 0 -> 2132 bytes .../external/L2_Secret_door_128x64/frame_51.png | Bin 0 -> 2258 bytes .../external/L2_Secret_door_128x64/frame_52.png | Bin 0 -> 1615 bytes .../external/L2_Secret_door_128x64/frame_6.png | Bin 0 -> 1591 bytes .../external/L2_Secret_door_128x64/frame_7.png | Bin 0 -> 1671 bytes .../external/L2_Secret_door_128x64/frame_8.png | Bin 0 -> 1612 bytes .../external/L2_Secret_door_128x64/frame_9.png | Bin 0 -> 1575 bytes .../external/L2_Secret_door_128x64/meta.txt | 14 ++++++++++++++ assets/dolphin/external/manifest.txt | 7 +++++++ 55 files changed, 21 insertions(+) create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_0.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_1.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_10.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_11.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_12.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_13.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_14.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_15.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_16.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_17.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_18.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_19.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_2.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_20.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_21.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_22.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_23.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_24.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_25.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_26.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_27.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_28.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_29.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_3.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_30.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_31.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_32.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_33.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_34.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_35.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_36.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_37.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_38.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_39.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_4.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_40.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_41.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_42.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_43.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_44.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_45.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_46.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_47.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_48.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_49.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_5.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_50.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_51.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_52.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_6.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_7.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_8.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_9.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/meta.txt diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_0.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..8f29d5e2c7065c50e1ff361f437e85f24cfd1113 GIT binary patch literal 1648 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 b3!?u3+$H50?G|~m00000NkvXXu0mjf9Le=- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_13.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..38171c273ca522acbac21d3154c54aca99cd5db0 GIT binary patch literal 1541 zcmV+g2KxDlP)|T}FV6v(CIEwG9Yq(9LZN z5mox)v3Ttk;ETODYW+HZC!^3e1Uwqw5b$t(3Gi@i%K=I$RBNSD$|dVPE!RQyyCnx` z#<+?Ee7z?iqTfVTgKs=;p32X+PJ3}pD3o^ zZAoC11U;*f`TKEC0;=DwRlo=d(0SYQPc(i5qTlUhAW8y$4GkAhHhzPr-{~p<&EmBr zPBIuzTr^ThYt9<~ZdJmlZY5~?eItN2nWpd=<6F;4BtX_c$&!hP4y`$6jgK6XsWK2T zWMoV~R`UcsDZJzm-Qtx*+1-F^-&6Vkky5Q6(}0kme@?>8_C^VD_uS(T2`HEYNXpNE z6tg1t`Ex6AcjKZs4-y3k;KilY%CTzy(S$F<&94YCk(T(MEFQFn_8d$Aq)yL(<@&FB zhv4A~j~GGZ9ZTqKVAckAmS3}b;<9UAY6afvckeA9sMBLgszq4Q{ToH*{7C7FlBU1D zid=sPAPK{--NnUm?2i8)Jo$F(^svgO{@y3HS9yH4;$pUCG*-~u4R^v@yBqR(W*B0&myX10 zE7L|2)~uFtbl%n84<SCwyi2x6f-66|pHAhOlm9LXCPkvTkKy3tRn?V{y+R674pV4G=77^vogp8Rk zWLBwYdVq|wY&51@0Wsj|cFK-!K~EcC=qt$980#cdB|o>BjaNQFkK1%R2c)@0*c+mOekl_(bu%L~9}U)j}yn zG<@ykNNZ9+GF^b36gkUeRm){T(Nzn9SRTk2r*_9uhji9!$ z_DM8f&qrIqcP1BF^RGJj83d`d{=N=IPQ2H))i!G-OY?fdMSD{&H( zfKK4dCZevezkfFZSj{bo1k(Pa#$0{+xr{F_n{VHvhTs{nH}-BuM!=0Bg|3yIPpz65 zLGR(uzYE}>t2cmc9h5?XP zDHpnb=1yerl0XgK9BJp=Lit!i!5_@$J6 z{~y3Jz7|v4^+;&QOt>_{qaMmD!~1f4#@J#NFtz(2_{m0vMCNJNq@S-qG=?L|pyUZx z8~^kGT-p8iRX{dlUDxJpa(afpI%m+<^GJR5=SxyHS7!7iNrpz5+}1{n-ykK7cTEr_ zJ@D&0J`e5YED0K<4M&AUlxoS35@NOI>t)Y&=OcO*JAqP+?FEl0u2)2a`5utXde8`q zA{utGjf&iFP(1)wo~B4?;}PTWIS>+f>qU%i%@>8WUQe!UbJ8;!i6z6&?C;Z2YqkGdc*LD%jYoibtJNp| r(@~1@8$r=n>Mb(srbaLl!k&nK#X!8+KN{(N00000NkvXXu0mjf#LVgb literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_14.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..3eb9a1f6327dffcc27476326c75d9b48c1f75cf8 GIT binary patch literal 1578 zcmV+_2G#kAP)74%zv8YW9hk9RX$=SXVtYs|Lb-cW?65(P5P$ zFp5ZPb|vg9E5C}2^1BPKPf;Eu;9%Q$5L8GG}W0bmN%uSYw za6|B-fd;9T@YY*0%tWMUy;YU{TPLvMAuGVTg5hUgG{~HjnU9F3vJ6I_p6)B=B?%UT zk-N7R4JZa%b;To|5kL#oJcrF@Dq9~&>!Wmz zDqr(+}{b*KG&{7T0!}35usWTkJeRPc!>ZRaHU0V z4|JW>GX7*kpvd$}uCD%3KM$#srFpmH!IFmqyx$_UG=O8#qG)9xADO6m6`l<%fY;v$ zvhJ@GQYC9OK&)};%vA){0gtCH!}roJorCWm)M~X37azAg-Xba@1R4!KJtAmPlfDA@ zk;8%%aiiDEZQK4*02Q{CyCVf2B}!YbK}!ptWC&EA+Evk$anv9@J$Q5;$MO0*fmNkv zj3G)KWwP54W?iee0{MAS+3_{ntG9=R@GYRr7&64sbCFoBc+4WJe9LH~w9wBig-WmZ zzFW>XFLe#4lG5v{(4m#<~ktEsW2U?}IW@^{8rk$nJ~dlnI_9IP=n2mQN|K z(}*;ZQY>fE#=GPT0Drdt%hNKOWg>)!OrFH~o-t&=l`W+q$y1|y)Aci6qcFb@FuJ+N zthcUe`Bu)(L?2EELi13{FU2e+cj!rs03$;71gi;26B^FXFfE-EQ9cdx(z%o_>u>&g zma;2O`WQW2Vmb?nU`@ys`aI=R#YV!Z literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_15.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..6244074889e65942a79e6f658f1ad82e989cb88a GIT binary patch literal 1897 zcmV-v2bTDWP)ewA(h)M%0 z3)Z2kVA_eDJ6@^#tDx9^p&gnTtz-oD%-ggMf0N*|_SAa+$&B~1%-lf)MIYF)uO`8c z9e)LhGju;+{lhLIDEh#s$17|9-FEzlZph|m@wSVONT3d!)#^wejM!U+-8Fv3FK;i) zh9e|-XKeKjWUy_JO++k(47-TX?Oqjpw8N2|Z0$$0=1&Pwu(CWSb?UK?G$CbV^tSySSsl-+^rN_`jL(WOjbm^dvJ-6u#P)_4NxF zrBa_FS1xUrgV4a4bLvEt{!vY3Bp40GY}#K9w0=lDD5={zeuAWinYnQCsxc`;}!K+onR+D}s-NtzutI|8{-|y^r*RkVuWPprG_s0qZkT z7Dji{CwD&6KGiait^cn+r<++;KO!r3|5f`}!pV1+NO z{aK5Y0BS&1Axr-e89Wi9vT)a(@8=TPok8!P{w#cFuvKI!-nZ)u#E4<HQ_*T)+b1d^)s#D$_OlZ}#def2zJ>s;>0ea!9ffv!f8L3J#3Nd8^m&zr zP}`#C%$I)sDgrRrU2XsF$W9WT2(}HgQ`w!UeOA|5Nn&F#+w!a6JAy5+UmTXAds~J` zEUF5*v70N*v52XXRXSBUF!6tUueS(ITp-oDS3!@9jsKLJ^RUCBQ6DeeCzMg{zD) zLjvZrMJ5z8-~LGguz*LqNIf~Ae$nZm5_})*(F|a1K2ijZzzV@C=dG4NgixN_c53iZ zKjat70NF9|v8R9vY|xP){A2>G?)*w-P{!-+ExwWfez#`}H+$5o_+`hAb~bBQ)dtb& z;-3_OUiGu1R>ukr;M=1ykv-*oK&D?Z5oWa*ZTiEP==ibk+wC>blW z`{UEUkOA14+AnUjuruWX^=0?6;9LKh-)h_QEc`6|iHOquh%KfkD!xR%DZ!79m8=c& zq4)Vp0N!&#XTqX%tvy!;jC>{?}@NXot@zeN_l%zS;IDxPB~uQc(gL zwMFyz3Ib%QJv&B4$V6Xelcj4$$n2Cdk^$^J-|n^qP=ck@bemFDyKF~gN52fHBr#iv z2r7xy;J5bB44|B?`!nT5N@PDG&WZyRIvPg4WTz2(Wc73Kejmrfz6-E*5v5j?`q-7h z5=Zq%X%`7lYbPpW=UW4WiISFs{gY@ z%E)tT0o|5DC=Y55 zDiB$tGIAA3wh~~)#Ly1Tc6xS={I!0wqa^a=AAUto`aWvbn-eYdLwcNsTqRosUw*u;w{MUcQm}%#BPm?f1S4av jBvaPT`!kA&bRGWzWq*skP+kMC00000NkvXXu0mjfeO{Yi literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_16.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..f1306c2eb4a149aad4d800fe64d0ef950c8ec0d5 GIT binary patch literal 1918 zcmV-^2Z8vBP)Wg zUzxEx8*~-S27jG!y4}BXZGL{mpKGo4SpQ}KrYs!@%!-)C^x`IV=IGd{;uR5}%^Tg@ zGec*u*m#ETOkp&s!Ymt$ve9Pl>g(bi={~X}S|6@6OXqAXK2Pmy&)n`KqI(k75OIjUI|5i-KyJB-v*yt~#{cC8g>0}Q@ynTmjVzYM!o9C-Ns7P!&Hiz(jL~EIvt9ojEIe%$6$} zzthOi*3gam2$_!Wok-}^8jmX?U)`Xp;X0jv()m|q1E%-;q{f=k>^&M#=~Kpd`OKNv zWKKo?M8j7_{i`r?3P5zDALY@(OukFp$=ZJs@u$%}J$~dAfas8d$*af1Zl=NK-Oaor z-B&iUjNtWE1YqKbDRVsAG<>f*>uTqB*1wZw*qDzZfJfCOvv*NOVe(mnnDrf3I=|BZW+Z5h`BVZJ zxn!bRRJRu$DBA{X&|e4B3m-N?3Y1TB8qY~ z`dOWi<}rDu>ni7aJC*rx9T^dB+XF<4Vw+=#G6Q!0ooHl@;#HB)`VrPRGNNKnVz?~< z49NQGI9BO8C4zsx>wNXJk-g8z=W%-iAa)u%rel`~Hpai}I$w2ObqcADm}~t)0!#vl z;Gsk$%Lvtc>S=+=qkW3L+Y_KVmic>puL4O>rDgv!lVOpl_72f}oec2fdiP`CE-+22 z^4MUV%-?Mo*}UpHBE7dIK&O^6Iee~e9+Opd;M`2V(lb;9$kzJ5Uf_-d$h!S(%v$kp z(7X%+9g&rmOb^OuM>@64CcX*GsDKhr_&k?`_vw_Es^HH~r^*SRPs$(U)$GMp8 zQREwb#qR?-Cq^d@^<+>rl3w=L5ukE5laD^4RL?r^alWka#KshV zRx^N-#eN+Dx*vF0w0T`s$M1EaT-C)#{YaOvkxbMV{J9cY2t zkI23Spd5R~y7+FNVLwNwoyTL~pNFgI?$wBDo|0Z|%uW_Sy}NerWRnQ~0~oIB*L(-? zO!v_qHF+5M_>~Z^QzXxtOtz+pxw9kU*nbq+HS#2Ls7Jl18%CR1(-b|MfYQiKAtIU0 z@zG6ScY~sIj$`qCmH||~s*TK+tAM8r|J?9a0#u7v&co5Q8mH>naejtM&vdr&dToCH zY>NP=tnDr^bBeb|%>Z~mMFM4ASXy4yKr`FuDL`>#8I`Zt87uk0O z{QTdz`BzWQny=~_>8W%uoH#$ydn%9iy@~{D1^-iMHw)}y#{rS8-C8~6{7PR}?rP`b zoL|=Y`addmYq|eGGMBme*a literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_17.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..ed8d96bab11cdcdde1efdc162669fa9ac5911ee8 GIT binary patch literal 1882 zcmV-g2c`IlP)>mitID=)!YQzt=};hXoLG?^ zw)bvhjD3tT_I)2?jInLon3&Hd<~w|j?=ALyAKSKlyPgdkFS=SkKjBwrEZcAO9JcX~ z0Eoy~hX}L%StO_g-7efG0*`)LsB5SaIkSsnY=7JK7U61&aP{CbKNWO{E&DQ~e?&^6 z1wEStciQ=tKw(O*&P^f0zVCktfI;Yzp-KdtSQl3fzUmk2gU_blRh!ZMwr%6h9A{)u z7LBpid*>}u6?YH5I`5ABE4E+Cz|(joXGO({$NJe`9p`GnxBhqYwfEdxqw1O=-geMv zf$R=uzk7YW#m9+OcgEF%Z|8JY(WdO^%H|nEPyhs5)M(I1K~N3Y4r2DN7W~f6uabZ% z=TYqYUMIlpXalOGhzb$Lh^yw6xpf7)L9Zx4SK`qIu1SjkWBknXRt+LDtWtz4kzhse z8Nu!SS$N?tt^!Gb0qhb1(Ou-T&{ZUso<5~i*7rGPTLCOsO27dXBCzMo@!t;q^sl2x zw*99A&+uC1gqIR5G9svHVO3S#!I6TG&4=0c?iJsv@qA^)TNO(6?Tx9Xg(wKoxGT^J zy0YW(-Re1bd;LL_}ci7*-UDM}v>;)wB{7a?v;ye=nr~2%Nk}gh)2eP=wAr zGJ8kT$`M>z0W7@T8|`L>ND)~7vkSk-o_)`hDjJ8)gN|_*0!(;8&uRk(Cbi5)Ca0{> zM1Q-d2(O6sQVR1UOdEG2z!>Y)V8RPXb}I13QAohrF+}sq>IXK*AHLm@027%S*k=kn zsZ|Tb?0t3TM>U7y1KMHNOMD0Df=$|crT__*Romzb^R2t{I~BjR&5o1odTRoVQQNhu zxUhYA-Cy!Qu2#f?( zBFuhhuad(R5@f;3`a}qcbLS!u;T_SU${5>5U`yp4$uhs9zES${j(VB@p4~?gV2nBm zCe-|1Jym!

NgTqtDDYR>z?IOMePpg^C1wCyCjnvR}NKp_9*#AgT!-qX24E?zLt8 zj-(HdG?g8n%~SQkv+iQ7`zY1Xe-3;6`--yGQ>$^@&ae?1U59L9*Oh z0X(R?DGR=}=M|QfaiX!50x>FVncb}afAbw6LKb}6&fERfanR=cT_FSW6YtgFZ=3Uf z3VkY+4>B3lqZclAmKLIqH7N6$pPsVN_xq>M9gC3#B&zsl|EQg+5cWN*?9~8zZO~5U zS7hfAH9&TBvXYAJY~QK^cu@K)wM6zjssqp$c2apn4WLx|o@}BVvw%ba@`zM9#v$>n zEpNQ&B;bt^`S)e_?kS)Oc4SiJQdxNHm)T}@2}74KS$cTHcYq3%Jg$N*EbH$Dz`j>f znrF{zr_=`j9RU#R2-WR-6i5Q^qmN=IQZM`bFaeG%!0N?gKMb}Y@^*--1WG$rjCsh^ zqJzlBEsH<~MLJn~Rui!2_I)QPM+kL9X|i!2p8{rH#-A~G0a#Ul&c`FqwsOSlAhPjN zB=Zb(chah{G5GAb#lXI@RmQy<-s&-AjL-kE!b(I15&PtteUQQS5s){t6URD)enfQSUDAIjWTC}mXFyfcRwx=uxs*-;W31T5%Q zU88YT5@dFx_eT}LJLOEg7%LQXDqyepvJR?OZw$PitpJw5RnmCxal9^~LUz9U10`a# UP{|Ze;s5{u07*qoM6N<$f?@!a9smFU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_18.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..e1df3ea495e94ea5d0336d3aba41f02af8dcca5c GIT binary patch literal 1907 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_19.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..d231a656181d21d77dfc168ace0e42aaffd02137 GIT binary patch literal 1974 zcmV;n2TAyeP) zcK2Vx*@Bo{$ALSO;10T1elUHs?`5*Rn%T>D0(j+UaVq#8olLwk304OGh`(&F5m714 zY%6^x`x8yBjIrQnV`{-yGQrAPztX1+m7ub6Qo&~YA1y4RtH`m6O~aXG;cmK*e2SiX zRG7&7Sx$ffJwP|hj!@E*lHd$fgJkIXtC~tL_v$!eikNAU{=V;0&YpSa(8-Q}2xjjS zg3lT}gzb$z?=V#a-0v3#p<;m1;>tuYd)>vcIX@-%Q8JpZQ7l&YOBr`GN`P`Injmwg zQX1aQPWpFu@GJh<8d28&HkqoOsAQY$iy5zil>}U{vX?OeqQeB$&AR*KcjsH>x8I)^ z4tAkR0!~;-1U8O#D!l=ofIEWUO~R_p6?VK|?;haj!m}i>1w=us^as^N&jf!Zx=CoV zxCXS@DI*a~@0E+z(V=Kn=PPA_(W)Bn1b_AEKf*kfGMF(T)AjbZZ5uzU7mpKUPO-W1 zMA&b3Qf6s>b?{gDQJqIUtLXpocmHe6E3ev&s#Dp#DoE(_-6!AswZb0^cGjcdzDK3g z#@>Gyz$n(E^W0C=&2F^ie=8be3w+wiKdTpsug2O=lef=RwSPsepT&yg^;n(& z29*TpfFE0(2#PJdjSg1OeYQT;OJTo-C03PO=7X1gUjxsADhW8|okh6{lr2X4M|y#k zbYJxs*O>Ja*}nbKJv#vmY$F(DGuhchz^rE*+X>bc!Ds8p)>AEQOy{x$@D8YFW8}5D zOWSI5x{Pq=$&c36(WN?9CcqfAw^<5V@-SIe`yzIkZSYnFzspvKf`2sw7&5A>=qY`* z5m^tCF~Us4r`9lIM>TU@TemX+qDuXlKH8W`1eIW`g3o-P4%|Um`&{-SXclbqJISB| zs%VkTV;jPJsnYz)liw)==&ypbe>Vd#RO(r#Uo8!0Z>B6LyDO8x+sl}y(ieOj129wq z9Lcd08Z}BtpY|P3nM#7p_R*e#CG=62G@GZr2|N=K#e3N1RckXQf+`K2fMAkzPClEb z3Z-l7v3VYu00ti@47NSTWT|W@O9XGz%b-7KBV&+=_Rp@)`!4=Hz*C^IoHcQNlcOLi zJ6)_i@f}&V_6Uz<0Bpn*)kmemGeiX)COPB8M~y1GEmZJG2EgFshTw^>pvnqedz~)7 z>tocIR+-?D31DEsXXB{jfr+FNAxaP&!@wR1z6vU{i!xF286KSg9=H)6SQ40?qT`5d zsy>;`YVb40h~)e_v*3^M;2wY>qHljf*hZl&21)6o4(h78W`d78-Q-|Q@l^@HU;*(B zpQ22rYoFLKs+)#-p3UX$%rbySwE_Lg1i&j_cD1_8zcKpn2@oo|s`eh)14O4{j9RU} zEBJoy&L`>_SQCuDBfF1GfHCrVnw^+V@L8f^V;MciG2bUtgWvVn!R!3Uyn_I+3Q#&qvpXR=z`HSYeuWVM?X3x@1I-Aepey9 z0M#p(m-U#X;K&y?M>anuP;|XSyGrN%>>CrH60~mc*?0CEZJ<9H)2Q*R@$KmN-(PzT zJQ94gHADMd-=fYwD-EizVT~lN=PMIH!FR(3`##gvcbDUFDN!P1tb%fttEjBa;14E21ePc&N&XiMz#1tW_ra)apa-7jYRSMLt7Jhx{W$(H znWzu)V_%&BjPfzKi~(lv;HKN@8>*$InvB)k!B;ndW9T}H!S$aiDZI@pvPRU)RduV0 zrFsR{_HgL`# zKiGZwaW$ga7`-WDa!N1zT(O48&dRx1PvgID+u#2yST#Iq!)G1r=tcEg$y9=c>iaz|5hhKl$4YKm;t;u&pNk-*{jI0 z=c|BwjK9m*z*(mhovUCUX~HV>+nFm1mPb`STT?bq2bBZ;2XF0HlYkc%w*UYD07*qo IM6N<$g8wtl{r~^~ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_2.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a864c76259bfec69678932d7d9321cf8279f41 GIT binary patch literal 1636 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 XJs`E4X;N8O00000NkvXXu0mjf8}P7( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_21.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..0d407de64bfc2072706a182f61cf9c0aa101cd6a GIT binary patch literal 1894 zcmV-s2buVZP)lgDWXM^mm@PS!#j4`(H9{@y- z00QIS-GXoBC@>%!_E}Xb0QjJd+C@AAo~|7~^3aZuBQw9VpUR%AfcYm{C|hjb_n(b; z^lu%zd+@d2M+YC-?PUCnd@{oIb6x%<@wq70q|f~SYZq?fp=_KOck zX8c`5{De&)YVI@`>_Sn7MvbB#li%98CuY80hY5bR&raWv?N7_sHPAYzjn6Pb)E{`x zGJ3Oe*NjK&Wo+01NeBQD4+zMhZ7Q?1JUzSSQ9zJ&uL?fOaF1!Tbvmp`0l+$DDb6z* z9ZWPA(dp4W!7KyJ2%FWYxwiHh5a>DXi=fP5+aF{GtH?wEJEAwxd$=O_kzd|EZNRnv z^4&JpX#Fw9*!GDXpyrJc54{nfY$mKe{40Z>om-_0i9Ty%3IKZHDhNbSlj8xw13@Tu+DiKL6UX23=m}eY!IxLtn05dqAU=W0U+8yBnaK?+IIa; zaPAa*#C)hkvi>nixbM!3ppGEJ)#lJ^Sfr_mkZlicTVGy|~0Fon%(tD~90 z(jQ`k6=h+CT-kFVU&i?VEKUPJ-9Jbgs3dsHS_JhqIGBF$Ie+Lpej)f&S>5n zW6OePx9Wd2<55C*tPqvHs1$tpc~=0490>&PfV)SsveC@;fqo+}Wq`gt07NQ4$T9|H zzt!#1?5bM^3NqRY(zW{DyfGdG0Fev`Sq+Uk>+H6`__nga%o;+rl-T|Ldj2#3h`1Xf z8>umHqlo5ZL5uWf8%&SZyEh2y!?HgNI$NUVh;8I-+Kozz zwxgd}{l@}ej5Qz_BL;%j=}>N$_A@4{tQ|4NqX94m4+3;RDAV;^IVc+lbl~Xssvd&X zMdO|?^7(iGh)BSqI%sK82idNLbfP}Nu3yPcq@UGwuOxXkytOZV6SzW{0CxLayN-O< zW1e)Hvgngx2djt9;Z+P^>!i(SogpZIsJZg&$lOkY8I3c+pApLxuVa8Q;x}XHxy_J@ zuxyg)v6-)R^*$o8<2Fft+1R`Mc(K?_N7NjF0L_VNJWKs(4)UA*v~%tH5mPV?@__Al9>#(Jp-a$uXN}+9(9XeC-@ndsv>h=2Y@3) zvjQpw%(&KLgOBDRzdX?E`AA;D08#xz6z}~$L#mnIWd?7)=xcc_0Bk3ZvO|CObk77r z#00FK2Ldb1?PA+b{7a#$1$FLrJi50toxdLUZUx?4=7y2^$Gi`SZYLB7o(OIhqnO&zk?*XE@-m@(DXdf&)K~VfP0EpPk zXYSK8N5xWj7JTGu27;(OJpM({j8w8Y>jk2Kc-KSy+m1uJPMSt*-=lr~`%eHMYVN#o zq?=J~=7ZKp!L_zLmdY5Q(nGus09mk+el#B4v8(fSKs?ZS!D6nB+9nD<(t8~MX0lSR zfifz)`c-|4SO@_jdKX~lGQl6?g$%%mcerTW)?_U!`ezHS2ng(zl@0#3{jom_&~-Jd z>r8MXDz|#kEkm#4*+uH`jaR$$WbDnEA;59mU>g?B}*M|14k@t<^(gf|2R54A22VCQ$>LW&bR=G8T3J z=g>%OqcYzr4#AM{-0Ux+z_&U=u6Jpcdz07*qoM6N<$f_-9@Gynhq literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_22.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..f53ee0588351daa326d75d7f83454511592676a1 GIT binary patch literal 1923 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 sFcLjT zda;iÎ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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_28.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..b16dda91d5811ca36993ea8783a19934068ea27c GIT binary patch literal 1969 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_30.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..08074849841151bf7e31c917e604a6f103c05bca GIT binary patch literal 2028 zcmV-P)t2Cq)Mc?8rJO4lgBX zuQ}%&V~p#%#{b)I=(hDfd$s0VZDWkm+!YzR5n03P!=A}@PnS!FV+`2Fo;^&diLjY# z6sJf(P5y6zcI1WU&O{y}MsUjG-w_E;V(medqZX(%#GZyfY&F23WewK&DzfKAi$Q1tKf!z;B2M zULo3rtYIMH7fPQ4WQ@Qvh6({|{ZBg@Mo%Lr!BU8|_G1mSI!T421?0W^i9J;jHN+>{2!hV{6{RBM>BJoo0Flp%<7i!!NtK-uX3qKbWss!+Rtr?CYt+Ac zQ{6{FDQARycFNHwBXm+i`-%3ALY|@?NFknbqMl^6oh-;Q1u8Q_M!NJ~?U`&ZRq~<{ zA`29s7 zk!RkT)^ivEJVYV{6ZDQMUCLQC0=ADG;-yUNy_}oP*_jfadc(T}$T28i-bKDdN4Vc5 zK8sWHO3XRGBEWicVu3MUKR*6+@}tG<37p`;TIZpw*(xlBu&|d2!#k*wFVlx^i>|A& z=36f`*JZ5y3JtFWgs2Mo%66;p>r96l*95H*A(&Ts-n!$>)v@e^6Dj&UL<&VDi(aEc zmZ+R^UYY{f0QuZ2p4hxB}1WkWR7dPP*! zQ!|F+DOLAHd$FQemiP>56*9XcfP|dyu^KdSQUH{vn0IA`h~~RtxmIfNBKr5UIE$8$zcNM!sIBLSlx9lir(wNB$V#yMPF}UF1t4vjv`9 z-wO8@@hl2>1X|BOg?tSrODO`#2`wWTBTjInc0g%L8$s0LQQe~D_4Y3NTYap4rw}#7 z2P2;;a^1_9Tu}E}ZJ857cB6>wOpe#cj6u2DGByicMiM1|&dI+Gtb}*H_&X5I@yy6~ z)|GQQWWx@7W-Aff&^2Y!*fYn(}p2_W9 z$Ql!qBU_(jQXs9s}8D0%PXC@N zu9JL}wyYy7bBf4O`1{YpBSz3kTBpKT7(~^+hj)`7ktcGZiKY+ub+A5Hzq@)xSBXgI z-}!98n(OX@kLgSrLgX`mb)`QGM~iiaey@0ncw*#8XubF3^x8j#{D>p1e%jHW$m35z zp1;vC5Z$wW8F@3t+jX+A_P2|OaUyw8l>hOA5Hx^2uR?}V`I67|=>UyqXL__w#=ub> zvHST!hyJ`{XJlW<2p+nFR@@0xk2SKNme;EID@AP7&={;J7F}Dv_tK^}7t+T|4WK7J zbquVKU0I`Q1XfBqo%bU$M#*PI{f`KM^3^Ht?}DcQS!-m-M`>i;qi5E96+^M=4J$RG zzG&aD2Jkk5gzQEnk7dC&?a3j>=txj0LbEcn(~b6G`C@Z;*N2=~TQw?((0$j&rP8AY zA(f>qUH6Gb0G5O0J-Z_LqR>1b*;!G`jNQ9lUt{buH8TkPTngNU=+TC)?J3Y|)Au{@ z3b`(wWyqg%zW$FODWvoU-KP6@g>a-G_Npqwg6x;^{A^^{0ZTtw@~`Xq`L}^61hK^} zi|*Q>H6N103!QRK6amoM6{BYBq4}bQSs^!710btso6`!E&pN=Oi;grX+{-zT=X=JD za;Y2_xmxpNpo_i}*9TLkhwl)Q93M>=5kr)7N}05bmIosFWIsfLZfDO?e-C$%{t^Kq zAs-=|=OUpU&ClAA0WuOQH2|8o>iLoV-bK2+nm-BCf-!+38>^0u$Y+hs7(rHfQjSO@ z$Qa*U*O&hfpcN~Mx0r{h~Qi zdY6_F^~)GRBz;&O{~L|qi4?ENVR_Bkgq}w$nHfU_(x{>#{d5G7`>v9&WxnJ3(2Gmu zYUOAFkpeZTXdVT{2++0000< KMNUMnLSTZsm)8OS literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_31.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..d4dfbce33f3b5c7aa34e16d5b3b7c855a3741c71 GIT binary patch literal 1922 zcmV-|2YvX7P)4V~^Od12cB9Hb(8_J~C};t>w-vQVzggS?nVH z1o^Y}&w?QyA?*N}Nf;;C74c6f!A^Uuz044g=1V)k43H5gNPn{uWX3HePrQdq=Y&-l z_3#}|Ajdz2oeI!e3;WhusWo5T!_`iq(KkWj z^8U=YD^iGC1E7hc=Q5|yc&Nl`MPSyl!sZ?GWQq42E$skI6j>FkbcPuMRylzjYX_pW zWz;CUPgYTl+Fgt>oix2+vX3QkdQ7Y0AAxkJH>Ck9O-Y+~LzAvKusFKq0|{h2iXgN= zJ?To!D!|H<0ZK2Yh?jO`a9bXNHij+4x>45H$na6jIKeLRyIo#euZ?bxFhhLD%@|@y zIU}z~ZdXUeY&saRCt9~|EA5=c3gSE57eR>lY>w3iuasVG+iCLq=Wc^;r|)Oxjq0)$ zxLL16i^y!Mj)ZS?f{ff}@9TLyWXAB?-ce<2J)5bnS4LuXd{nDM7?Jxd*nVugUgJb6 zb)${(NMYsQ8e=EJ`X=&A z;S0_yOMZtYjagWRgjr}Mo4NM!tYVg3d#2q<+#>`;vVyR?K(p-ZM2#weUN2Kvd24?g zZoS}H0<0P$lK(dqhN!?BCH@WYY`;$~EJON}-&<+tNl4r8RSSsZALV5EOe+R6 z9)nTxy?ft+$X@CzVVW>cp7n&fEjn`P`63{xj{102zaINE@jK^$d^PYyZ-U-%Ytyo% z&dO-kajf>JRzZC<*m@;ugPzaSLTJ76v#9{G5la$NK?%qqbJeK`8gx;UW!81y>&9t^Lq2W9abu~+T8Mkbq2N&Rqzb?45^tu z(gy2iWXmY=Pn-fWX;rrI`dz(~cMP+tbff@sT*t@n0xTs!N4v;byrb4AvQ8lHbvxue z%ch8U8HL{hWUKkR0A`alv0kPBB#=sZ>7o>0AuNzjydip5!AoP_Dd8lt3OtKGhxvT~%fr}|ASdYecWmqm6|nOA z6Vb2LR#*2cfa z#xN@{dl4!NIM4QuQI+%HDGs`>oZI?`;LU#_IgB<9Mb1n_Nn%ljj%@OrW!A2Y?cR}k zH?qgX*2I4gw;aJ+fJc7jyd4p1r5MJR%yT^-+a_|l-AXWvTGd|)wO;;skuKX>KR;br zkAOK^>UJ4*Iq3w^{{JX|^d#DZs{86-VWJY*Ajd=fBcRhsr~s#AP8x+aPu`QZL{2G&&sQ}v8XfOtu4cEk@qD3zPjS*ZzUKrVUkXJ7 z?SyyYV3lW;6EKCZ9?vTnvwP>gK+4o$DCd7DfaM)eAUd|NW6U~%B;U*NyI$^~n!q9u z*~XIJns^iV7RY1ePP;rO@gh&>^(fD09K=!{k3g$WhWuJ@q@T1T#^>YjmjltcHIDu5(glP=|Lwa54IGmbF2;W`+s;9#!aI9Yq7T|2HHe=FEB zlv%*;yuZ`QBv6s%-AB)@&zbp8T*Fge(!q|24O1%i` z&-F$z{dPdkC+*7Gq}ydTPtMOML1fcu@_%Xo27069qIbiaN7o=;TUooH=b6px^Xuzq z4e*!(czFsfxc4~`{W7A^oLUtuN3bH-d(K9F)@eMPc7mbr1DuL{J-0<(p3IYm+kr(-uR=WGvhGpJFNhJ02l+#Rk^2=ZvX%Q07*qo IM6N<$g7b8%pa1{> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_32.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..c9c169f2eb4b33cc403c054bc170d3880343f82c GIT binary patch literal 1900 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_34.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f3a5383d809824126230e478a9e2ad74da83ff GIT binary patch literal 1923 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_36.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..4b34fe377c1b7ed112f6b453fbfd5ad4d004e4d5 GIT binary patch literal 1937 zcmV;C2X6R@P)JAWDFmT z{pkGLh3w{J>gaX?DgQ2X7~j(Qx-M!ivuBq9c2lQS5dKE`myl~|24Wd$)nTRcdu!d6 z0$3x+3RTYgElyB196OI4H(5e6pgx21i3njJ9pSe)L8h?Vecp2oYn|y3A9=H@4q)rRPm0Iu2cW^VgK`yfrF! zL)zqnqFCo~^xV5n;d)X1>Nxxr2KYZ1KVrLcjQocZEWKKE+kNm)w1zv_V`plzbag)n zvJ9Wiv6@A89NrVg@7#waYh?}1?#MPLx{09kedSOk_3m;4R==tdcNCI4jIV*%d5kg6 zXhZ1PN_D+rgp8vy&LGD{>;ASb0@)0WIjw{7jKb1Cl2Ks{xo&3T6-My%l=7b_Aer;9 z(zZ44%os#2l4Sry|K8zS8;oEXYb8A(r@9+nFwOVYJPAGiivcnU>1!k-8+k|;V*t%! z+Mz1OM~Bbqb2lR|sW-e*A}|lreK}p7$=yr82LNSEjo|RwWh4IOK3#@ zC@*)d&B(LTf!Fy+r;+z|ppuHKko%#&4%P}q)xI99smgm^1#@V~QJ9n^b=?8I?v6FB zbA}%UTZf5qxt=G-T1B0gW3A^qz_36!TUqcNxWfr7{h!uNBN9lM1ws zT5mMwPHL7qbjsB7%`bzPDE0EyN<>?R+!2XZH{$7Y7eg_va1~R=N8gZF{gR4V?UR=l zv;tXr&E)xL3=6CE(q|&aTGuYjbJi~wQlHPf;d!~f=T)q_a*hf{Z;yy@jF;qDs0N35bOvS*Qq=p95HX()TKnbr%z4f!2rL21d@nI*a8f znfkzUFjT#>k?bKVNYUS{aqn~jrVuy34P+v0UcEwal@6nb)l3srz3lpI>XPa4=?Ir# zj6Vj!0Eo)1>KsL(XMDyO9s}s~!nCT|&BZvc~U(?n>l+PnXOYSP_RD&pIbIYCU8ke?I@} z0vMY1s#>`j)DC8i-$|*ulbAqhOU_xX8?xO*1&r|#8-A+AStdoSMm{ne>lkc=BL=iO z7bFKowv@ZGHeij9^f)WVmjW%CTZw#&HnWGwv~N?cS1VME|3onu86Uy?JwO!U*7F@X zzjC0=cvSRSwE~)po_82Oa=gsgNT0vH4D#ARRxqN^3@kQ@T2F5-vle=mZSA@vKcaTF zt#zOOd(D3u=qX$^{+!Ef!ZJFN$2dVWH%bl68CALfrhCZ*sK(E~hi8p%(R`=zJ$aoi zrB^6wCa4;}iYW4}^JfG0Ck0qWX9VtweCwHh?jcGAndf&K-_mD7x2udk+Q#P3!u4WW zFglc7(byq(1(uV%#rRAcILGNg|LBc*RX-9opRE|Flg<&kD)Wqt862p9T?K0+tq)@P zzkU#cCRoqAB7V2=8B~qS&gnR-sO^k&vFf{z9)OAot9o7C9Lizrylg}>2CG!@jI8YW zpkPG$v7Lw1>5BpM0A1-uMN$<}TjfDU;GH@6F34bvic=3(i}}R>9=+=ctA(F7+MJ)I z)#{v8==Hq)SM-WnZ+6d!?$P~^oPZQT@2eZr4%1cf-|Ykv-RoLWtFK;>X%&2C4PX^w zY=^*ZEHeHIqLkpV)#|?FbwxI^gJ(cACu98MIR5@UfZmi=paY%V11gHLwZZDXv>{g3 z@E9?=&i5JPU)N>-9$;5n)+N_VN!k&lk@TypJ!1^i1B7tPUgT%w-+BG|?_le1OW`hb$y#*;7Kj1n z)nft=cN_nQ0W4)$bg@9ELwSx+&BwCys4@Yw#v=@Xlwl7hJEyu<1=A7aYu>Nu;6;3N zW#zwu0kV+Mv0~6C>j`UZCgSCM>#!NTh5<5|8<(4`Fi3Xp4xm~{?+Y1SqRIeI)Byhg XB{?Fy!d-=500000NkvXXu0mjf&_JrU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_37.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_37.png new file mode 100644 index 0000000000000000000000000000000000000000..4935b75a6f35f2c11093d8cee1a0e9991e2a1bf7 GIT binary patch literal 1944 zcmV;J2WR++P)NS!%o*lQ%)q(yt(Y)&5mv#78JQKt+k-1iK>sj!dw_o@ftOh#x3BzzW!+uOR)&nV^aa z@!8n7ufJWWF3y&YZYQwvuR;g;%;xhvrMb$Q!wj%Hb+U%=8`Hmq)#Wpgnvqr=);fQr z>#`D{l3+VjJMUYZV3s&_o;q%137-M|9(?YI5Czr|zQqYDjot439y*ww;Sg{8u&WPH z>TV??cu(%=3A*tl@{dF3@|})w$b?$Y8%A_&y7lI#HHJ?kzk;#@D4u2CBWGA4pvwuY ztSj(D@{i-k0Wwl;9rn-ty|3LC))`hd!#dQH%D__*od!O30Gi|{vIxqWjUC>VG;me&^x;Dy`9& zXx?XrkeLjC(!WANwqT}bj9+mE*4Q%pb|MO<$QHJT5zvaD{I_aBeKYk^s;o! zY(dU7(-_`OdREJkZOZ<9u54;L(mtxe(HyIX-LrJgoEhDBAPc-sT|?HVOK6ttcKm0T>a|YE}x*{QpxNJ@}yLi&G z(~h01;7TV@8u6(NUpIm#d+Yf1W4*G*Hb15i;@>vZ5XX*^A)U!ANDcRvT4y4ZETASn&G* zQB$WXvk}T7Mv<41sy3pC)Xa|T9g>li1-4IW)JCX8{&@dS50EuHR(RH&p@xddyaKGQ zBQkBDfieb@kDgtzUWI&Arrxu)vsxl%$-kFIni%uY3_SLmNBOlH=iG@mpM;*F1sUloel0w6 z0)_k;;!*UoF5X8Eo2%$zU{QxJsE?L3Qnw=KMU*za@Z?t_tjEd@9b=d8Eu7gdJz?n5)3-i24ja-*QU77Vedy z&KISpF7j9FpB<-U0rmY8$ybhnW1J84k0(89ikPtRepfVJd6Yt6aj?bv9tEuU{p>p42V0J0T8*&2{OYfi?oLN zXW7Ek)VB-10Y;dyEj#+EeF#eT==s}DK!%|EmFV}RS;J;h@g1G`Z04%SN1eUjcNUDS z;iEkOtHaEY8Tj&o^s#3du66>i+g?4YrOnHV2t;!#*TIlZ?Sm~?Upj@k%CAB33C?0!f^JQ(Y z@KMiDDHDDk>(Hafua=}M$Uo2X_s78&na4gUYcNN=!Z|xLz*VIG$^cfmH>R=ycf+4D zX69$GM55Yn$ISEqSCfC9C;v~xRO{y|x@4_7f&%LZ=<45pMFv2|sOZW9oemW_!b(GC zf49$-YemoP8KAN;=zdm4H-;nFpYeRf1|Q*8#%vE@<=vhEs<5?jh0r_u38vfEenh&( eYzDZa2lx*Z!7Ci0000P)2x$ZqmI z)d0uhtC*zw-2pqw_pJzwGATunD(3!2d_ulf6zGGTJ&2=BawVclMVb5Gu%moWMWD2y zYeD2ZVx4zYertQm_pJ-aGKt{M%0IB1e8n~}!X*98+VrdZAM7Mwu?Fyn^W%x2Kx3xh zoAr!JV~bSpk>YK&e4!#RT3AG@*cH`&m2x8?wnF*zyj2)h%U9y7k1>X4od}Z>(-w1* z@}jveij0RK*ITqAhCd_Uv`m`tFvwC4!ekY5(*72o)hQ;*6Uxs{@`Y*u?FO_Qr7^VH z!!tlaY;;&N%L^*s3gQc(bE`&&xmeDZaX`>!iJph6I{&;(hL2V0YiE-lTVjuGMO>H=57TLeI{%JZdt5F?oJ8m)Mx z5g`-eDW4K%c&>^PQ||<>R=x+XP30eL-GSqNv?5Sio8fIIJX_Plo`$QHj)eS7QRq$D z5CfO?`Ltt1@N5l#5Ja)dO-jm(To+ltV(q74wt+KKEd;AN2V@;RqU&3;dCt^DRkMvO z9n%1;;-x}yk%5a9lD%;44V+NAo?tTytSG_}I@&!f`NNRj`{|QmtP03PPWqj%*{z^M zWGk)b1@SWyIuNhdX5mQ1oNcwdhbY?f3Jbp}k4M0D9G(MMLX3oHYhP(?#879wb(Qj~ z@_i-Bo&`j_%QGT+Z0%etAO@K2!83}Fw4W=Lo_#g~8$gv;ctw>J!Vw->CbsZvHlXVlV>^{C=^Y1_2t1-}6gZ63ZlZ|(xg*wK5} z3>Ce02iZP6-n|s2zF3-Ci1lQuM40Acp9jN z6gKI{GCDpSqMnBss5BVjw6*WMBb!FtE?@3IVwTf3p z1Zwo+JAqd!eP#%PMggg&iF&?wl%Vk@4UDpF911A{_TGQmKxy&HHjwT`Tf$jbU4j(> zH2%DZk&Oh_APS{{rVlI4pWX+c$E!m>HH204tq6cdhG-d0t)fwcOh^VN^lN>)w5EekDF7S{bFkMF1+KtKsE8t4Pf7Yzc$% zGjp^ejdib8_Ypb1M!}vJT>;BWkci>&?Foa@myX`LOwA$jhoD0FV~n3c5W4AXZnl)m z_tCEBf;>K+(R#w5yda+KosZTW5y#6aoy*}=1JILPaFkhX9SZGQOX*pf{9KjiSWckT z0N(Xh?I8U&%kLPpkW3d!04b-uwCg3Pyx!kw literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_39.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..d276c21233c76a9226e90ffb3065d2f9bfaa23af GIT binary patch literal 1662 zcmV-^27&pBP)|~@=aLw{BfrY{SWbZd^cvNX=m41TiX&KMVVANZ=!|?!pCYlmEm0!~V zFp)|}h$tBgy#qTVzSFs>&Nv-jv`e($F$it&q&CGFE?VmDqMZB~RPo!33%6ZWn)s8^v;dL}q;PUa*$j5u7`}Oe0=x_v@dHy&6 zUAV&$GW4p@74aUqR}9WIdYl~E=P?Fcnt`Rp%_|tUAyt537ve zePUi>2H=FL%nBMUnof801f9m$j9QU7vS51vZjp4YvSHMCNX}3J+J9t?uNhu5dUPL} z&b{fMJq2KRg<5q4YDlef?s5VuOQrFpy=CS0>e)*FShm+dt23!3+9O!cW93?rv+4{L zY2PxwXD{7g4L|oX#u$+IGJb|{6(Vwdm4Gq{1Zd42w?-35 zTBnr-aq|t4ukG2AxdNW3$M^q~0I%{b=F)R3opDsD*#>XL_*OaN`UD9P$KsyzWF|2K zWW7_{c(&$8l}fMeG=5}HniEt6qQv7%Pf*+hY64NAEMy!dS{8O1-)hV_e%_Kt_sP-c z-n)MxB#W;jM9q(vytL01XmtSUaoI?Y^5i0z%HlOXl>>iX z>7Ok=yU^wUE61V=WoX8ksht$)A%)I$c;b{6tWBFHhqWIgj2M@f@`1*Pg-9y7y{X zDYVl_6x_bwTa$Sh&3G1Bil~UG>`X0 z(|_cAUtu(FHmcMuruCHpR+^HPGw~hKkNM&sNgl1~>yF?q;{z}m0B@fA{8U)SLe7A9 zoMOC=lyNw2FPC>R=6QV*xM-IKRuxCS2zK@ZVg|^JO;1n%6Wl<^a~kT9VFz7pPbC@P zL~zHasLa)S^OV#op3-cC;>t$+$paAYxt$s$+Wcj(BXBs|Nc@&Ez=`n6hU-0NWDgz9 zajfSsPwIr#&}2*X0GyyF*MFKVP+)?>RqfJ;Gg^&txsn`Y7EKj1Pc*BS<|Gi{I|69!_Y6;Vm5U6YYG<(ef>R7XaRJEbA43}o zNza^&Zl`>TE-nDxxBzQfzdM?_`2)TF^fC2LeC7b! zIsYW%TiyDKj4O^qpS7lf713Y8*Y!Jrt3p=6!~dkN{w{q*_`1FgtVXvpd1|4D27um} zX)_Zqxb+?froBp*ulu`zyQ4pftP^MhF^sM(Lr-Dax(b`98K9EU9jM3z7HugDMCe4W zTk=Hte*R!YSK7bZfjXT)mX*jcB|8(px7@cCy z2TnyK`X?$JQ2bA#e$^4G(eXXPPX6qljr%_v|BiY5DpvUa1224YscsB!od5s;07*qo IM6N<$g381tfdBvi literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_4.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..354a96b6c5b40c54cf1fd55e0d7205040cdbf583 GIT binary patch literal 1618 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 TfiX(a00000NkvXXu0mjftQ1Sx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_41.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..529f6cdd4554f19c211e1c06ee28370e31309452 GIT binary patch literal 1654 zcmV-+28sEJP)t^d=5qgAbU_ z)f@mY0071q;Ndt9u*TwZiZKQp$MJawKU?>r*KTC(id=DRBz@jJ`V8fGI;61x04EhD zIgnzE@e!aoTblqMzq|9PjU7QyX&=J#pM;a&=m-QYI(KzGwQt4FC=;yO2@qp&34a2B z3a3*1 z0H~14K$ux%YV;krbH{fEH(xVp4KH3LdWblL7kJt;E^{Xa=-%Oj!ONzafup341bL)VVi<`LPc)j&Hh{-8P#xI8+JGX+f_Z{7KHu6(``{S+O6z?tVy z0Pul#1VTn$6}onO#O@V`ONE|fhxP9<23(edwZbiGm^2|Nup+_G6$mKzF6Vpm&a#J> zM(8}LE|mc!V=7s}lEvxtj+&s;`Ib{Fm?I0Z29O46_bLlUONSN=72x$p*7=s>EvL`U z!&>tw{fnmnil|YmfxsPURnA>Oz-_5?zBae4-O+XS=%3nl3^F=HEweR(_dZ=*Z|AHA zLj~=7&X3Hc8=~WJE@OwN3P!)1H# zX>u$E_{h^zK$e=KLEurjhS_nuIp0eW5jiupmByMMSx{UAMh&uz(qNrdBI9>(eq{b3 z#|2o@s{Zk_+X<`yDqN+@=qW+dmvO!}m)Ut(&gZhIExeYguGw!1fJknW5M5x+@zV2I z=SPFio5#$Yc@T8swo?FI{0KB>db&^#l_S|=1pyz=ZOS5AS6M;G=eLSh2Jk9S4J!*R zu>q%7d``!C^j}5&QTy?;wGk=;;q0QAC!EjqOL>($kLOr!zhs~Fe0T7mxgv$nZ1iQF zUky&{Qe_=M1tVoqSqn(>z9I!shH5fKcL$7BU~$IK-nX99G2Xac@WlW;2xc6=>twmJ zwc}SL_j{0zdONNQwdu5~U_RIa&96J|egOaLTWO3nmOq!)uCijU0Jrh9jzU@vjSAld zk>-wef3?z|4T9`ZF#^d(kpUv-WbL}^?nm(59(6E(H-hZ$M>dtxRdpXgWq_IM8^CRq znnrgrSoiUp!t&9dQ$UsxehnRSn@y?G`%!qsEh5DLn&92(AK`V_C{`Lj(mQvsrX*jp ze~AHhhD7b&kuVEuO;%0^RrXrGDq(=#>HxaCt;LQOfe}c0ywd^wk)Cr|S-UKhFu+c1 zFx$APgj8roZSvToYmL*V05ov-<_~~QxgEN#UA-etx;WgKk=(!2M0c%CGKJq=b2%lN zi8%l5eSn?y-x*L^lIS0=)-mLb&VQN(&<23Hvw9~^iT?3w z<@~ofpW|u!`7!V*d&J)di0phyS5lyOJ@%&<=0qmVW^IgDQyDpnze~68Dg85gL*E7>o?cW-e?@Jg&Dk37z3;(m zqepbF=nXT#)6NHoMRv5ax2n6VmCR3J*0>6POff(ex`QcF4M$`|bZ9KFs%O^jtlqER z2l%CCPc{fD0zdoiOjAm-Go$yb6Zk96N6d7`vhurR-v#kc$5!I$0O}5cDr8#H6#?k} z$(Xn9eu|2kz>{4i>u&u^e=+1u=q@^+#ESlZ083e%Y!Y(6GXMYp07*qoM6N<$g6lCG AJOBUy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_42.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_42.png new file mode 100644 index 0000000000000000000000000000000000000000..15168cd11124217a5092687483149426b63aebba GIT binary patch literal 1495 zcmV;|1t|K7P)xkkIPCPs3`dZ>^RC9n@{P#p%><)z0{AjGhYtt9 z#48*D)9cr(Bgewb$geU!mJ{G_U!!{@Ishg-;t1BUaMor;Ff;NoeTt~Abge{RJAO?E zz(gt?A+pI>=reF;#LskYx@X)T9<)ld;4ug-@T6yy=AAOY?1<-rq2JFqy>~6O$w3KJ zJA$;1d)70@RcC*fVOGRTdUztW8ePtVKB#sCN)NB2nF1dlt42Q7mFnx^k1@j$Xy*BF z0J`uDN665t!mNn*$US0kuF&J;(0*^N!J!#gD%`w+aSCyc<#C2tj?n3zWqeETZ1%8@ z5v(WXC1wCln98i6(W2?}jGADk@in7XWR5J@8h~3Q-K#7ZH6D^PRDjkWS>tPl*Nh(3 zL)&w2`e(ZU46jh5jzA6Rt(+?}0F|ZE_>yi}`MvvWrGG5j*3jxqYKhhematx06;4o* z_ATRkvcPPxhVQzx)|w;?cg9&vI`PrZc)JA8T8mDV@vRI;dCmfteIzH?;Q#jls^D=1 zZcSD36K$m`jW2Dwwr&>OPS7SxBSp5?@X<)m6rBBY6~@>2bH=Yc3M{!ehcLT<3ZcBw zfsr+2#1PeyACb7Iss=7_ZLDcm}!_@9pg=l z7+;ffhVdyc?e_{ya{z$zTAnB(#|PvK4fXQloK%?d}@tChHG^mlLt9tl)^YebKd*;L^V5?5{?r-T!ei;NHIq znAWaU!4bwYdepO^RsJ2-XTMZbYEDoAO6wy7NE0(lkk!&Z#@W%&8Q*dOjz$!mE*~{O z_DU671)dF`Rrzz1K4}a*3t|SyyfBI2F3xgw5mt?8$5xU7o`@N`MeC~({#J3@k;(ub z!mGgA4F9Tef;%O1>nMYLP z7%~2e=OYH!a$S)LuEfrz^+@E8w1QWH(}PRPPsfgp%1r)l z5j&Uu`;|NMxv}rlBIcX2z|N)rewEIAeEnfZ%1ZRBl=eDO_uK= zMRIm5Y&p@Lz-6dA$~hBn|1*e2K<1smN}H#>1j?r6NNZf_Y+1esH6%@cp7}tvGFO4q z$&0Eq9>pl%R}t6+(9ZS_>9|-GQn4D*iFQWTnOqe>qkMPzz}x4qQs7xwty8JS0id7t z8df)cM@5m!%}4O)oLk>9!h4kO(DwnJj0zpE+NTKL)Hi|vo)i5XxH$k9Q5Eejx873> z;?Iom4QBvJ=+ziPj#Yb)ZG#6pN{RyjbUOe5002ovPDHLkV1jaF)=B^X literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_43.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_43.png new file mode 100644 index 0000000000000000000000000000000000000000..16196a7062fb707b4adc7a2199a42d9709024ab5 GIT binary patch literal 1440 zcmV;R1z-A!P)7U9a@zky#-pYv!KVgZvz+5t*YFVhtb-(%y?K7`?KioPyUMwdCiB&ynu6BeyQr z^e>(QD6C3FID#g_S|eD-=D-WlNb3K}j#kAW~YV;#0$yx@WV`u1kUHaGWa`$Rbd>B-N9iks$P5+}( zzei?-Bith@*HwXi3EmgLdlGg}{oH9DOX1@=k9wkiR;KWtP-bQ{i&NIfLOhlNs7Lj= z8u$r8!tT*8;noTu0AIne>!L{MpWmNp8$J6+%LMqU0dh{@ElPHDgk0s1m>K;&>$Hyy zp!Lk760Vk7x8g425<5k=9>3#Z{KkSEFO8CT;*d{ZnX}L3{W>&(o`K5nSJsZ~;KFL_R>iI;QtDr;rNCnUM6A^z_1O zS5$!1%qw;RxlnIkU>D?ct<4DD=2%o9)Xw{H3g830N5h@*1{=Ki^_xLF^`An1WJ}v~ zG>%-i+Ib&ND6fP|1>zgX2dMj9e2S(Hl0M#fzyC_u z+nYTjQM*pLo`f_9=cH!dU!6eh6mSChHTHRvc4uc3G}oYssm^bR;5{;v)4zq2F}Px(Zqf3t-41= zzPISL+G~N7{@H9-N-2_HPb;Psa*{Eeu@qi%y$ha6KGm%iuaRJhf^viHQ@el+Wi2Bl z^hS>5`E2s7a~$2(v~2luf-<27eLvg*Bm;WK_2?cI`4RFn zw}O^!-2KZ_eMeifJIM$5Jy5xWd=A-lfOq{$Dc@ZH6_#PZ-Q-tfSNp4x&)H?cPX@>^ z0ATJBt`~Qc@3E7jhcC`n^ACi0;9ZPaj)IA`lCRl1oBUo~1)Tv@3~>N}d8GEhLVjzD z98Lal9G`zJn<7YefK{Wv0+TFd06g|iTd6aSs2iFugCf`=`YWvIe^%7W?cb%IBr&a|6#NMy(2Cee1xFhff?H-Ctu>KD+Azzq8*)q zzICIYjnq5a=_LG|1bQ>Uu8__4cdg+`(|WyHBmW7l z;G1z`Qn?dAiTo#R2NZ8|YF8If@nrJfhLcPG^~&w}QWx-tJ#k8B``Vt5uYcGZ%`O1o zJHkEh0CwvVR(vlo8Yh69{xPJ?Ae}wqetPvSg4+8g8~{)(kq@9($M~M)6kH*BCpddK zJH7DED=R=EdfAgeD%5)}unSr*fg%|JXA|rAj5Uku{pkYe0B>u!GoCO9FTQ>=h^GFx zknfEi4W8CGbKRnPFG?t_gj)sT6UYbX|4rPiH10*O-U|!iBAvt2P6DMlc;-esqgGbQ z3Z!1WC%s|m^PfOE#pQ)5%r(^5&>7)a!L!u}8D>7_1 z{n4m5I6@Se4s=HrYW~fjH;~`k?cBXaJ^KFxQ_;Jyc>n*600000NkvXXu0mjfVSBd? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_45.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..5a2221cb9282fccb2758f6d5f0d0d854d864d06f GIT binary patch literal 1464 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 AOaK4? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_47.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_47.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d5407ad0f5f04dc351b8d5fb995f2c99094d34 GIT binary patch literal 1369 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_49.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_49.png new file mode 100644 index 0000000000000000000000000000000000000000..0e6845ac244980c1d2fcea4abc84ef0b33b215d9 GIT binary patch literal 1851 zcmV-B2gLY^P)Wqssi>-iO&uk9PLGs^@Sv~@?<@>&HLz*6iA zaTp;g3s-YKv;U|vFv79)u=|0136&9|_exhTtin4)#qUy@Q6T zfXKj1f54dItlGaaP%7S48hFVkiBUXOjTlNA8HM)d>& z3xKS=QLM`Np1R$;){2~uawE<$kah1BR1C3n99agAaQ+bip6wyFZHvDHFhR2e!KzVu z{_37!rSq@Q4_G>fJ_OAO1Sv2$dM=uGitu2w;BMmj^URJ01@t7D*#67XA}rk=QCMW zZ&GoFHy-D{In3y1diAFNB4`Gz4EJihCyOnTZIpgC;{4U9FyJg4$^as@e+Bg5Q8bS6 zGR+HuYSUMtbnjGxvhd!T-=p`?9)O{8eCcTtEgfE2V6rl$skf<%0xr{Y^y+m#1sK*(+|+TNqvo8#Ha?5Wm$Pw&&)1g6|FFf-3;WGn+M-5H&1ezw10wq)#> zUBf-bnuQkwcn#33ZvtnWkF#}kr1s;u%6?_aqc^ulr`Eguz3&%<0xeWGaVA*l5y_oO zScAr7SFaV@(XQ+Xh5>DOM8;)}ZAUC-#F@?);b-fn51&O&*7LONmCoPy{lmf0$%U2npl49PsVHhi}8QLbfQMYd-XWq`~g*cA<4?bKc{o4v8tx>h(pJ1?St zE0OVPGC<{ODbC`kKtZa+l4a3s=zFH4x3{wN_mqiY2Cy2TZ$q|MV3f-;9e-Dx&z@I;1@XZEdLb3#MvS-v zbf1t31lA+tWz{Zl*-JayCFoQL}jn^pk0H}1t z;dc!d3H<9%=55s0K1~zV*z?OzqH2tZ^!UE8+(4FIbQSedVd z-JJm<7DFdk*%l!i2woCKO{3NoCG%Sk;B}c%=HHnC+NSH0W{nfcQG7#`7*?~$7(w5A zKGL$h9zxspv@$u5KO&qh$ay}dP#0Og7w+8`()t!6K zEky65+OEe`_B!i)5qbZE5H!T9`QPe%1Y1A5pRs<6X&3{raavB+`6BY(1Y(VkXzE)U zL4>tw>_m{s#>#t3UUc$c?;_{FP66$QS7-j?^Hij-z3`5oUiTi^iDRus@qrV*oz|;} zGT(DxyN(1&blS+u(9nKPeF^-gqhAla%#X^XNZk3a1oM^lZ`WT0vEX1hYtg-E_D0`B z*cD-&=d1S2fOo{7^8W%D#o&4R%0PSZdAqkxMUiYh-+PakwRcu8G0Xt0*!m_Q8@F^-4GnaX14o pfU4j-JHMgo#B2{xWq>n!fIogQ{T#2oX!rmC002ovPDHLkV1iOkj#vNy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_5.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..45decf285fb34e41768e7304fa41765308dbe80e GIT binary patch literal 1625 zcmV-f2B!ImP)?A%j88?oM5O-)g zJ&33g5w+InI1Xy9(JVTh<2a^i-B^pq*$mt`7he?~uVE$a8#VaqVS0tK?i6r++$rG0 zaT~yg<7h1Q&vpV;orFerS6ca$`sJYiZ6ML_p=X3#LBpcC!w6QK;i`Bt03zA)EkQ)r zXAw05&KJrP-{;-&Ck2R$24QeP?hq31R6HZ#yrLfcY04)`6oIX^?ZS~1>M6-GT5pd} zQ9cpTmvj>q*l`@<+7$vE_v=RJMS?r*`*=zLqri|HstIeyaLGyskreRB$|o8OV1?4S zP$QvOqbIE$jX+Be8RfrX7ZBCo(OPRrKFu-{$;F=1BxACS0NHDYlD$Y-xepS>pXBHn zz)WbgJaS~nVpjT$ukj)QHwrZD?~DLvr5~{f)R0jM|7^UE9@CTn0ZWfDd-Y5US;F9u>G}S?e@U+*zGVm{r=y z_sX$(#`C3T#NP$X7CT;7!5QP9BZBe@(iIWl46KeKVi7pg_F4BUCx>3!c*XEbbiK6j zV*nK`j#QOvs2siWbTb-Mc8?W>;?^%@twasbhrps+{l96u%WbdImaF;Ro=% zs$wX@g^)40jPkoIxJ?rso{7C`QkOpm`kglKtUbyv?stut$Naxc!A}h6U zd1jT*dCNfkT|lOwlZq>emiM0TWA*A*%PJug^0mD+dXf{3Ya<;C!h^IAia!Qm6?D=@ zR#=nZRmGZOkwOje?26Q*!82N|jYeTb*L0`V+V3}l#!W_|Gewf+p`I6bbkH?bKvMup zW4z~KRcbxHY3tvqpA|6LVj+X1DxPNG)jrm99a39uVP#$Q^tEur2u6ld8x{?6&P2wj zr2-LA?CPRgjR0tzvAFVxq z9{|OwG+_yl@2??po=n*Gi~^-8{Twld3i+3G0V|E6IzMW8SfcOcd~~0cn3j8w0JBEG zQJprt2oVLU{PF!6d`kPz@-TlU zxhw)iB1A?BQDtW%z!;)id!Fn`${EjvdVb{jl~+%$ODBP|9-5^u?Kj?&jAe#@ZGZ3i z-SQScZ{N~ruYT<>{$Bg+Y<_pi!+SxKz*j*7A`PG;wj@f) zbvwow=NMz0=Q$!A$1%LQ*L{ogJjZbyKWnbvy|L_dH@bFZuGiTxeJgCp$Q)ygWBdt# z3p)ZKW8k+rAF(SKm<}APq7ncPw$VuGd7eL;P+1>2S&e^#^R0a|c4nC%gKOW>wY=8> z44@RdLL5ej%EGrfpV@y`8JJ~&P6>Ep0CoT~jDT!l4cL(>E3h;3t$f7nUNdW7>i=d16lW8LB$ZI zJD6an^FQr)#7ofICa}|m&XfkW-s}EXmxUFy|F1d!!pmcza-4d!QFE>E&Kl+Ls2ROG z2$=oKu5U)E3CyAr2>9ry4y?)uS^Uq=zYx9$m{u@Z7Eso%vhYdgcg~2Rqj!4%T#7pI z>T9Jec#oPZGC$kMGOTK5yLz89FEJ3SV~K4_l?@!9Y4Ezv{}-|BZvhvgI_}NS=&Ux1 zpKyLQ8+Sa)R`o7#0vRwn?KrF96`2?4OO8ZYSW)wp-f94>dJO-%02qTicP0Sr8jC0p zs?KM!RBuvohBqJAzBo+uGhMymSHf0eN4-3b?yKMx zP=>OoR%@d1ihet$Xdl<1y$aQ5aV6KQ`D|=P@6$Vk3*Ag)MtXGS1qIIc=FP5QGBSs| zqlRN?SpDeu%rro=n^Nm9op|4yZ|7L2Ms{K#;6Zw|KZ{H zMd)>~_Qx8#0+&5_7%a;p8TPYhXXI35Hguf2W^83(K~Z4^ocD+XWd&R3*s;ifE|{)L zAXxj53D`Uv1YQG~xpsg-7(KbLJ+b-Es_{`qdzrtoc4biQ6TI=xIVnbn^j7wj@r`HO zTMsb1Nwu+EO$^&C>Q#Y|2@qClR-KD?4#;TNRrLm}kILBAVt`1ocZhcb%K|n~7K;Au zy=)M$HI=~k0>>*!4BHt14aXYUJp{JCgAU`cI*;~bGk%uwR4}lTVr{`-r`O;2`x&31 zI5zIdTgeoatj?4%>tyBNRgU+-l8><)OUPvmz~CLy-6bH(#;QGbe&m4N@ciH)`8?}92nEO0#;9@AMF zcRC*Js3zAm19+5jL3OAZV^=jzs*Y#%SW0K>K9ThvDt5dvz!Ts4=;@gf*EYvI{g z!M8h~A^S9!dJkX`WxiK0)dAWyYCId^$|jGgJanD!)!p4dUwG#fz6rE~tO9g~Rm6U0 z24?4}IT`0$be}*X=U3TfXAmeRy!5`E-x&N$p?eA$nWNN|EDEQ3e;7;v}-H#-Z@4sXqFWD>_~k{{?8 zRVy+;2C)|ql>lK)>ufL3K{jY2=O4%Mw**u(RT-5n7uG=R8@xbK&Q^NxiUDDN zGEht~gNh8#p>_^CYc}VB3sl{$AYuZ0kM#|9PUL)5GNRzKws~~v!3!LZN!b~D43uFg z&u3l_6@DK8H^T&G;4zD$XQX+xv5NDfvX((rp52?;1J3iDUTHkpTzf99Zv=V4fr^k~ zSu`qowsY8+Dkm9x6*?@S#uam+9t?tvOpA!IaX%#n=mrJSICB^pr6^ia7^`!YGN9mP zY;?HlJ}ZArr~S+lf;ls?MFrn919(AzG>$Z1Jv4jI%IO3_23dQf%)>H;Yng0TUb;50 zwQmf7gDaem2Z#Z|=r7uUr>nA$46rPzb5tT+9^jkk#hw{SE^t z`KZHVfK2d3Nf6cb%zn}gpp1()X@Ad98yb;0L|mf|cU2D%>Bs#?`|}t?owFA-iec40 z)V`+wpTG(&+WV0+)Zuv!QR}=z&f1BYnoW)j1E~8|#`5-|_Vo)f1j$ux%XXCj0000< KMNUMnLSTZ-kN8Re literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_51.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_51.png new file mode 100644 index 0000000000000000000000000000000000000000..38184aa75e307e689cff898bae3a09765ef43093 GIT binary patch literal 2258 zcmV;@2rc)CP)yrx@}qw{e!5KH$c($X zwqqT@0LpNxBw>WAE&R3QGylKU26h=>r3HL206TykMnE=*4%|^GC*;n`kNQz|V|Dia z=>OL>o?Ycx6-LFl7N2zvxw3m&zDf5EvvpXwtNV$GaSgdZz;(=?1BP$PH!2=!I^M_k zj&$?*w*3MNT?Lhc&V0a_eLDl7yw*-yK+~>uk`KJSUY+?+RaEl2D0hBBOOPSjc>MrZzTU40C#7I>$>Xi z0hprGKu|p@?!S8`xHI{GVIEL6x4sBE0R#mAW#cpmu>Gti-%0#8hgl}H{SE?vuA{3! zP|2~;Zg>SOqy1Hc$O zxjO)ed!kf8=q8`(QnN`X8QFT=`y*juo>{e9{z}+MxuaiR*Y%NL1}r?lmE_+oD+RDC zUuA$X@{df4T04hQRf9Vl;J&%H8j@W~&pwr)F8o>TU$Ohu86X;7*|)m4+ZL3LyN0?7 zaL&xJYB&8V_=G4&8LHKu>VC(5yyqS;`m3b-Sv=F*nL~E$KC?q$#+8HBzPUc5vkVlt zK3liDhv{$*_jC_;3Sn2C&H%IYbgrEkV>}pd_Q{3C#MXZ>03w(nI+LvOPxdZHxSvqMzR z?iK_>^a9de10nV4yc%@mZWSb-5;LodZT7&ofX5T>0iEpH;f+=iILV`(RYpWdKG|Y|U>8JCJ$ZZ4byK6$m<& zAN49W$QYTiA7y|p2$XU)6J+o25;Z$8;AT29e;E+s&n!8svz4;H(kA2#&~XwK#%l~Z zOF-t}j;Q3L?#fQ2U>68;J*#wl-${OCg!pp7g7Rj76(`X#d`$z{HIp4Cag*-~##8}; zwHG((7&qnv{h4-DQDyC6?xnvG)JeWVB|6uoD|Hy^{j|68%=M=Ng1Wa#`>Ox5_e@a# zj3df}Dd;4hF#v)9opHpSDeyiX3|8W4O1Obyp}vr__3dSQN$_Oiw5Iof$+4t8_N^ zWHW+O*(i=qJX`h*&*OoK&Wtf7Sz@LUHn5<>_+m4Jy1L1aS65iVS={QL(pmY@DZuDk z<^wB+2Qz|6aulNCDlsw9?4>|d%>-^d4iMsgOn$~DyG+wfKKje%G3DRK_W)TRTj~9{ zKHjgurI?h)V z27;<8vv#F`&f17W`>O++Vj~5{6&om+t7-Qc-!S9M5p}3hs^q#3GX{u0s9w#k(RP){ zPD{&ZUB!{Lq!S6pSeWhWy1c&|7&lIZE@+|?uC40ulwX;ixsLf#&o2RXNps5Sc=?lISz8bA42^NEP?WpqJ^3eou+MKinjuutTfb zLMN%+%5{^^{8Q{S6N2r9#ZnECDsDE5tO7yp%i{g`eSnT(1%hm_spz(96>49W@XY_X zo=J?O0us}Y`?Gl1ImTCJhs`3&=8qX*HTg_gb;aPEzRK1`LFRJW2h-63rPDSMT#i4g zl_NE#VAf&RHAJDm;qJ?j?Uq*Ltw)XzEDT(b1}#) zxy)atyVI7YzIg4Nk6IV)M*F*hcB4N_|4yJwYiNue!b~6oBJOeRu=mk#1j5b?po(XO g0lK~1l`O9R0h`z=xZ>{7K>z>%07*qoM6N<$f}HGIiU0rr literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_52.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_52.png new file mode 100644 index 0000000000000000000000000000000000000000..1259a591a4b81d5bf1d12cf1d6dc739f94e88a16 GIT binary patch literal 1615 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_8.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..3621243f71f10b63c819eca3ae8474717db41550 GIT binary patch literal 1612 zcmV-S2DABzP)c?O7i*cfGg~qE&{Vk${ma@O3rf!@U?EU@0qaf5RNJ-cQCr6@96x2Ob~R~ zH&p~`6Gl#w3(CxKH|G=4lf!q|H{AtPgCHyWj?N#b1OX~{j34hyZD1w{_-EcCzRrIJ z8U(ZUHH*MZaiQfbOKD9`)+?f1Ri{~bGl5dw`4Oz{0JX0fjF~ntQc#;TtDHw+-k1m- z0glYU$9C5;Xb|wZ!vd063WywG!Rtpg=Uq|ob<_VaDj(V^ZZtB8}+?l|UXB65PK8NRg zEf8mz@d~DR5WU}*FK5$>SA59>8!2C&pn1+Z~%1XAjEK4IUMLorS=3b>JF^=yx%*qqzJSo z(jF_$FhNJ#wBmSm=Gn(g4N}3jB2eyl>GZ1eqqZwTHccRUAsvG?-@CQYS^Ne7y5l1z zu=1$3<11KoScM!8S@ZE?bjRspCl31mm9VT+GjxsSJ^e&?^+B6mAq(+9mr`x$2j<43!PNN2g3R&@tl2B*uLDo*!`h72|Iqnb)NTolhKiV{2VToLy0mJ~BB33_s7w22FuhmKN zJtB+W6S2$GT8PyE5-1&+!V`xgp91a>X%{UBv;dHsO1oxtTb@sldX3{ePoDwgf>C6O z9y$I~f`RKSZNzPuEy|>f=(@%R85{n7Eu6o0IbTa3T92GbGk+D)TC;To3Y7tdzz*kI za%91&MQ=6r?0`k-{5nw_&{1lzWLT`@&85|JFXsT-Kz@U^NN=H)J~isAuSQe^TF9Ok zSoff^kvU(!6pB>-Xlvs+4e36#Jo*lSpg9`Jzj6&)*I?bF%_aS&=@GF)_{X8hZuBZx z3);Cu6a?B_SD`Y$)h&2MINC8-4v&2D5&%f<=mKg8%^=x8SiOb{vTN7w9q;+)TsQ4= zj;8>Kb}mZ&C^8HXMK(gcD`4e)G$>?XTd&@|)$2s83#dY?9}sz&=&vxsN~ddUk;oK- z7OYxm#%FSF^;rNyL6%Tdv1q4fgCTPK?wY}x4`tb?AnsZZ{Cxng^+6UUf$Z!|5OBFy z7l;-t$7R#tc<N6V% z6}qn&57lF=Ba3X>=?L;6-*U9I_H+%v6_yLI7K0i{8`H%A2&%eDc=KuUx|mLrFa4zH z$L~oq0P1`%7_@vgYlJ9hH3M|l3aisv2gAvAA4L9t8YOd0lzYaQXeU)t1`02D$94JH zCqWDLD%$?F@F$_5LJOvL^;kXME}%>Y$)bP(J{1xDe>$@Dj)i~MKbMr*&bD^|0000< KMNUMnLSTYT=Jyc* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_9.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_9.png new file mode 100644 index 0000000000000000000000000000000000000000..4d3d537162cb4c59a4c379e0493749eabde8edad GIT binary patch literal 1575 zcmV+?2H5$DP)pcN0+xJWYqe}7!LpR0Oc>?HOSF`VnM@v`EnoQ@=dDY7O7f^?jPksv_j_VH2szLkNIAfTV2boONDcc4LVm3_@l zpp>|1N=wl?LSdqk$@x)O>!DeE)IJsVMQp(3xUFDINpxNoRnE=oGU|Lwj~VujR1km4 zKxv{Esa$D~mZEoC6qf~#4s_YxVb~q9uLBPjJn{*XQMzn^sp(b8KTAyIH z^IHqP4YU@QHn9>$>0dkf)%)}8H_N_fD|n>j&^}+y`4Z3*J>$Cal1yn#Ca0ph@%ejH zD0MJ5&=t;~-AC_`Xcqxu1GE8@wJ(xThQ1Lr+xS;GSAtiIBk1~%=0A}&s>bgbWvVe! z$5w$iXAoG_D#@-uHN%PlaOX+%P+b~w*|SR2OcgW}`^wI)Ld5yVsg9PVD<{R}SHLF* z6g~M-XCnKpv`MvnFUbMWyD}@Aqq<#P(U4jVmjr;+`{tiVz-35|JdOi$@Td+moFBW4B(WP>dvWC`PV~3XD2On#b>Ii4j)B$p9Kv)8|)2b2LAY=A(LMov)pCl=3sz zRuxVLKz~|@BvKKK-qABb_bS;YQc~a9INGyL?~oMF*4Zn9GiEe@L9g7aATofYD4L~P z6#nUf;8iaO18N5=(t3`J_h2$WUnp7=b2*tN(<(p`KkIp=9+h}WCqJqTbued;JNo5U z!8?sJlDx`bI*#M_e+Q4`09wRHa*9u41kV<&`R`1uF{lnz=>FLG$@dnKPCftK$|sHQ808u7D4&&RGLbdEYvMG;AJjVHiK(I<(Z=5wAg~gO8 z1D`p7rWkT9Sjt(n&&NsgJ|Pe&v}-{^_Nue0j)?P*flk5b%7c;!wRtP9CbLmG~j+Awkm7jZX8046{*3(tpx^ zBz@^;QjkP-;m)V)XMU=E0E-E%%|4Mtc&nxAC^&-os`dU|?#OY`Wu z(&v`VQ9o&VQNRqKZJHa5Z$LUh-p^`A&_GYT=6pm?Pw)PmucPThZOvI>WWN$4p3ho~ z{jFfnsH3NT7PQkwpJ~sp&><=#D~H4VYvFH$!G!}NAA@WjH7yTo0S5RY4t^D6kjbkw Z`~cbttH@|hC}98q002ovPDHLkV1k|1=!pOT literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/meta.txt b/assets/dolphin/external/L2_Secret_door_128x64/meta.txt new file mode 100644 index 0000000000..4091b12769 --- /dev/null +++ b/assets/dolphin/external/L2_Secret_door_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 29 +Active frames: 24 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 1d3f351068..d7348b25cd 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -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 From e027d5c3e818dcf921a6c9423fd930044899bc17 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 1 Dec 2023 04:47:17 +0300 Subject: [PATCH 086/111] Add new API function for varitemlist by Willy-JL https://github.com/Flipper-XFW/Xtreme-Firmware/commit/466e1f989f5c320675facbfde099d0cc5e53071a --- .../services/gui/modules/variable_item_list.c | 17 +++++++++++++++++ .../services/gui/modules/variable_item_list.h | 9 +++++++++ targets/f7/api_symbols.csv | 1 + 3 files changed, 27 insertions(+) diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index 724f70bcdd..9aefcfd0e2 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -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, diff --git a/applications/services/gui/modules/variable_item_list.h b/applications/services/gui/modules/variable_item_list.h index db8b1788fb..59a3b0830a 100644 --- a/applications/services/gui/modules/variable_item_list.h +++ b/applications/services/gui/modules/variable_item_list.h @@ -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 diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 34353d1cf7..0129354a05 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3166,6 +3166,7 @@ Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* Function,+,variable_item_list_add,VariableItem*,"VariableItemList*, const char*, uint8_t, VariableItemChangeCallback, void*" Function,+,variable_item_list_alloc,VariableItemList*, Function,+,variable_item_list_free,void,VariableItemList* +Function,+,variable_item_list_get,VariableItem*,"VariableItemList*, uint8_t" Function,+,variable_item_list_get_selected_item_index,uint8_t,VariableItemList* Function,+,variable_item_list_get_view,View*,VariableItemList* Function,+,variable_item_list_reset,void,VariableItemList* From d675563271d4398b3e6bce3d93e37d388c03c907 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 1 Dec 2023 04:47:54 +0300 Subject: [PATCH 087/111] sync anims --- .ci_files/anims_ofw.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.ci_files/anims_ofw.txt b/.ci_files/anims_ofw.txt index 5bc576779d..a061c48cee 100644 --- a/.ci_files/anims_ofw.txt +++ b/.ci_files/anims_ofw.txt @@ -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 From 961dd297dd1987de72deacba04b30564da5e6730 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 1 Dec 2023 04:53:11 +0300 Subject: [PATCH 088/111] upd changelog --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc6d5813b8..79c17d2cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,21 +5,27 @@ - Mifare Mini clones reading is broken (OFW) - Mifare Classic dict attack fast skip causes glitches/incorrect reading (OFW) - EMV simple data parser was removed with protocol with refactoring (OFW) +- NFC V(Slix), Mifare Classic Emulation issues (unconfirmed) (OFW) +- Option to unlock Slix-L (NFC V) with preset or custom password 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 -**API was updated to v46.x** +- 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
+**API was updated to v47.x** ## New changes * 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 * 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 +* API: Add new get function for varitemlist (by @Willy-JL) * Misc code cleanup * 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: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* 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 From 890c9e87ceac86dc3d70dd3f09657483b1c4209b Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 1 Dec 2023 13:16:48 +0400 Subject: [PATCH 089/111] [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 --- .pvsoptions | 2 +- SConstruct | 11 +- .../debug/ccid_test/iso7816_t0_apdu.h | 4 +- .../debug/display_test/application.fam | 2 +- applications/debug/unit_tests/rpc/rpc_test.c | 36 +- .../example_plugins_advanced/application.fam | 2 + .../example_advanced_plugins.c | 5 +- applications/main/nfc/application.fam | 2 +- applications/main/u2f/application.fam | 2 +- applications/main/u2f/hmac_sha256.c | 98 - applications/main/u2f/hmac_sha256.h | 38 - applications/main/u2f/u2f.c | 243 +- applications/main/u2f/u2f_data.c | 2 +- applications/services/rpc/rpc_gui.c | 5 +- applications/services/rpc/rpc_i.h | 2 +- applications/services/rpc/rpc_storage.c | 16 +- .../storage_move_to_sd/storage_move_to_sd.c | 4 +- furi/core/common_defines.h | 8 + lib/ReadMe.md | 5 +- lib/SConscript | 73 +- lib/appframe.scons | 3 + lib/digital_signal/SConscript | 3 + lib/drivers/SConscript | 3 + lib/flipper_application/elf/elf_file.c | 7 +- lib/flipper_format/SConscript | 3 + lib/fnv1a-hash/fnv1a-hash.c | 10 - lib/fnv1a-hash/fnv1a-hash.h | 39 - lib/heatshrink.scons | 23 + lib/infrared/SConscript | 3 + lib/mbedtls | 2 +- lib/mbedtls.scons | 32 +- lib/mbedtls_cfg.h | 92 + lib/micro-ecc/LICENSE.txt | 21 - lib/micro-ecc/README.md | 41 - lib/micro-ecc/asm_arm.inc | 821 ------ lib/micro-ecc/asm_arm_mult_square.inc | 2311 ----------------- lib/micro-ecc/asm_arm_mult_square_umaal.inc | 1202 --------- lib/micro-ecc/curve-specific.inc | 1249 --------- lib/micro-ecc/platform-specific.inc | 94 - lib/micro-ecc/types.h | 108 - lib/micro-ecc/uECC.c | 1669 ------------ lib/micro-ecc/uECC.h | 367 --- lib/micro-ecc/uECC_vli.h | 172 -- lib/microtar.scons | 2 +- lib/misc.scons | 58 - lib/mlib.scons | 27 + lib/music_worker/SConscript | 3 + lib/nanopb.scons | 31 + lib/nfc/SConscript | 3 + lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 14 +- .../protocols/mf_ultralight/mf_ultralight.h | 2 +- lib/print/SConscript | 3 + lib/pulse_reader/SConscript | 3 + lib/qrcode/qrcode.c | 975 ------- lib/qrcode/qrcode.h | 99 - lib/signal_reader/SConscript | 5 +- lib/subghz/SConscript | 3 + lib/toolbox/SConscript | 5 +- lib/toolbox/md5.c | 299 --- lib/toolbox/md5.h | 83 - lib/toolbox/md5_calc.c | 44 +- lib/toolbox/sha256.c | 221 -- lib/toolbox/sha256.h | 24 - lib/u8g2/SConscript | 20 + lib/update_util/SConscript | 16 + scripts/fbt_tools/fbt_apps.py | 1 - scripts/fbt_tools/fbt_version.py | 4 +- targets/f18/api_symbols.csv | 211 +- targets/f18/target.json | 12 +- targets/f7/api_symbols.csv | 213 +- targets/f7/furi_hal/furi_hal_usb_ccid.c | 6 +- targets/f7/furi_hal/furi_hal_usb_cdc.c | 4 +- targets/f7/furi_hal/furi_hal_usb_hid.c | 12 +- targets/f7/furi_hal/furi_hal_usb_u2f.c | 2 +- targets/f7/target.json | 8 +- 75 files changed, 888 insertions(+), 10360 deletions(-) delete mode 100644 applications/main/u2f/hmac_sha256.c delete mode 100644 applications/main/u2f/hmac_sha256.h delete mode 100644 lib/fnv1a-hash/fnv1a-hash.c delete mode 100644 lib/fnv1a-hash/fnv1a-hash.h create mode 100644 lib/heatshrink.scons create mode 100644 lib/mbedtls_cfg.h delete mode 100644 lib/micro-ecc/LICENSE.txt delete mode 100644 lib/micro-ecc/README.md delete mode 100644 lib/micro-ecc/asm_arm.inc delete mode 100644 lib/micro-ecc/asm_arm_mult_square.inc delete mode 100644 lib/micro-ecc/asm_arm_mult_square_umaal.inc delete mode 100644 lib/micro-ecc/curve-specific.inc delete mode 100644 lib/micro-ecc/platform-specific.inc delete mode 100644 lib/micro-ecc/types.h delete mode 100644 lib/micro-ecc/uECC.c delete mode 100644 lib/micro-ecc/uECC.h delete mode 100644 lib/micro-ecc/uECC_vli.h delete mode 100644 lib/misc.scons create mode 100644 lib/mlib.scons create mode 100644 lib/nanopb.scons delete mode 100644 lib/qrcode/qrcode.c delete mode 100644 lib/qrcode/qrcode.h delete mode 100644 lib/toolbox/md5.c delete mode 100644 lib/toolbox/md5.h delete mode 100644 lib/toolbox/sha256.c delete mode 100644 lib/toolbox/sha256.h create mode 100644 lib/u8g2/SConscript create mode 100644 lib/update_util/SConscript diff --git a/.pvsoptions b/.pvsoptions index 0312180924..3337d7eb5c 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/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/* diff --git a/SConstruct b/SConstruct index a2c5cd9e7a..b42218a579 100644 --- a/SConstruct +++ b/SConstruct @@ -288,13 +288,17 @@ distenv.PhonyTarget( 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", @@ -304,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", diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816_t0_apdu.h index b66d66054d..5ca13eb604 100644 --- a/applications/debug/ccid_test/iso7816_t0_apdu.h +++ b/applications/debug/ccid_test/iso7816_t0_apdu.h @@ -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( diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam index 6a2d9c20c1..7b2357b01d 100644 --- a/applications/debug/display_test/application.fam +++ b/applications/debug/display_test/application.fam @@ -4,7 +4,7 @@ App( apptype=FlipperAppType.DEBUG, entry_point="display_test_app", requires=["gui"], - fap_libs=["misc"], + fap_libs=["u8g2"], stack_size=1 * 1024, order=120, fap_category="Debug", diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 5659ba877d..3faf615721 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -1,27 +1,31 @@ -#include "flipper.pb.h" #include #include -#include "pb_decode.h" -#include -#include "rpc/rpc_i.h" -#include "storage.pb.h" -#include "storage/filesystem_api_defines.h" -#include "storage/storage.h" #include -#include "../minunit.h" #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "../minunit.h" + +#include +#include +#include +#include +#include +#include + LIST_DEF(MsgList, PB_Main, M_POD_OPLIST) #define M_OPL_MsgList_t() LIST_OPLIST(MsgList) diff --git a/applications/examples/example_plugins_advanced/application.fam b/applications/examples/example_plugins_advanced/application.fam index 0c7e3e3b94..10fdc042ff 100644 --- a/applications/examples/example_plugins_advanced/application.fam +++ b/applications/examples/example_plugins_advanced/application.fam @@ -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, ) diff --git a/applications/examples/example_plugins_advanced/example_advanced_plugins.c b/applications/examples/example_plugins_advanced/example_advanced_plugins.c index 2b137e1d48..77ab820510 100644 --- a/applications/examples/example_plugins_advanced/example_advanced_plugins.c +++ b/applications/examples/example_plugins_advanced/example_advanced_plugins.c @@ -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; } diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 33a2011a70..9a98b57c8c 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -13,7 +13,7 @@ App( "!plugins", "!nfc_cli.c", ], - fap_libs=["assets"], + fap_libs=["assets", "mbedtls"], fap_icon="icon.png", fap_category="NFC", ) diff --git a/applications/main/u2f/application.fam b/applications/main/u2f/application.fam index bf41eb0f7a..5e0cde736c 100644 --- a/applications/main/u2f/application.fam +++ b/applications/main/u2f/application.fam @@ -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", ) diff --git a/applications/main/u2f/hmac_sha256.c b/applications/main/u2f/hmac_sha256.c deleted file mode 100644 index 611aa2a6f4..0000000000 --- a/applications/main/u2f/hmac_sha256.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * hmac.c - HMAC - * - * Copyright (C) 2017 Sergei Glushchenko - * Author: Sergei Glushchenko - * - * 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 . - * - * 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 - -#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); -} diff --git a/applications/main/u2f/hmac_sha256.h b/applications/main/u2f/hmac_sha256.h deleted file mode 100644 index add123142d..0000000000 --- a/applications/main/u2f/hmac_sha256.h +++ /dev/null @@ -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 diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c index ce70212a9f..3bdafe9185 100644 --- a/applications/main/u2f/u2f.c +++ b/applications/main/u2f/u2f.c @@ -1,18 +1,22 @@ -#include #include "u2f.h" #include "u2f_hid.h" #include "u2f_data.h" + +#include #include #include #include // for lfs_tobe32 -#include "toolbox/sha256.h" -#include "hmac_sha256.h" -#include "micro-ecc/uECC.h" +#include +#include +#include +#include #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; + + mbedtls_sha256_context sha_ctx; + + mbedtls_sha256_init(&sha_ctx); + mbedtls_sha256_starts(&sha_ctx, 0); - uECC_sign(U2F->cert_key, hash, 32, signature, U2F->p_curve); + 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); - - // 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); - - // 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); - - if(memcmp(req->key_handle.hash, mac_control, 32) != 0) { + { + mbedtls_sha256_context sha_ctx; + + mbedtls_sha256_init(&sha_ctx); + mbedtls_sha256_starts(&sha_ctx, 0); + + 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)); + + 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; diff --git a/applications/main/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c index 34360f3d63..cb9c4c2947 100644 --- a/applications/main/u2f/u2f_data.c +++ b/applications/main/u2f/u2f_data.c @@ -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; diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 9eff4bca6c..ca8fc61a45 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -1,9 +1,10 @@ -#include "flipper.pb.h" #include "rpc_i.h" -#include "gui.pb.h" #include #include +#include +#include + #define TAG "RpcGui" typedef enum { diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 16e5e594d1..ffca50231c 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -1,6 +1,6 @@ #pragma once #include "rpc.h" -#include "storage/filesystem_api_defines.h" +#include #include #include #include diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 913d89f1af..a934d1c31a 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -1,18 +1,18 @@ -#include "flipper.pb.h" #include #include #include -#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 +#include +#include +#include +#include #include #include #include +#include +#include +#include + #define TAG "RpcStorage" #define MAX_NAME_LENGTH 255 diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 25893f0110..5d1e694bca 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -1,8 +1,8 @@ #include "storage_move_to_sd.h" + #include #include -#include "loader/loader.h" -#include +#include #include #include diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 2b30c3b06d..b0062e6591 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -17,6 +17,10 @@ extern "C" { #define FURI_WEAK __attribute__((weak)) #endif +#ifndef FURI_PACKED +#define FURI_PACKED __attribute__((packed)) +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif @@ -47,6 +51,10 @@ void __furi_critical_exit(__FuriCriticalInfo info); #define FURI_CRITICAL_EXIT() __furi_critical_exit(__furi_critical_info); #endif +#ifndef FURI_CHECK_RETURN +#define FURI_CHECK_RETURN __attribute__((__warn_unused_result__)) +#endif + #ifdef __cplusplus } #endif diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 326d933aa5..3adb770187 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -11,7 +11,6 @@ - `fatfs` - FatFS file system driver - `flipper_application` - Flipper application library, used for FAPs - `flipper_format` - Flipper File Format library -- `fnv1a-hash` - FNV-1a hash library - `heatshrink` - Heatshrink compression library - `ibutton` - ibutton library, used by iButton application - `infrared` - Infrared library, used by Infrared application @@ -19,7 +18,6 @@ - `libusb_stm32` - LibUSB for STM32 series MCU - `littlefs` - LittleFS file system driver, used by internal storage - `mbedtls` - MbedTLS cryptography library -- `micro-ecc` - MicroECC cryptography library - `microtar` - MicroTAR library - `mlib` - M-Lib C containers library - `nanopb` - NanoPB library, protobuf implementation for MCU @@ -28,11 +26,10 @@ - `print` - Tiny printf implementation - `digital_signal` - Digital Signal library used by NFC for software implemented protocols - `pulse_reader` - Pulse Reader library used by NFC for software implemented protocols -- `qrcode` - QR-Code library - `stm32wb_cmsis` - STM32WB series CMSIS headers, extends CMSIS Core - `stm32wb_copro` - STM32WB Copro library: contains WPAN and radio co-processor firmware - `stm32wb_hal` - STM32WB HAL library, extends STM32WB CMSIS and provides HAL - `subghz` - Subghz library, used by SubGhz application -- `toolbox` - Toolbox library, contains various things that is used by flipper firmware +- `toolbox` - Toolbox library, contains various things that is used by Flipper firmware - `u8g2` - u8g2 graphics library, used by GUI subsystem - `update_util` - update utilities library, used by updater \ No newline at end of file diff --git a/lib/SConscript b/lib/SConscript index f2cc9d18a0..4835724e09 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -1,87 +1,24 @@ Import("env") -env.Append( - LINT_SOURCES=[ - Dir("app-scened-template"), - Dir("digital_signal"), - Dir("pulse_reader"), - Dir("signal_reader"), - Dir("drivers"), - Dir("flipper_format"), - Dir("infrared"), - Dir("nfc"), - Dir("subghz"), - Dir("toolbox"), - Dir("u8g2"), - Dir("update_util"), - Dir("print"), - Dir("music_worker"), - ], -) - env.Append( CPPPATH=[ "#/", "#/lib", # TODO FL-3553: remove! - "#/lib/mlib", # Ugly hack Dir("../assets/compiled"), ], - SDK_HEADERS=[ - *( - File(f"#/lib/mlib/m-{name}.h") - for name in ( - "algo", - "array", - "bptree", - "core", - "deque", - "dict", - "list", - "rbtree", - "tuple", - "variant", - ) - ), - ], - CPPDEFINES=[ - '"M_MEMORY_FULL(x)=abort()"', - ], ) -# drivers -# fatfs -# flipper_format -# infrared -# littlefs -# subghz -# toolbox -# one_wire -# micro-ecc -# misc -# digital_signal -# fnv1a_hash -# microtar -# nfc -# qrcode -# u8g2 -# update_util -# heatshrink -# nanopb -# apps -# app-scened-template -# callback-connector -# app-template - - libs = env.BuildModules( [ + "mlib", "stm32wb", "freertos", "print", "microtar", + "mbedtls", "toolbox", "libusb_stm32", "drivers", @@ -91,17 +28,19 @@ libs = env.BuildModules( "ibutton", "infrared", "littlefs", - "mbedtls", "subghz", "nfc", "digital_signal", "pulse_reader", "signal_reader", "appframe", - "misc", + "u8g2", "lfrfid", "flipper_application", "music_worker", + "nanopb", + "update_util", + "heatshrink", ], ) diff --git a/lib/appframe.scons b/lib/appframe.scons index 935986d644..fb268579d6 100644 --- a/lib/appframe.scons +++ b/lib/appframe.scons @@ -5,6 +5,9 @@ env.Append( "#/lib/app-scened-template", "#/lib/callback-connector", ], + LINT_SOURCES=[ + Dir("app-scened-template"), + ], ) diff --git a/lib/digital_signal/SConscript b/lib/digital_signal/SConscript index b94c26f780..41d3348904 100644 --- a/lib/digital_signal/SConscript +++ b/lib/digital_signal/SConscript @@ -8,6 +8,9 @@ env.Append( File("digital_signal.h"), File("digital_sequence.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="digital_signal") diff --git a/lib/drivers/SConscript b/lib/drivers/SConscript index cf93d4bce9..a790d54a7a 100644 --- a/lib/drivers/SConscript +++ b/lib/drivers/SConscript @@ -9,6 +9,9 @@ env.Append( File("st25r3916_reg.h"), File("st25r3916.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 8a78cca413..6543168662 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -1,7 +1,8 @@ -#include "storage/storage.h" -#include #include "elf_file.h" #include "elf_file_i.h" + +#include +#include #include "elf_api_interface.h" #include "../api_hashtable/api_hashtable.h" @@ -34,7 +35,7 @@ const uint8_t trampoline_code_little_endian[TRAMPOLINE_CODE_SIZE] = typedef struct { uint8_t code[TRAMPOLINE_CODE_SIZE]; uint32_t addr; -} __attribute__((packed)) JMPTrampoline; +} FURI_PACKED JMPTrampoline; /**************************************************************************************************/ /********************************************* Caches *********************************************/ diff --git a/lib/flipper_format/SConscript b/lib/flipper_format/SConscript index 9c9e8b6f33..f16cd4f391 100644 --- a/lib/flipper_format/SConscript +++ b/lib/flipper_format/SConscript @@ -9,6 +9,9 @@ env.Append( File("flipper_format_i.h"), File("flipper_format_stream.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) diff --git a/lib/fnv1a-hash/fnv1a-hash.c b/lib/fnv1a-hash/fnv1a-hash.c deleted file mode 100644 index 69c675f310..0000000000 --- a/lib/fnv1a-hash/fnv1a-hash.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "fnv1a-hash.h" - -// FNV-1a hash, 32-bit -uint32_t fnv1a_buffer_hash(const uint8_t* buffer, uint32_t length, uint32_t hash) -{ - for (uint32_t i = 0; i < length; i++) { - hash = (hash ^ buffer[i]) * 16777619ULL; - } - return hash; -} diff --git a/lib/fnv1a-hash/fnv1a-hash.h b/lib/fnv1a-hash/fnv1a-hash.h deleted file mode 100644 index 3218cc27c7..0000000000 --- a/lib/fnv1a-hash/fnv1a-hash.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define FNV_1A_INIT 2166136261UL - -// FNV-1a hash, 32-bit -uint32_t fnv1a_buffer_hash(const uint8_t* buffer, uint32_t length, uint32_t hash); - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus -// constexpr FNV-1a hash for strings, 32-bit -inline constexpr uint32_t fnv1a_string_hash(const char* str) { - uint32_t hash = FNV_1A_INIT; - - while(*str) { - hash = (hash ^ *str) * 16777619ULL; - str += 1; - } - return hash; -} -#else -// FNV-1a hash for strings, 32-bit -inline uint32_t fnv1a_string_hash(const char* str) { - uint32_t hash = FNV_1A_INIT; - - while(*str) { - hash = (hash ^ *str) * 16777619ULL; - str += 1; - } - return hash; -} -#endif diff --git a/lib/heatshrink.scons b/lib/heatshrink.scons new file mode 100644 index 0000000000..241b5a34e3 --- /dev/null +++ b/lib/heatshrink.scons @@ -0,0 +1,23 @@ +from fbt.util import GLOB_FILE_EXCLUSION + +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/heatshrink", + ], +) + + +libenv = env.Clone(FW_LIB_NAME="heatshrink") +libenv.ApplyLibFlags() + +sources = Glob( + "heatshrink/heatshrink_*.c*", + exclude=GLOB_FILE_EXCLUSION, + source=True, +) + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/infrared/SConscript b/lib/infrared/SConscript index 9a1543f00f..a32248a645 100644 --- a/lib/infrared/SConscript +++ b/lib/infrared/SConscript @@ -10,6 +10,9 @@ env.Append( File("worker/infrared_worker.h"), File("worker/infrared_transmit.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) diff --git a/lib/mbedtls b/lib/mbedtls index d65aeb3734..edb8fec988 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit d65aeb37349ad1a50e0f6c9b694d4b5290d60e49 +Subproject commit edb8fec9882084344a314368ac7fd957a187519c diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons index 79a4a25203..77add76966 100644 --- a/lib/mbedtls.scons +++ b/lib/mbedtls.scons @@ -2,13 +2,21 @@ Import("env") env.Append( CPPPATH=[ - "#/lib/mbedtls", + # "#/lib/mbedtls", "#/lib/mbedtls/include", ], SDK_HEADERS=[ File("mbedtls/include/mbedtls/des.h"), File("mbedtls/include/mbedtls/sha1.h"), + File("mbedtls/include/mbedtls/sha256.h"), + File("mbedtls/include/mbedtls/md5.h"), + File("mbedtls/include/mbedtls/md.h"), + File("mbedtls/include/mbedtls/ecdsa.h"), + File("mbedtls/include/mbedtls/ecdh.h"), + File("mbedtls/include/mbedtls/ecp.h"), + # File("mbedtls/include/mbedtls/sha1.h"), ], + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"mbedtls_cfg.h\\"')], ) @@ -20,14 +28,30 @@ libenv.AppendUnique( # Required for lib to be linkable with .faps "-mword-relocations", "-mlong-calls", + # Crappy code :) + "-Wno-redundant-decls", ], ) +# If we were to build full mbedtls, we would need to use this: +# sources = libenv.GlobRecursive("*.c*", "mbedtls/library") +# Otherwise, we can just use the files we need: sources = [ - "mbedtls/library/des.c", - "mbedtls/library/sha1.c", - "mbedtls/library/platform_util.c", + File("mbedtls/library/bignum.c"), + File("mbedtls/library/bignum_core.c"), + File("mbedtls/library/ecdsa.c"), + File("mbedtls/library/ecp.c"), + File("mbedtls/library/ecp_curves.c"), + File("mbedtls/library/md.c"), + File("mbedtls/library/md5.c"), + File("mbedtls/library/platform_util.c"), + File("mbedtls/library/ripemd160.c"), + File("mbedtls/library/sha1.c"), + File("mbedtls/library/sha256.c"), + File("mbedtls/library/des.c"), ] +Depends(sources, File("mbedtls_cfg.h")) + lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/mbedtls_cfg.h b/lib/mbedtls_cfg.h new file mode 100644 index 0000000000..5af98e9657 --- /dev/null +++ b/lib/mbedtls_cfg.h @@ -0,0 +1,92 @@ +#pragma once + +/** +* A subset of the mbedTLS configuration options that are relevant to the +* Flipper Zero firmware and apps. They are built to "mbedtls" library you can +* link your apps with. +* +* If you need more features, either bring the full mbedtls library into your +* app using "fap_private_libs" or open an issue on GitHub to add them to the +* default configuration. +**/ + +#define MBEDTLS_HAVE_ASM + +#define MBEDTLS_NO_UDBL_DIVISION +#define MBEDTLS_NO_64BIT_MULTIPLICATION + +#define MBEDTLS_DEPRECATED_WARNING + +#define MBEDTLS_AES_FEWER_TABLES +// #define MBEDTLS_CHECK_RETURN_WARNING + +#define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_CIPHER_MODE_CFB +#define MBEDTLS_CIPHER_MODE_CTR +#define MBEDTLS_CIPHER_MODE_OFB +#define MBEDTLS_CIPHER_MODE_XTS + +#define MBEDTLS_CIPHER_PADDING_PKCS7 +#define MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS +#define MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN +#define MBEDTLS_CIPHER_PADDING_ZEROS + +/* Short Weierstrass curves (supporting ECP, ECDH, ECDSA) */ +// #define MBEDTLS_ECP_DP_SECP192R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP384R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP521R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP192K1_ENABLED +// #define MBEDTLS_ECP_DP_SECP224K1_ENABLED +// #define MBEDTLS_ECP_DP_SECP256K1_ENABLED +// #define MBEDTLS_ECP_DP_BP256R1_ENABLED +// #define MBEDTLS_ECP_DP_BP384R1_ENABLED +// #define MBEDTLS_ECP_DP_BP512R1_ENABLED +/* Montgomery curves (supporting ECP) */ +// #define MBEDTLS_ECP_DP_CURVE25519_ENABLED +// #define MBEDTLS_ECP_DP_CURVE448_ENABLED + +#define MBEDTLS_ECP_NIST_OPTIM + +#define MBEDTLS_GENPRIME +// #define MBEDTLS_PKCS1_V15 +// #define MBEDTLS_PKCS1_V21 + +#define MBEDTLS_MD_C + +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C +#define MBEDTLS_BASE64_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_OID_C + +// #define MBEDTLS_CHACHA20_C +// #define MBEDTLS_CHACHAPOLY_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_DES_C +#define MBEDTLS_DHM_C + +#define MBEDTLS_ECDH_C + +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ECP_C + +#define MBEDTLS_GCM_C + +#define MBEDTLS_AES_C +#define MBEDTLS_MD5_C + +// #define MBEDTLS_PEM_PARSE_C +// #define MBEDTLS_PEM_WRITE_C + +// #define MBEDTLS_PLATFORM_MEMORY +// #define MBEDTLS_PLATFORM_C + +// #define MBEDTLS_RIPEMD160_C +// #define MBEDTLS_RSA_C +#define MBEDTLS_SHA224_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA1_C + +#define MBEDTLS_ERROR_C \ No newline at end of file diff --git a/lib/micro-ecc/LICENSE.txt b/lib/micro-ecc/LICENSE.txt deleted file mode 100644 index ab099ae5a5..0000000000 --- a/lib/micro-ecc/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2014, Kenneth MacKay -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/micro-ecc/README.md b/lib/micro-ecc/README.md deleted file mode 100644 index 111321bf76..0000000000 --- a/lib/micro-ecc/README.md +++ /dev/null @@ -1,41 +0,0 @@ -micro-ecc -========== - -A small and fast ECDH and ECDSA implementation for 8-bit, 32-bit, and 64-bit processors. - -The static version of micro-ecc (ie, where the curve was selected at compile-time) can be found in the "static" branch. - -Features --------- - - * Resistant to known side-channel attacks. - * Written in C, with optional GCC inline assembly for AVR, ARM and Thumb platforms. - * Supports 8, 32, and 64-bit architectures. - * Small code size. - * No dynamic memory allocation. - * Support for 5 standard curves: secp160r1, secp192r1, secp224r1, secp256r1, and secp256k1. - * BSD 2-clause license. - -Usage Notes ------------ -### Point Representation ### -Compressed points are represented in the standard format as defined in http://www.secg.org/sec1-v2.pdf; uncompressed points are represented in standard format, but without the `0x04` prefix. All functions except `uECC_decompress()` only accept uncompressed points; use `uECC_compress()` and `uECC_decompress()` to convert between compressed and uncompressed point representations. - -Private keys are represented in the standard format. - -### Using the Code ### - -I recommend just copying (or symlink) the uECC files into your project. Then just `#include "uECC.h"` to use the micro-ecc functions. - -For use with Arduino, you can use the Library Manager to download micro-ecc (**Sketch**=>**Include Library**=>**Manage Libraries**). You can then use uECC just like any other Arduino library (uECC should show up in the **Sketch**=>**Import Library** submenu). - -See uECC.h for documentation for each function. - -### Compilation Notes ### - - * Should compile with any C/C++ compiler that supports stdint.h (this includes Visual Studio 2013). - * If you want to change the defaults for any of the uECC compile-time options (such as `uECC_OPTIMIZATION_LEVEL`), you must change them in your Makefile or similar so that uECC.c is compiled with the desired values (ie, compile uECC.c with `-DuECC_OPTIMIZATION_LEVEL=3` or whatever). - * When compiling for a Thumb-1 platform, you must use the `-fomit-frame-pointer` GCC option (this is enabled by default when compiling with `-O1` or higher). - * When compiling for an ARM/Thumb-2 platform with `uECC_OPTIMIZATION_LEVEL` >= 3, you must use the `-fomit-frame-pointer` GCC option (this is enabled by default when compiling with `-O1` or higher). - * When compiling for AVR, you must have optimizations enabled (compile with `-O1` or higher). - * When building for Windows, you will need to link in the `advapi32.lib` system library. diff --git a/lib/micro-ecc/asm_arm.inc b/lib/micro-ecc/asm_arm.inc deleted file mode 100644 index 43844da6a5..0000000000 --- a/lib/micro-ecc/asm_arm.inc +++ /dev/null @@ -1,821 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_ASM_ARM_H_ -#define _UECC_ASM_ARM_H_ - -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #define uECC_MIN_WORDS 8 -#endif -#if uECC_SUPPORTS_secp224r1 - #undef uECC_MIN_WORDS - #define uECC_MIN_WORDS 7 -#endif -#if uECC_SUPPORTS_secp192r1 - #undef uECC_MIN_WORDS - #define uECC_MIN_WORDS 6 -#endif -#if uECC_SUPPORTS_secp160r1 - #undef uECC_MIN_WORDS - #define uECC_MIN_WORDS 5 -#endif - -#if (uECC_PLATFORM == uECC_arm_thumb) - #define REG_RW "+l" - #define REG_WRITE "=l" -#else - #define REG_RW "+r" - #define REG_WRITE "=r" -#endif - -#if (uECC_PLATFORM == uECC_arm_thumb || uECC_PLATFORM == uECC_arm_thumb2) - #define REG_RW_LO "+l" - #define REG_WRITE_LO "=l" -#else - #define REG_RW_LO "+r" - #define REG_WRITE_LO "=r" -#endif - -#if (uECC_PLATFORM == uECC_arm_thumb2) - #define RESUME_SYNTAX -#else - #define RESUME_SYNTAX ".syntax divided \n\t" -#endif - -#if (uECC_OPTIMIZATION_LEVEL >= 2) - -uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { -#if (uECC_MAX_WORDS != uECC_MIN_WORDS) - #if (uECC_PLATFORM == uECC_arm_thumb) || (uECC_PLATFORM == uECC_arm_thumb2) - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 2 + 1; - #else /* ARM */ - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 4; - #endif -#endif - uint32_t carry; - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "movs %[carry], #0 \n\t" - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "adr %[left], 1f \n\t" - ".align 4 \n\t" - "adds %[jump], %[left] \n\t" - #endif - - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "adds %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t" - - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "bx %[jump] \n\t" - #endif - "1: \n\t" - REPEAT(DEC(uECC_MAX_WORDS), - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "adcs %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t") - - "adcs %[carry], %[carry] \n\t" - RESUME_SYNTAX - : [dptr] REG_RW_LO (result), [lptr] REG_RW_LO (left), [rptr] REG_RW_LO (right), - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - [jump] REG_RW_LO (jump), - #endif - [carry] REG_WRITE_LO (carry), [left] REG_WRITE_LO (left_word), - [right] REG_WRITE_LO (right_word) - : - : "cc", "memory" - ); - return carry; -} -#define asm_add 1 - -#pragma GCC diagnostic ignored "-Wredundant-decls" -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { -#if (uECC_MAX_WORDS != uECC_MIN_WORDS) - #if (uECC_PLATFORM == uECC_arm_thumb) || (uECC_PLATFORM == uECC_arm_thumb2) - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 2 + 1; - #else /* ARM */ - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 4; - #endif -#endif - uint32_t carry; - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "movs %[carry], #0 \n\t" - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "adr %[left], 1f \n\t" - ".align 4 \n\t" - "adds %[jump], %[left] \n\t" - #endif - - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "subs %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t" - - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "bx %[jump] \n\t" - #endif - "1: \n\t" - REPEAT(DEC(uECC_MAX_WORDS), - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "sbcs %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t") - - "adcs %[carry], %[carry] \n\t" - RESUME_SYNTAX - : [dptr] REG_RW_LO (result), [lptr] REG_RW_LO (left), [rptr] REG_RW_LO (right), - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - [jump] REG_RW_LO (jump), - #endif - [carry] REG_WRITE_LO (carry), [left] REG_WRITE_LO (left_word), - [right] REG_WRITE_LO (right_word) - : - : "cc", "memory" - ); - return !carry; /* Note that on ARM, carry flag set means "no borrow" when subtracting - (for some reason...) */ -} -#define asm_sub 1 - -#endif /* (uECC_OPTIMIZATION_LEVEL >= 2) */ - -#if (uECC_OPTIMIZATION_LEVEL >= 3) - -#if (uECC_PLATFORM != uECC_arm_thumb) - -#if uECC_ARM_USE_UMAAL - #include "asm_arm_mult_square_umaal.inc" -#else - #include "asm_arm_mult_square.inc" -#endif - -#if (uECC_OPTIMIZATION_LEVEL == 3) - -uECC_VLI_API void uECC_vli_mult(uint32_t *result, - const uint32_t *left, - const uint32_t *right, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register const uint32_t *r2 __asm__("r2") = right; - register uint32_t r3 __asm__("r3") = num_words; - - __asm__ volatile ( - ".syntax unified \n\t" -#if (uECC_MIN_WORDS == 5) - FAST_MULT_ASM_5 - #if (uECC_MAX_WORDS > 5) - FAST_MULT_ASM_5_TO_6 - #endif - #if (uECC_MAX_WORDS > 6) - FAST_MULT_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_MULT_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 6) - FAST_MULT_ASM_6 - #if (uECC_MAX_WORDS > 6) - FAST_MULT_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_MULT_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 7) - FAST_MULT_ASM_7 - #if (uECC_MAX_WORDS > 7) - FAST_MULT_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 8) - FAST_MULT_ASM_8 -#endif - "1: \n\t" - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -} -#define asm_mult 1 - -#if uECC_SQUARE_FUNC -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register uint32_t r2 __asm__("r2") = num_words; - - __asm__ volatile ( - ".syntax unified \n\t" -#if (uECC_MIN_WORDS == 5) - FAST_SQUARE_ASM_5 - #if (uECC_MAX_WORDS > 5) - FAST_SQUARE_ASM_5_TO_6 - #endif - #if (uECC_MAX_WORDS > 6) - FAST_SQUARE_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_SQUARE_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 6) - FAST_SQUARE_ASM_6 - #if (uECC_MAX_WORDS > 6) - FAST_SQUARE_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_SQUARE_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 7) - FAST_SQUARE_ASM_7 - #if (uECC_MAX_WORDS > 7) - FAST_SQUARE_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 8) - FAST_SQUARE_ASM_8 -#endif - - "1: \n\t" - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -} -#define asm_square 1 -#endif /* uECC_SQUARE_FUNC */ - -#else /* (uECC_OPTIMIZATION_LEVEL > 3) */ - -uECC_VLI_API void uECC_vli_mult(uint32_t *result, - const uint32_t *left, - const uint32_t *right, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register const uint32_t *r2 __asm__("r2") = right; - register uint32_t r3 __asm__("r3") = num_words; - -#if uECC_SUPPORTS_secp160r1 - if (num_words == 5) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_5 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp192r1 - if (num_words == 6) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_6 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp224r1 - if (num_words == 7) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_7 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - if (num_words == 8) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_8 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -} -#define asm_mult 1 - -#if uECC_SQUARE_FUNC -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register uint32_t r2 __asm__("r2") = num_words; - -#if uECC_SUPPORTS_secp160r1 - if (num_words == 5) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_5 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp192r1 - if (num_words == 6) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_6 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp224r1 - if (num_words == 7) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_7 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - if (num_words == 8) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_8 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -} -#define asm_square 1 -#endif /* uECC_SQUARE_FUNC */ - -#endif /* (uECC_OPTIMIZATION_LEVEL > 3) */ - -#endif /* uECC_PLATFORM != uECC_arm_thumb */ - -#endif /* (uECC_OPTIMIZATION_LEVEL >= 3) */ - -/* ---- "Small" implementations ---- */ - -#if !asm_add -uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uint32_t carry = 0; - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "1: \n\t" - "ldmia %[lptr]!, {%[left]} \n\t" /* Load left word. */ - "ldmia %[rptr]!, {%[right]} \n\t" /* Load right word. */ - "lsrs %[carry], #1 \n\t" /* Set up carry flag (carry = 0 after this). */ - "adcs %[left], %[left], %[right] \n\t" /* Add with carry. */ - "adcs %[carry], %[carry], %[carry] \n\t" /* Store carry bit. */ - "stmia %[dptr]!, {%[left]} \n\t" /* Store result word. */ - "subs %[ctr], #1 \n\t" /* Decrement counter. */ - "bne 1b \n\t" /* Loop until counter == 0. */ - RESUME_SYNTAX - : [dptr] REG_RW (result), [lptr] REG_RW (left), [rptr] REG_RW (right), - [ctr] REG_RW (num_words), [carry] REG_RW (carry), - [left] REG_WRITE (left_word), [right] REG_WRITE (right_word) - : - : "cc", "memory" - ); - return carry; -} -#define asm_add 1 -#endif - -#if !asm_sub -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uint32_t carry = 1; /* carry = 1 initially (means don't borrow) */ - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "1: \n\t" - "ldmia %[lptr]!, {%[left]} \n\t" /* Load left word. */ - "ldmia %[rptr]!, {%[right]} \n\t" /* Load right word. */ - "lsrs %[carry], #1 \n\t" /* Set up carry flag (carry = 0 after this). */ - "sbcs %[left], %[left], %[right] \n\t" /* Subtract with borrow. */ - "adcs %[carry], %[carry], %[carry] \n\t" /* Store carry bit. */ - "stmia %[dptr]!, {%[left]} \n\t" /* Store result word. */ - "subs %[ctr], #1 \n\t" /* Decrement counter. */ - "bne 1b \n\t" /* Loop until counter == 0. */ - RESUME_SYNTAX - : [dptr] REG_RW (result), [lptr] REG_RW (left), [rptr] REG_RW (right), - [ctr] REG_RW (num_words), [carry] REG_RW (carry), - [left] REG_WRITE (left_word), [right] REG_WRITE (right_word) - : - : "cc", "memory" - ); - return !carry; -} -#define asm_sub 1 -#endif - -#if !asm_mult -uECC_VLI_API void uECC_vli_mult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { -#if (uECC_PLATFORM != uECC_arm_thumb) - uint32_t c0 = 0; - uint32_t c1 = 0; - uint32_t c2 = 0; - uint32_t k = 0; - uint32_t i; - uint32_t t0, t1; - - __asm__ volatile ( - ".syntax unified \n\t" - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[i], #0 \n\t" /* i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[i], %[k] \n\t" /* i = k */ - "subs %[i], %[last_word] \n\t" /* i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "subs %[t0], %[k], %[i] \n\t" /* t0 = k-i */ - - "ldr %[t1], [%[right], %[t0]] \n\t" /* t1 = right[k - i] */ - "ldr %[t0], [%[left], %[i]] \n\t" /* t0 = left[i] */ - - "umull %[t0], %[t1], %[t0], %[t1] \n\t" /* (t0, t1) = left[i] * right[k - i] */ - - "adds %[c0], %[c0], %[t0] \n\t" /* add low word to c0 */ - "adcs %[c1], %[c1], %[t1] \n\t" /* add high word to c1, including carry */ - "adcs %[c2], %[c2], #0 \n\t" /* add carry to c2 */ - - "adds %[i], #4 \n\t" /* i += 4 */ - "cmp %[i], %[last_word] \n\t" /* i > (num_words - 1) (times 4)? */ - "bgt 4f \n\t" /* if so, exit the loop */ - "cmp %[i], %[k] \n\t" /* i <= k? */ - "ble 3b \n\t" /* if so, continue looping */ - - "4: \n\t" /* end inner loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[k] = c0 */ - "mov %[c0], %[c1] \n\t" /* c0 = c1 */ - "mov %[c1], %[c2] \n\t" /* c1 = c2 */ - "movs %[c2], #0 \n\t" /* c2 = 0 */ - "adds %[k], #4 \n\t" /* k += 4 */ - "cmp %[k], %[last_word] \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[k], %[last_word], lsl #1 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, start with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[num_words * 2 - 1] = c0 */ - RESUME_SYNTAX - : [c0] "+r" (c0), [c1] "+r" (c1), [c2] "+r" (c2), - [k] "+r" (k), [i] "=&r" (i), [t0] "=&r" (t0), [t1] "=&r" (t1) - : [result] "r" (result), [left] "r" (left), [right] "r" (right), - [last_word] "r" ((num_words - 1) * 4) - : "cc", "memory" - ); - -#else /* Thumb-1 */ - uint32_t r4, r5, r6, r7; - - __asm__ volatile ( - ".syntax unified \n\t" - "subs %[r3], #1 \n\t" /* r3 = num_words - 1 */ - "lsls %[r3], #2 \n\t" /* r3 = (num_words - 1) * 4 */ - "mov r8, %[r3] \n\t" /* r8 = (num_words - 1) * 4 */ - "lsls %[r3], #1 \n\t" /* r3 = (num_words - 1) * 8 */ - "mov r9, %[r3] \n\t" /* r9 = (num_words - 1) * 8 */ - "movs %[r3], #0 \n\t" /* c0 = 0 */ - "movs %[r4], #0 \n\t" /* c1 = 0 */ - "movs %[r5], #0 \n\t" /* c2 = 0 */ - "movs %[r6], #0 \n\t" /* k = 0 */ - - "push {%[r0]} \n\t" /* keep result on the stack */ - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[r7], #0 \n\t" /* r7 = i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[r7], %[r6] \n\t" /* r7 = k */ - "mov %[r0], r8 \n\t" /* r0 = (num_words - 1) * 4 */ - "subs %[r7], %[r0] \n\t" /* r7 = i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "mov r10, %[r3] \n\t" - "mov r11, %[r4] \n\t" - "mov r12, %[r5] \n\t" - "mov r14, %[r6] \n\t" - "subs %[r0], %[r6], %[r7] \n\t" /* r0 = k - i */ - - "ldr %[r4], [%[r2], %[r0]] \n\t" /* r4 = right[k - i] */ - "ldr %[r0], [%[r1], %[r7]] \n\t" /* r0 = left[i] */ - - "lsrs %[r3], %[r0], #16 \n\t" /* r3 = a1 */ - "uxth %[r0], %[r0] \n\t" /* r0 = a0 */ - - "lsrs %[r5], %[r4], #16 \n\t" /* r5 = b1 */ - "uxth %[r4], %[r4] \n\t" /* r4 = b0 */ - - "movs %[r6], %[r3] \n\t" /* r6 = a1 */ - "muls %[r6], %[r5], %[r6] \n\t" /* r6 = a1 * b1 */ - "muls %[r3], %[r4], %[r3] \n\t" /* r3 = b0 * a1 */ - "muls %[r5], %[r0], %[r5] \n\t" /* r5 = a0 * b1 */ - "muls %[r0], %[r4], %[r0] \n\t" /* r0 = a0 * b0 */ - - /* Add middle terms */ - "lsls %[r4], %[r3], #16 \n\t" - "lsrs %[r3], %[r3], #16 \n\t" - "adds %[r0], %[r4] \n\t" - "adcs %[r6], %[r3] \n\t" - - "lsls %[r4], %[r5], #16 \n\t" - "lsrs %[r5], %[r5], #16 \n\t" - "adds %[r0], %[r4] \n\t" - "adcs %[r6], %[r5] \n\t" - - "mov %[r3], r10\n\t" - "mov %[r4], r11\n\t" - "mov %[r5], r12\n\t" - "adds %[r3], %[r0] \n\t" /* add low word to c0 */ - "adcs %[r4], %[r6] \n\t" /* add high word to c1, including carry */ - "movs %[r0], #0 \n\t" /* r0 = 0 (does not affect carry bit) */ - "adcs %[r5], %[r0] \n\t" /* add carry to c2 */ - - "mov %[r6], r14\n\t" /* r6 = k */ - - "adds %[r7], #4 \n\t" /* i += 4 */ - "cmp %[r7], r8 \n\t" /* i > (num_words - 1) (times 4)? */ - "bgt 4f \n\t" /* if so, exit the loop */ - "cmp %[r7], %[r6] \n\t" /* i <= k? */ - "ble 3b \n\t" /* if so, continue looping */ - - "4: \n\t" /* end inner loop */ - - "ldr %[r0], [sp, #0] \n\t" /* r0 = result */ - - "str %[r3], [%[r0], %[r6]] \n\t" /* result[k] = c0 */ - "mov %[r3], %[r4] \n\t" /* c0 = c1 */ - "mov %[r4], %[r5] \n\t" /* c1 = c2 */ - "movs %[r5], #0 \n\t" /* c2 = 0 */ - "adds %[r6], #4 \n\t" /* k += 4 */ - "cmp %[r6], r8 \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[r6], r9 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[r3], [%[r0], %[r6]] \n\t" /* result[num_words * 2 - 1] = c0 */ - "pop {%[r0]} \n\t" /* pop result off the stack */ - - ".syntax divided \n\t" - : [r3] "+l" (num_words), [r4] "=&l" (r4), - [r5] "=&l" (r5), [r6] "=&l" (r6), [r7] "=&l" (r7) - : [r0] "l" (result), [r1] "l" (left), [r2] "l" (right) - : "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -#endif -} -#define asm_mult 1 -#endif - -#if uECC_SQUARE_FUNC -#if !asm_square -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { -#if (uECC_PLATFORM != uECC_arm_thumb) - uint32_t c0 = 0; - uint32_t c1 = 0; - uint32_t c2 = 0; - uint32_t k = 0; - uint32_t i, tt; - uint32_t t0, t1; - - __asm__ volatile ( - ".syntax unified \n\t" - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[i], #0 \n\t" /* i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[i], %[k] \n\t" /* i = k */ - "subs %[i], %[last_word] \n\t" /* i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "subs %[tt], %[k], %[i] \n\t" /* tt = k-i */ - - "ldr %[t1], [%[left], %[tt]] \n\t" /* t1 = left[k - i] */ - "ldr %[t0], [%[left], %[i]] \n\t" /* t0 = left[i] */ - - "umull %[t0], %[t1], %[t0], %[t1] \n\t" /* (t0, t1) = left[i] * right[k - i] */ - - "cmp %[i], %[tt] \n\t" /* (i < k - i) ? */ - "bge 4f \n\t" /* if i >= k - i, skip */ - "adds %[c0], %[c0], %[t0] \n\t" /* add low word to c0 */ - "adcs %[c1], %[c1], %[t1] \n\t" /* add high word to c1, including carry */ - "adcs %[c2], %[c2], #0 \n\t" /* add carry to c2 */ - - "4: \n\t" - "adds %[c0], %[c0], %[t0] \n\t" /* add low word to c0 */ - "adcs %[c1], %[c1], %[t1] \n\t" /* add high word to c1, including carry */ - "adcs %[c2], %[c2], #0 \n\t" /* add carry to c2 */ - - "adds %[i], #4 \n\t" /* i += 4 */ - "cmp %[i], %[k] \n\t" /* i >= k? */ - "bge 5f \n\t" /* if so, exit the loop */ - "subs %[tt], %[k], %[i] \n\t" /* tt = k - i */ - "cmp %[i], %[tt] \n\t" /* i <= k - i? */ - "ble 3b \n\t" /* if so, continue looping */ - - "5: \n\t" /* end inner loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[k] = c0 */ - "mov %[c0], %[c1] \n\t" /* c0 = c1 */ - "mov %[c1], %[c2] \n\t" /* c1 = c2 */ - "movs %[c2], #0 \n\t" /* c2 = 0 */ - "adds %[k], #4 \n\t" /* k += 4 */ - "cmp %[k], %[last_word] \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[k], %[last_word], lsl #1 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, start with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[num_words * 2 - 1] = c0 */ - RESUME_SYNTAX - : [c0] "+r" (c0), [c1] "+r" (c1), [c2] "+r" (c2), - [k] "+r" (k), [i] "=&r" (i), [tt] "=&r" (tt), [t0] "=&r" (t0), [t1] "=&r" (t1) - : [result] "r" (result), [left] "r" (left), [last_word] "r" ((num_words - 1) * 4) - : "cc", "memory" - ); - -#else - uint32_t r3, r4, r5, r6, r7; - - __asm__ volatile ( - ".syntax unified \n\t" - "subs %[r2], #1 \n\t" /* r2 = num_words - 1 */ - "lsls %[r2], #2 \n\t" /* r2 = (num_words - 1) * 4 */ - "mov r8, %[r2] \n\t" /* r8 = (num_words - 1) * 4 */ - "lsls %[r2], #1 \n\t" /* r2 = (num_words - 1) * 8 */ - "mov r9, %[r2] \n\t" /* r9 = (num_words - 1) * 8 */ - "movs %[r2], #0 \n\t" /* c0 = 0 */ - "movs %[r3], #0 \n\t" /* c1 = 0 */ - "movs %[r4], #0 \n\t" /* c2 = 0 */ - "movs %[r5], #0 \n\t" /* k = 0 */ - - "push {%[r0]} \n\t" /* keep result on the stack */ - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[r6], #0 \n\t" /* r6 = i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[r6], %[r5] \n\t" /* r6 = k */ - "mov %[r0], r8 \n\t" /* r0 = (num_words - 1) * 4 */ - "subs %[r6], %[r0] \n\t" /* r6 = i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "mov r10, %[r2] \n\t" - "mov r11, %[r3] \n\t" - "mov r12, %[r4] \n\t" - "mov r14, %[r5] \n\t" - "subs %[r7], %[r5], %[r6] \n\t" /* r7 = k - i */ - - "ldr %[r3], [%[r1], %[r7]] \n\t" /* r3 = left[k - i] */ - "ldr %[r0], [%[r1], %[r6]] \n\t" /* r0 = left[i] */ - - "lsrs %[r2], %[r0], #16 \n\t" /* r2 = a1 */ - "uxth %[r0], %[r0] \n\t" /* r0 = a0 */ - - "lsrs %[r4], %[r3], #16 \n\t" /* r4 = b1 */ - "uxth %[r3], %[r3] \n\t" /* r3 = b0 */ - - "movs %[r5], %[r2] \n\t" /* r5 = a1 */ - "muls %[r5], %[r4], %[r5] \n\t" /* r5 = a1 * b1 */ - "muls %[r2], %[r3], %[r2] \n\t" /* r2 = b0 * a1 */ - "muls %[r4], %[r0], %[r4] \n\t" /* r4 = a0 * b1 */ - "muls %[r0], %[r3], %[r0] \n\t" /* r0 = a0 * b0 */ - - /* Add middle terms */ - "lsls %[r3], %[r2], #16 \n\t" - "lsrs %[r2], %[r2], #16 \n\t" - "adds %[r0], %[r3] \n\t" - "adcs %[r5], %[r2] \n\t" - - "lsls %[r3], %[r4], #16 \n\t" - "lsrs %[r4], %[r4], #16 \n\t" - "adds %[r0], %[r3] \n\t" - "adcs %[r5], %[r4] \n\t" - - /* Add to acc, doubling if necessary */ - "mov %[r2], r10\n\t" - "mov %[r3], r11\n\t" - "mov %[r4], r12\n\t" - - "cmp %[r6], %[r7] \n\t" /* (i < k - i) ? */ - "bge 4f \n\t" /* if i >= k - i, skip */ - "movs %[r7], #0 \n\t" /* r7 = 0 */ - "adds %[r2], %[r0] \n\t" /* add low word to c0 */ - "adcs %[r3], %[r5] \n\t" /* add high word to c1, including carry */ - "adcs %[r4], %[r7] \n\t" /* add carry to c2 */ - "4: \n\t" - "movs %[r7], #0 \n\t" /* r7 = 0 */ - "adds %[r2], %[r0] \n\t" /* add low word to c0 */ - "adcs %[r3], %[r5] \n\t" /* add high word to c1, including carry */ - "adcs %[r4], %[r7] \n\t" /* add carry to c2 */ - - "mov %[r5], r14\n\t" /* r5 = k */ - - "adds %[r6], #4 \n\t" /* i += 4 */ - "cmp %[r6], %[r5] \n\t" /* i >= k? */ - "bge 5f \n\t" /* if so, exit the loop */ - "subs %[r7], %[r5], %[r6] \n\t" /* r7 = k - i */ - "cmp %[r6], %[r7] \n\t" /* i <= k - i? */ - "ble 3b \n\t" /* if so, continue looping */ - - "5: \n\t" /* end inner loop */ - - "ldr %[r0], [sp, #0] \n\t" /* r0 = result */ - - "str %[r2], [%[r0], %[r5]] \n\t" /* result[k] = c0 */ - "mov %[r2], %[r3] \n\t" /* c0 = c1 */ - "mov %[r3], %[r4] \n\t" /* c1 = c2 */ - "movs %[r4], #0 \n\t" /* c2 = 0 */ - "adds %[r5], #4 \n\t" /* k += 4 */ - "cmp %[r5], r8 \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[r5], r9 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[r2], [%[r0], %[r5]] \n\t" /* result[num_words * 2 - 1] = c0 */ - "pop {%[r0]} \n\t" /* pop result off the stack */ - - ".syntax divided \n\t" - : [r2] "+l" (num_words), [r3] "=&l" (r3), [r4] "=&l" (r4), - [r5] "=&l" (r5), [r6] "=&l" (r6), [r7] "=&l" (r7) - : [r0] "l" (result), [r1] "l" (left) - : "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -#endif -} -#define asm_square 1 -#endif -#endif /* uECC_SQUARE_FUNC */ - -#endif /* _UECC_ASM_ARM_H_ */ diff --git a/lib/micro-ecc/asm_arm_mult_square.inc b/lib/micro-ecc/asm_arm_mult_square.inc deleted file mode 100644 index 8907fc1858..0000000000 --- a/lib/micro-ecc/asm_arm_mult_square.inc +++ /dev/null @@ -1,2311 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_ASM_ARM_MULT_SQUARE_H_ -#define _UECC_ASM_ARM_MULT_SQUARE_H_ - -#define FAST_MULT_ASM_5 \ - "push {r3} \n\t" \ - "add r0, 12 \n\t" \ - "add r2, 12 \n\t" \ - "ldmia r1!, {r3,r4} \n\t" \ - "ldmia r2!, {r6,r7} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adc r10, r10, r14 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - \ - "sub r0, 28 \n\t" \ - "sub r2, 20 \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - "ldmia r1!, {r5} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adc r11, r11, r9 \n\t" \ - "stmia r0!, {r10, r11} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_5_TO_6 \ - "cmp r3, #5 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high, r5 = right high */ \ - "ldr r4, [r1] \n\t" \ - "ldr r5, [r2] \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - "sub r2, #20 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - /* skip past already-loaded (r4, r5) */ \ - "ldr r7, [r1], #8 \n\t" \ - "ldr r8, [r2], #8 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "umull r11, r12, r4, r5 \n\t" \ - "adds r11, r11, r14 \n\t" \ - "adc r12, r12, r9 \n\t" \ - "stmia r0!, {r11, r12} \n\t" - -#define FAST_MULT_ASM_6 \ - "push {r3} \n\t" \ - "add r0, 12 \n\t" \ - "add r2, 12 \n\t" \ - "ldmia r1!, {r3,r4,r5} \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - \ - "sub r0, 36 \n\t" \ - "sub r2, 24 \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r1!, {r5} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r2!, {r8} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r5, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "umull r10, r11, r5, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adc r14, r14, r11 \n\t" \ - "stmia r0!, {r12, r14} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_6_TO_7 \ - "cmp r3, #6 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high, r5 = right high */ \ - "ldr r4, [r1] \n\t" \ - "ldr r5, [r2] \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - "sub r2, #24 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - /* skip past already-loaded (r4, r5) */ \ - "ldr r7, [r1], #8 \n\t" \ - "ldr r8, [r2], #8 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "umull r11, r12, r4, r5 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" - -#define FAST_MULT_ASM_7 \ - "push {r3} \n\t" \ - "add r0, 24 \n\t" \ - "add r2, 24 \n\t" \ - "ldmia r1!, {r3} \n\t" \ - "ldmia r2!, {r6} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - \ - "sub r0, 20 \n\t" \ - "sub r2, 16 \n\t" \ - "ldmia r2!, {r6, r7, r8} \n\t" \ - "ldmia r1!, {r4, r5} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r12, r3, r7 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r9, r11, r4, r6 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adcs r12, r12, r11 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - \ - "sub r0, 44 \n\t" \ - "sub r1, 16 \n\t" \ - "sub r2, 28 \n\t" \ - "ldmia r1!, {r3,r4,r5} \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r12, r3, r7 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r9, r11, r4, r6 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adcs r12, r12, r11 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r5} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r5, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r4, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r8} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r4, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "umull r10, r11, r3, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adc r14, r14, r11 \n\t" \ - "stmia r0!, {r12, r14} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_7_TO_8 \ - "cmp r3, #7 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high, r5 = right high */ \ - "ldr r4, [r1] \n\t" \ - "ldr r5, [r2] \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - "sub r2, #28 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - /* skip past already-loaded (r4, r5) */ \ - "ldr r7, [r1], #8 \n\t" \ - "ldr r8, [r2], #8 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "umull r11, r12, r4, r5 \n\t" \ - "adds r11, r11, r10 \n\t" \ - "adc r12, r12, r14 \n\t" \ - "stmia r0!, {r11, r12} \n\t" - -#define FAST_MULT_ASM_8 \ - "push {r3} \n\t" \ - "add r0, 24 \n\t" \ - "add r2, 24 \n\t" \ - "ldmia r1!, {r3,r4} \n\t" \ - "ldmia r2!, {r6,r7} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adc r10, r10, r14 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - \ - "sub r0, 28 \n\t" \ - "sub r2, 20 \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - "ldmia r1!, {r5} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adc r11, r11, r9 \n\t" \ - "stmia r0!, {r10, r11} \n\t" \ - \ - "sub r0, 52 \n\t" \ - "sub r1, 20 \n\t" \ - "sub r2, 32 \n\t" \ - "ldmia r1!, {r3,r4,r5} \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r1!, {r5} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r8} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r5, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r5, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r5, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r3, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "umull r9, r10, r4, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - "pop {r3} \n\t" - -#define FAST_SQUARE_ASM_5 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2,r3,r4,r5,r6} \n\t" \ - "push {r1} \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "umull r1, r14, r3, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "umull r1, r14, r3, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "umull r1, r14, r4, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r3, r6 \n\t" \ - "umull r1, r14, r4, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r4, r6 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r5, r5 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r5, r6 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r6, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_5_TO_6 \ - "cmp r2, #5 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r6,r7,r8,r9,r10,r11} \n\t" \ - "umull r3, r4, r6, r11 \n\t" \ - "umull r6, r5, r7, r11 \n\t" \ - "adds r4, r4, r6 \n\t" \ - "umull r7, r6, r8, r11 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "umull r8, r7, r9, r11 \n\t" \ - "adcs r6, r6, r8 \n\t" \ - "umull r9, r8, r10, r11 \n\t" \ - "adcs r7, r7, r9 \n\t" \ - "adcs r8, r8, #0 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r9, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r14, [r0], #4 \n\t" \ - "adds r3, r3, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r4, r4, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r5, r5, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r6, r6, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r7, r7, r14 \n\t" \ - "adcs r8, r8, #0 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "sub r0, #20 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r8, r9, r11, r11 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9} \n\t" - -#define FAST_SQUARE_ASM_6 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2,r3,r4,r5,r6,r7} \n\t" \ - "push {r1} \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "umull r1, r14, r3, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "umull r1, r14, r3, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "umull r1, r14, r4, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r7 \n\t" \ - "umull r1, r14, r3, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r1, r14, r4, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r3, r7 \n\t" \ - "umull r1, r14, r4, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "umull r1, r14, r5, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r4, r7 \n\t" \ - "umull r1, r14, r5, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r5, r7 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r6, r6 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r6, r7 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r7, r7 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_6_TO_7 \ - "cmp r2, #6 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r6,r7,r8,r9,r10,r11,r12} \n\t" \ - "umull r3, r4, r6, r12 \n\t" \ - "umull r6, r5, r7, r12 \n\t" \ - "adds r4, r4, r6 \n\t" \ - "umull r7, r6, r8, r12 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "umull r8, r7, r9, r12 \n\t" \ - "adcs r6, r6, r8 \n\t" \ - "umull r9, r8, r10, r12 \n\t" \ - "adcs r7, r7, r9 \n\t" \ - "umull r10, r9, r11, r12 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r10, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r14, [r0], #4 \n\t" \ - "adds r3, r3, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r4, r4, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r5, r5, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r6, r6, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r7, r7, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "sub r0, #24 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r9, r10, r12, r12 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10} \n\t" - -#define FAST_SQUARE_ASM_7 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2, r3, r4, r5, r6, r7, r8} \n\t" \ - "push {r1} \n\t" \ - "sub r1, 4 \n\t" \ - \ - "add r0, 24 \n\t" \ - "umull r9, r10, r2, r8 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - "sub r0, 32 \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r4 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r3, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r4 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r7 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r5 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "ldmia r1!, {r2} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r3, r7 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r3, r2 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r5, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r4, r2 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r7 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r6, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r5, r2 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r6, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r6, r2 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r7, r7 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r7, r2 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r2, r2 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_7_TO_8 \ - "cmp r2, #7 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r6,r7,r8,r9,r10,r11,r12,r14} \n\t" \ - "umull r3, r4, r6, r14 \n\t" \ - "umull r6, r5, r7, r14 \n\t" \ - "adds r4, r4, r6 \n\t" \ - "umull r7, r6, r8, r14 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "umull r8, r7, r9, r14 \n\t" \ - "adcs r6, r6, r8 \n\t" \ - "umull r9, r8, r10, r14 \n\t" \ - "adcs r7, r7, r9 \n\t" \ - "umull r10, r9, r11, r14 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "umull r11, r10, r12, r14 \n\t" \ - "adcs r9, r9, r11 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r11, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "sub r0, #28 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r10, r11, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10,r11} \n\t" - -#define FAST_SQUARE_ASM_8 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2,r3,r4,r5,r6,r7,r8,r9} \n\t" \ - "push {r1} \n\t" \ - "sub r1, 8 \n\t" \ - \ - "add r0, 24 \n\t" \ - "umull r10, r11, r2, r8 \n\t" \ - "umull r12, r14, r2, r9 \n\t" \ - "umull r8, r9, r3, r9 \n\t" \ - "adds r11, r11, r12 \n\t" \ - "adcs r12, r14, r8 \n\t" \ - "adcs r14, r9, #0 \n\t" \ - "stmia r0!, {r10, r11, r12, r14} \n\t" \ - "sub r0, 40 \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r4 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r3, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r4 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r7 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r5 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "ldmia r1!, {r2} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r3, r7 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r3, r2 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r5, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r4, r2 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r7 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r6, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r4, r3 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r5, r2 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r6, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r5, r3 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r6, r2 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r7, r7 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r6, r3 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r7, r2 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r7, r3 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r2, r2 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r2, r3 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r3, r3 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#endif /* _UECC_ASM_ARM_MULT_SQUARE_H_ */ diff --git a/lib/micro-ecc/asm_arm_mult_square_umaal.inc b/lib/micro-ecc/asm_arm_mult_square_umaal.inc deleted file mode 100644 index c554d20e38..0000000000 --- a/lib/micro-ecc/asm_arm_mult_square_umaal.inc +++ /dev/null @@ -1,1202 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_ASM_ARM_MULT_SQUARE_H_ -#define _UECC_ASM_ARM_MULT_SQUARE_H_ - -#define FAST_MULT_ASM_5 \ - "push {r3} \n\t" \ - "ldmia r2!, {r3, r4, r5, r6, r7} \n\t" \ - "push {r2} \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "umull r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "mov r14, #0 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "pop {r2, r3} \n\t" - -#define FAST_MULT_ASM_5_TO_6 \ - "cmp r3, #5 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high */ \ - "ldr r4, [r1] \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - "sub r2, #20 \n\t" \ - \ - /* Do right side */ \ - "ldr r14, [r2], #4 \n\t" \ - "mov r5, #0 \n\t" \ - "ldr r6, [r0], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r7, [r0], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r8, [r0], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r9, [r0], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r10, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "sub r0, #20 \n\t" \ - \ - /* r4 = right high */ \ - "ldr r4, [r2], #4 \n\t" \ - \ - /* Do left side */ \ - "ldr r14, [r1], #4 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r12, r5, r4, r14 \n\t" \ - "str r12, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "str r5, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "str r6, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "str r7, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "stmia r0!, {r9, r10} \n\t" - -#define FAST_MULT_ASM_6 \ - "ldmia r2!, {r4, r5, r6} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umull r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - "ldmia r2!, {r4, r5, r6} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" - -#define FAST_MULT_ASM_6_TO_7 \ - "cmp r3, #6 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high */ \ - "ldr r4, [r1] \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - "sub r2, #24 \n\t" \ - \ - /* Do right side */ \ - "ldr r14, [r2], #4 \n\t" \ - "mov r5, #0 \n\t" \ - "ldr r6, [r0], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r7, [r0], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r8, [r0], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r9, [r0], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r10, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r11, [r0], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "sub r0, #24 \n\t" \ - \ - /* r4 = right high */ \ - "ldr r4, [r2], #4 \n\t" \ - \ - /* Do left side */ \ - "ldr r14, [r1], #4 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r12, r5, r4, r14 \n\t" \ - "str r12, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "str r5, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "str r6, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "str r7, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "stmia r0!, {r10, r11} \n\t" - -#define FAST_MULT_ASM_7 \ - "ldmia r2!, {r4, r5, r6, r7} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umull r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - "ldmia r2!, {r4, r5, r6} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" - -#define FAST_MULT_ASM_7_TO_8 \ - "cmp r3, #7 \n\t" \ - "beq 1f \n\t" \ - "push {r3} \n\t" \ - \ - /* r4 = left high */ \ - "ldr r4, [r1] \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - "sub r2, #28 \n\t" \ - \ - /* Do right side */ \ - "ldr r14, [r2], #4 \n\t" \ - "mov r5, #0 \n\t" \ - "ldr r6, [r0], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r7, [r0], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r8, [r0], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r9, [r0], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r10, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r11, [r0], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "umaal r11, r12, r4, r14 \n\t" \ - "sub r0, #28 \n\t" \ - \ - /* r4 = right high */ \ - "ldr r4, [r2], #4 \n\t" \ - \ - /* Do left side */ \ - "ldr r14, [r1], #4 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r3, r5, r4, r14 \n\t" \ - "str r3, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "str r5, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "str r6, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "str r7, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "str r9, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r11, r12, r4, r14 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_8 \ - "ldmia r2!, {r4, r5, r6, r7} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umull r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" \ - \ - "sub r0, #32 \n\t" \ - "sub r1, #32 \n\t" \ - "ldmia r2!, {r4, r5, r6, r7} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" - -#define FAST_SQUARE_ASM_5 \ - "ldmia r1!, {r9,r10,r11,r12,r14} \n\t" \ - "push {r1, r2} \n\t" \ - \ - "umull r1, r2, r10, r9 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r2, r3, r11, r9 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r12, r9 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r14, r9 \n\t" \ - \ - "mov r6, #0 \n\t" \ - "umaal r6, r3, r11, r10 \n\t" \ - "umaal r3, r4, r12, r10 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - \ - "umull r7, r8, r9, r9 \n\t" \ - /* Store carry in r9 */ \ - "mov r9, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "stmia r0!, {r7,r8} \n\t" \ - \ - "umull r7, r8, r10, r10 \n\t" \ - "adcs r7, r7, r2 \n\t" \ - "adcs r8, r8, r6 \n\t" \ - "stmia r0!, {r7,r8} \n\t" \ - \ - "umaal r4, r5, r14, r10 \n\t" \ - /* Store carry in r10 */ \ - "mov r10, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - \ - "mov r1, #0 \n\t" \ - "umaal r1, r4, r12, r11 \n\t" \ - "umaal r4, r5, r14, r11 \n\t" \ - \ - "mov r2, #0 \n\t" \ - "umaal r2, r5, r14, r12 \n\t" \ - /* Load carry from r9 */ \ - "lsrs r9, #1 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - /* r9 is 0 now */ \ - "adc r9, r9, #0 \n\t" \ - \ - /* Use carry from r10 */ \ - "umaal r3, r10, r11, r11 \n\t" \ - "adds r10, r10, r1 \n\t" \ - "stmia r0!, {r3,r10} \n\t" \ - \ - "umull r6, r10, r12, r12 \n\t" \ - "adcs r6, r6, r4 \n\t" \ - "adcs r10, r10, r2 \n\t" \ - "stmia r0!, {r6,r10} \n\t" \ - \ - "umull r6, r10, r14, r14 \n\t" \ - "adcs r6, r6, r5 \n\t" \ - "adcs r10, r10, r9 \n\t" \ - "stmia r0!, {r6,r10} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_5_TO_6 \ - "cmp r2, #5 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r5,r6,r7,r8,r9,r14} \n\t" \ - "umull r3, r4, r5, r14 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r14 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r7, r14 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r8, r14 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r7, r8, r9, r14 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r9, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "adcs r8, r8, #0 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "sub r0, #20 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r8, r9, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9} \n\t" - -#define FAST_SQUARE_ASM_6 \ - "ldmia r1!, {r8,r9,r10,r11,r12,r14} \n\t" \ - "push {r1, r2} \n\t" \ - \ - "umull r1, r2, r9, r8 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r2, r3, r10, r8 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r11, r8 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r12, r8 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r14, r8 \n\t" \ - \ - "mov r7, #0 \n\t" \ - "umaal r7, r3, r10, r9 \n\t" \ - "umaal r3, r4, r11, r9 \n\t" \ - "umaal r4, r5, r12, r9 \n\t" \ - "push {r4, r5} \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - \ - "umull r4, r5, r8, r8 \n\t" \ - /* Store carry in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r5, r5, r1 \n\t" \ - "stmia r0!, {r4,r5} \n\t" \ - \ - "umull r4, r5, r9, r9 \n\t" \ - "adcs r4, r4, r2 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "stmia r0!, {r4,r5} \n\t" \ - \ - "pop {r4, r5} \n\t" \ - "umaal r5, r6, r14, r9 \n\t" \ - /* Store carry in r9 */ \ - "mov r9, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - \ - "mov r1, #0 \n\t" \ - "umaal r1, r4, r11, r10 \n\t" \ - "umaal r4, r5, r12, r10 \n\t" \ - "umaal r5, r6, r14, r10 \n\t" \ - \ - "mov r2, #0 \n\t" \ - "umaal r2, r5, r12, r11 \n\t" \ - "umaal r5, r6, r14, r11 \n\t" \ - \ - "mov r7, #0 \n\t" \ - "umaal r7, r6, r14, r12 \n\t" \ - \ - /* Load carry from r8 */ \ - "lsrs r8, #1 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - /* Use carry from r9 */ \ - "umaal r3, r9, r10, r10 \n\t" \ - "adds r9, r9, r1 \n\t" \ - "stmia r0!, {r3,r9} \n\t" \ - \ - "umull r9, r10, r11, r11 \n\t" \ - "adcs r9, r9, r4 \n\t" \ - "adcs r10, r10, r2 \n\t" \ - "stmia r0!, {r9,r10} \n\t" \ - \ - "umull r9, r10, r12, r12 \n\t" \ - "adcs r9, r9, r5 \n\t" \ - "adcs r10, r10, r7 \n\t" \ - "stmia r0!, {r9,r10} \n\t" \ - \ - "umull r9, r10, r14, r14 \n\t" \ - "adcs r9, r9, r6 \n\t" \ - "adcs r10, r10, r8 \n\t" \ - "stmia r0!, {r9,r10} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_6_TO_7 \ - "cmp r2, #6 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r5,r6,r7,r8,r9,r10,r14} \n\t" \ - "umull r3, r4, r5, r14 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r14 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r7, r14 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r8, r14 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r7, r8, r9, r14 \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r10, r14 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r10, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "sub r0, #24 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r9, r10, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10} \n\t" - -#define FAST_SQUARE_ASM_7 \ - "ldmia r1!, {r9,r10,r11,r12} \n\t" \ - "push {r2} \n\t" \ - \ - "umull r14, r2, r10, r9 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r2, r3, r11, r9 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r12, r9 \n\t" \ - \ - "mov r5, #0 \n\t" \ - "umaal r5, r3, r11, r10 \n\t" \ - "adds r14, r14, r14 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - /* Store carry in r7 */ \ - "mov r7, #0 \n\t" \ - "adc r7, r7, #0 \n\t" \ - \ - "umull r6, r8, r9, r9 \n\t" \ - "adds r8, r8, r14 \n\t" \ - "stmia r0!, {r6,r8} \n\t" \ - \ - "umull r6, r8, r10, r10 \n\t" \ - "adcs r6, r6, r2 \n\t" \ - "adcs r8, r8, r5 \n\t" \ - "stmia r0!, {r6,r8} \n\t" \ - /* Store carry in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "ldmia r1!, {r2, r6, r14} \n\t" \ - "push {r1} \n\t" \ - "umaal r3, r4, r2, r9 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r9 \n\t" \ - "mov r1, #0 \n\t" \ - "umaal r5, r1, r14, r9 \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umaal r3, r9, r12, r10 \n\t" \ - "umaal r9, r4, r2, r10 \n\t" \ - "umaal r4, r5, r6, r10 \n\t" \ - "umaal r5, r1, r14, r10 \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umaal r10, r9, r12, r11 \n\t" \ - "umaal r9, r4, r2, r11 \n\t" \ - "umaal r4, r5, r6, r11 \n\t" \ - "umaal r5, r1, r14, r11 \n\t" \ - \ - /* Load carry from r7 */ \ - "lsrs r7, #1 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - /* Store carry back in r7 */ \ - "adc r7, r7, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r3, r8, r11, r11 \n\t" \ - "adds r8, r8, r10 \n\t" \ - "stmia r0!, {r3,r8} \n\t" \ - /* Store carry back in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "mov r3, #0 \n\t" \ - "umaal r3, r4, r2, r12 \n\t" \ - "umaal r4, r5, r6, r12 \n\t" \ - "umaal r5, r1, r14, r12 \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umaal r10, r5, r6, r2 \n\t" \ - "umaal r5, r1, r14, r2 \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umaal r11, r1, r14, r6 \n\t" \ - \ - /* Load carry from r7 */ \ - "lsrs r7, #1 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - "adc r7, r7, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r8, r9, r12, r12 \n\t" \ - "adds r9, r9, r3 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r2, r2 \n\t" \ - "adcs r8, r8, r4 \n\t" \ - "adcs r9, r9, r10 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r6, r6 \n\t" \ - "adcs r8, r8, r5 \n\t" \ - "adcs r9, r9, r11 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r14, r14 \n\t" \ - "adcs r8, r8, r1 \n\t" \ - "adcs r9, r9, r7 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_7_TO_8 \ - "cmp r2, #7 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r5,r6,r7,r8,r9,r10,r11,r14} \n\t" \ - "umull r3, r4, r5, r14 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r14 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r7, r14 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r8, r14 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r7, r8, r9, r14 \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r10, r14 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r11, r14 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r11, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "sub r0, #28 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r10, r11, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10,r11} \n\t" - -#define FAST_SQUARE_ASM_8 \ - "ldmia r1!, {r10,r11,r12,r14} \n\t" \ - "push {r2} \n\t" \ - \ - "umull r2, r3, r11, r10 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r12, r10 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r14, r10 \n\t" \ - \ - "mov r6, #0 \n\t" \ - "umaal r6, r4, r12, r11 \n\t" \ - "adds r2, r2, r2 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - /* Store carry in r7 */ \ - "mov r7, #0 \n\t" \ - "adc r7, r7, #0 \n\t" \ - \ - "umull r8, r9, r10, r10 \n\t" \ - "adds r9, r9, r2 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r11, r11 \n\t" \ - "adcs r8, r8, r3 \n\t" \ - "adcs r9, r9, r6 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - /* Store carry in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "ldmia r1!, {r2, r3} \n\t" \ - "push {r1} \n\t" \ - "umaal r4, r5, r2, r10 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r3, r10 \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umaal r9, r4, r14, r11 \n\t" \ - "umaal r4, r5, r2, r11 \n\t" \ - \ - "mov r1, #0 \n\t" \ - "umaal r1, r4, r14, r12 \n\t" \ - \ - /* Load carry from r7 */ \ - "lsrs r7, #1 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - /* Store carry back in r7 */ \ - "adc r7, r7, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r8, r9, r12, r12 \n\t" \ - "adds r9, r9, r1 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - /* Store carry back in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "pop {r1} \n\t" \ - /* TODO could fix up r1 value on stack here */ \ - /* and leave the value on the stack (rather */ \ - /* than popping) if supporting curves > 256 bits */ \ - "ldr r9, [r1], #4 \n\t" \ - "ldr r1, [r1] \n\t" \ - \ - "push {r7} \n\t" \ - "umaal r5, r6, r9, r10 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r1, r10 \n\t" \ - /* Carry now stored in r10 */ \ - "pop {r10} \n\t" \ - \ - "umaal r4, r5, r3, r11 \n\t" \ - "umaal r5, r6, r9, r11 \n\t" \ - "umaal r6, r7, r1, r11 \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umaal r11, r4, r2, r12 \n\t" \ - "umaal r4, r5, r3, r12 \n\t" \ - "umaal r5, r6, r9, r12 \n\t" \ - "umaal r6, r7, r1, r12 \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umaal r12, r4, r2, r14 \n\t" \ - "umaal r4, r5, r3, r14 \n\t" \ - "umaal r5, r6, r9, r14 \n\t" \ - "umaal r6, r7, r1, r14 \n\t" \ - \ - /* Load carry from r10 */ \ - "lsrs r10, #1 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r8, r11, r14, r14 \n\t" \ - "adds r11, r11, r12 \n\t" \ - "stmia r0!, {r8,r11} \n\t" \ - /* Store carry back in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umaal r11, r5, r3, r2 \n\t" \ - "umaal r5, r6, r9, r2 \n\t" \ - "umaal r6, r7, r1, r2 \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umaal r12, r6, r9, r3 \n\t" \ - "umaal r6, r7, r1, r3 \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umaal r14, r7, r1, r9 \n\t" \ - \ - /* Load carry from r10 */ \ - "lsrs r10, #1 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r14, r14, r14 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adc r10, r10, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r4, r8, r2, r2 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - \ - "umull r4, r8, r3, r3 \n\t" \ - "adcs r4, r4, r5 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - \ - "umull r4, r8, r9, r9 \n\t" \ - "adcs r4, r4, r6 \n\t" \ - "adcs r8, r8, r14 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - \ - "umull r4, r8, r1, r1 \n\t" \ - "adcs r4, r4, r7 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - /* TODO pop {r1, r2} if supporting curves > 256 bits */ \ - "pop {r2} \n\t" - -#endif /* _UECC_ASM_ARM_MULT_SQUARE_H_ */ diff --git a/lib/micro-ecc/curve-specific.inc b/lib/micro-ecc/curve-specific.inc deleted file mode 100644 index f5d3da842c..0000000000 --- a/lib/micro-ecc/curve-specific.inc +++ /dev/null @@ -1,1249 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_CURVE_SPECIFIC_H_ -#define _UECC_CURVE_SPECIFIC_H_ - -#define num_bytes_secp160r1 20 -#define num_bytes_secp192r1 24 -#define num_bytes_secp224r1 28 -#define num_bytes_secp256r1 32 -#define num_bytes_secp256k1 32 - -#if (uECC_WORD_SIZE == 1) - -#define num_words_secp160r1 20 -#define num_words_secp192r1 24 -#define num_words_secp224r1 28 -#define num_words_secp256r1 32 -#define num_words_secp256k1 32 - -#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \ - 0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h -#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d - -#elif (uECC_WORD_SIZE == 4) - -#define num_words_secp160r1 5 -#define num_words_secp192r1 6 -#define num_words_secp224r1 7 -#define num_words_secp256r1 8 -#define num_words_secp256k1 8 - -#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a, 0x##h##g##f##e -#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a - -#elif (uECC_WORD_SIZE == 8) - -#define num_words_secp160r1 3 -#define num_words_secp192r1 3 -#define num_words_secp224r1 4 -#define num_words_secp256r1 4 -#define num_words_secp256k1 4 - -#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##h##g##f##e##d##c##b##a##ull -#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##ull - -#endif /* uECC_WORD_SIZE */ - -#if uECC_SUPPORTS_secp160r1 || uECC_SUPPORTS_secp192r1 || \ - uECC_SUPPORTS_secp224r1 || uECC_SUPPORTS_secp256r1 -static void double_jacobian_default(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve) { - /* t1 = X, t2 = Y, t3 = Z */ - uECC_word_t t4[uECC_MAX_WORDS]; - uECC_word_t t5[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - if (uECC_vli_isZero(Z1, num_words)) { - return; - } - - uECC_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */ - uECC_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 = A */ - uECC_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */ - uECC_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */ - uECC_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */ - - uECC_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1 + z1^2 */ - uECC_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 = 2*z1^2 */ - uECC_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1 - z1^2 */ - uECC_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 - z1^4 */ - - uECC_vli_modAdd(Z1, X1, X1, curve->p, num_words); /* t3 = 2*(x1^2 - z1^4) */ - uECC_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = 3*(x1^2 - z1^4) */ - if (uECC_vli_testBit(X1, 0)) { - uECC_word_t l_carry = uECC_vli_add(X1, X1, curve->p, num_words); - uECC_vli_rshift1(X1, num_words); - X1[num_words - 1] |= l_carry << (uECC_WORD_BITS - 1); - } else { - uECC_vli_rshift1(X1, num_words); - } - /* t1 = 3/2*(x1^2 - z1^4) = B */ - - uECC_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */ - uECC_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - A */ - uECC_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - 2A = x3 */ - uECC_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A - x3 */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A - x3) */ - uECC_vli_modSub(t4, X1, t4, curve->p, num_words); /* t4 = B * (A - x3) - y1^4 = y3 */ - - uECC_vli_set(X1, Z1, num_words); - uECC_vli_set(Z1, Y1, num_words); - uECC_vli_set(Y1, t4, num_words); -} - -/* Computes result = x^3 + ax + b. result must not overlap x. */ -static void x_side_default(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve) { - uECC_word_t _3[uECC_MAX_WORDS] = {3}; /* -a = 3 */ - wordcount_t num_words = curve->num_words; - - uECC_vli_modSquare_fast(result, x, curve); /* r = x^2 */ - uECC_vli_modSub(result, result, _3, curve->p, num_words); /* r = x^2 - 3 */ - uECC_vli_modMult_fast(result, result, x, curve); /* r = x^3 - 3x */ - uECC_vli_modAdd(result, result, curve->b, curve->p, num_words); /* r = x^3 - 3x + b */ -} -#endif /* uECC_SUPPORTS_secp... */ - -#if uECC_SUPPORT_COMPRESSED_POINT -#if uECC_SUPPORTS_secp160r1 || uECC_SUPPORTS_secp192r1 || \ - uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1 -/* Compute a = sqrt(a) (mod curve_p). */ -static void mod_sqrt_default(uECC_word_t *a, uECC_Curve curve) { - bitcount_t i; - uECC_word_t p1[uECC_MAX_WORDS] = {1}; - uECC_word_t l_result[uECC_MAX_WORDS] = {1}; - wordcount_t num_words = curve->num_words; - - /* When curve->p == 3 (mod 4), we can compute - sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */ - uECC_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p + 1 */ - for (i = uECC_vli_numBits(p1, num_words) - 1; i > 1; --i) { - uECC_vli_modSquare_fast(l_result, l_result, curve); - if (uECC_vli_testBit(p1, i)) { - uECC_vli_modMult_fast(l_result, l_result, a, curve); - } - } - uECC_vli_set(a, l_result, num_words); -} -#endif /* uECC_SUPPORTS_secp... */ -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -#if uECC_SUPPORTS_secp160r1 - -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp160r1 = { - num_words_secp160r1, - num_bytes_secp160r1, - 161, /* num_n_bits */ - { BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_4(FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9), - BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00) }, - { BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68), - BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E), - BYTES_TO_WORDS_4(68, B5, 96, 4A), - - BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04), - BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31), - BYTES_TO_WORDS_4(55, 28, A6, 23) }, - { BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81), - BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54), - BYTES_TO_WORDS_4(FC, BE, 97, 1C) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp160r1 -#endif -}; - -uECC_Curve uECC_secp160r1(void) { return &curve_secp160r1; } - -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) -/* Computes result = product % curve_p - see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf page 354 - - Note that this only works if log2(omega) < log2(p) / 2 */ -static void omega_mult_secp160r1(uECC_word_t *result, const uECC_word_t *right); -#if uECC_WORD_SIZE == 8 -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp160r1]; - uECC_word_t copy; - - uECC_vli_clear(tmp, num_words_secp160r1); - uECC_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); - - omega_mult_secp160r1(tmp, product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */ - - product[num_words_secp160r1 - 1] &= 0xffffffff; - copy = tmp[num_words_secp160r1 - 1]; - tmp[num_words_secp160r1 - 1] &= 0xffffffff; - uECC_vli_add(result, product, tmp, num_words_secp160r1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp160r1); - tmp[num_words_secp160r1 - 1] = copy; - omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1); /* Rq*c */ - uECC_vli_add(result, result, product, num_words_secp160r1); /* (C1, r) = r + Rq*c */ - - while (uECC_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) > 0) { - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); - } -} - -static void omega_mult_secp160r1(uint64_t *result, const uint64_t *right) { - uint32_t carry; - unsigned i; - - /* Multiply by (2^31 + 1). */ - carry = 0; - for (i = 0; i < num_words_secp160r1; ++i) { - uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32); - result[i] = (tmp << 31) + tmp + carry; - carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i] == tmp)); - } - result[i] = carry; -} -#else -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp160r1]; - uECC_word_t carry; - - uECC_vli_clear(tmp, num_words_secp160r1); - uECC_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); - - omega_mult_secp160r1(tmp, product + num_words_secp160r1); /* (Rq, q) = q * c */ - - carry = uECC_vli_add(result, product, tmp, num_words_secp160r1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp160r1); - omega_mult_secp160r1(product, tmp + num_words_secp160r1); /* Rq*c */ - carry += uECC_vli_add(result, result, product, num_words_secp160r1); /* (C1, r) = r + Rq*c */ - - while (carry > 0) { - --carry; - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); - } - if (uECC_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) > 0) { - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); - } -} -#endif - -#if uECC_WORD_SIZE == 1 -static void omega_mult_secp160r1(uint8_t *result, const uint8_t *right) { - uint8_t carry; - uint8_t i; - - /* Multiply by (2^31 + 1). */ - uECC_vli_set(result + 4, right, num_words_secp160r1); /* 2^32 */ - uECC_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */ - result[3] = right[0] << 7; /* get last bit from shift */ - - carry = uECC_vli_add(result, result, right, num_words_secp160r1); /* 2^31 + 1 */ - for (i = num_words_secp160r1; carry; ++i) { - uint16_t sum = (uint16_t)result[i] + carry; - result[i] = (uint8_t)sum; - carry = sum >> 8; - } -} -#elif uECC_WORD_SIZE == 4 -static void omega_mult_secp160r1(uint32_t *result, const uint32_t *right) { - uint32_t carry; - unsigned i; - - /* Multiply by (2^31 + 1). */ - uECC_vli_set(result + 1, right, num_words_secp160r1); /* 2^32 */ - uECC_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */ - result[0] = right[0] << 31; /* get last bit from shift */ - - carry = uECC_vli_add(result, result, right, num_words_secp160r1); /* 2^31 + 1 */ - for (i = num_words_secp160r1; carry; ++i) { - uint64_t sum = (uint64_t)result[i] + carry; - result[i] = (uint32_t)sum; - carry = sum >> 32; - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) */ - -#endif /* uECC_SUPPORTS_secp160r1 */ - -#if uECC_SUPPORTS_secp192r1 - -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp192r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp192r1 = { - num_words_secp192r1, - num_bytes_secp192r1, - 192, /* num_n_bits */ - { BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14), - BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4), - BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C), - BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18), - - BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73), - BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63), - BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07) }, - { BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE), - BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F), - BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp192r1 -#endif -}; - -uECC_Curve uECC_secp192r1(void) { return &curve_secp192r1; } - -#if (uECC_OPTIMIZATION_LEVEL > 0) -/* Computes result = product % curve_p. - See algorithm 5 and 6 from http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf */ -#if uECC_WORD_SIZE == 1 -static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) { - uint8_t tmp[num_words_secp192r1]; - uint8_t carry; - - uECC_vli_set(result, product, num_words_secp192r1); - - uECC_vli_set(tmp, &product[24], num_words_secp192r1); - carry = uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0; - tmp[8] = product[24]; tmp[9] = product[25]; tmp[10] = product[26]; tmp[11] = product[27]; - tmp[12] = product[28]; tmp[13] = product[29]; tmp[14] = product[30]; tmp[15] = product[31]; - tmp[16] = product[32]; tmp[17] = product[33]; tmp[18] = product[34]; tmp[19] = product[35]; - tmp[20] = product[36]; tmp[21] = product[37]; tmp[22] = product[38]; tmp[23] = product[39]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[8] = product[40]; - tmp[1] = tmp[9] = product[41]; - tmp[2] = tmp[10] = product[42]; - tmp[3] = tmp[11] = product[43]; - tmp[4] = tmp[12] = product[44]; - tmp[5] = tmp[13] = product[45]; - tmp[6] = tmp[14] = product[46]; - tmp[7] = tmp[15] = product[47]; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, num_words_secp192r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); - } -} -#elif uECC_WORD_SIZE == 4 -static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t *product) { - uint32_t tmp[num_words_secp192r1]; - int carry; - - uECC_vli_set(result, product, num_words_secp192r1); - - uECC_vli_set(tmp, &product[6], num_words_secp192r1); - carry = uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[1] = 0; - tmp[2] = product[6]; - tmp[3] = product[7]; - tmp[4] = product[8]; - tmp[5] = product[9]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[2] = product[10]; - tmp[1] = tmp[3] = product[11]; - tmp[4] = tmp[5] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, num_words_secp192r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); - } -} -#else -static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t *product) { - uint64_t tmp[num_words_secp192r1]; - int carry; - - uECC_vli_set(result, product, num_words_secp192r1); - - uECC_vli_set(tmp, &product[3], num_words_secp192r1); - carry = (int)uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = 0; - tmp[1] = product[3]; - tmp[2] = product[4]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[1] = product[5]; - tmp[2] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, num_words_secp192r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0) */ - -#endif /* uECC_SUPPORTS_secp192r1 */ - -#if uECC_SUPPORTS_secp224r1 - -#if uECC_SUPPORT_COMPRESSED_POINT -static void mod_sqrt_secp224r1(uECC_word_t *a, uECC_Curve curve); -#endif -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp224r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp224r1 = { - num_words_secp224r1, - num_bytes_secp224r1, - 224, /* num_n_bits */ - { BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_4(FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13), - BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_4(FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34), - BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A), - BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B), - BYTES_TO_WORDS_4(BD, 0C, 0E, B7), - - BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44), - BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD), - BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5), - BYTES_TO_WORDS_4(88, 63, 37, BD) }, - { BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27), - BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50), - BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C), - BYTES_TO_WORDS_4(85, 0A, 05, B4) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_secp224r1, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp224r1 -#endif -}; - -uECC_Curve uECC_secp224r1(void) { return &curve_secp224r1; } - - -#if uECC_SUPPORT_COMPRESSED_POINT -/* Routine 3.2.4 RS; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rs(uECC_word_t *d1, - uECC_word_t *e1, - uECC_word_t *f1, - const uECC_word_t *d0, - const uECC_word_t *e0, - const uECC_word_t *f0) { - uECC_word_t t[num_words_secp224r1]; - - uECC_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <-- d0 ^ 2 */ - uECC_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1 <-- d0 * e0 */ - uECC_vli_modAdd(d1, t, f0, curve_secp224r1.p, num_words_secp224r1); /* d1 <-- t + f0 */ - uECC_vli_modAdd(e1, e1, e1, curve_secp224r1.p, num_words_secp224r1); /* e1 <-- e1 + e1 */ - uECC_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1 <-- t * f0 */ - uECC_vli_modAdd(f1, f1, f1, curve_secp224r1.p, num_words_secp224r1); /* f1 <-- f1 + f1 */ - uECC_vli_modAdd(f1, f1, f1, curve_secp224r1.p, num_words_secp224r1); /* f1 <-- f1 + f1 */ -} - -/* Routine 3.2.5 RSS; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rss(uECC_word_t *d1, - uECC_word_t *e1, - uECC_word_t *f1, - const uECC_word_t *d0, - const uECC_word_t *e0, - const uECC_word_t *f0, - const bitcount_t j) { - bitcount_t i; - - uECC_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */ - uECC_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */ - uECC_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */ - for (i = 1; i <= j; i++) { - mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS (d1,e1,f1,d1,e1,f1) */ - } -} - -/* Routine 3.2.6 RM; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rm(uECC_word_t *d2, - uECC_word_t *e2, - uECC_word_t *f2, - const uECC_word_t *c, - const uECC_word_t *d0, - const uECC_word_t *e0, - const uECC_word_t *d1, - const uECC_word_t *e1) { - uECC_word_t t1[num_words_secp224r1]; - uECC_word_t t2[num_words_secp224r1]; - - uECC_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1 <-- e0 * e1 */ - uECC_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1 <-- t1 * c */ - /* t1 <-- p - t1 */ - uECC_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p, num_words_secp224r1); - uECC_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2 <-- d0 * d1 */ - uECC_vli_modAdd(t2, t2, t1, curve_secp224r1.p, num_words_secp224r1); /* t2 <-- t2 + t1 */ - uECC_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1 <-- d0 * e1 */ - uECC_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2 <-- d1 * e0 */ - uECC_vli_modAdd(e2, e2, t1, curve_secp224r1.p, num_words_secp224r1); /* e2 <-- e2 + t1 */ - uECC_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <-- e2^2 */ - uECC_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2 <-- f2 * c */ - /* f2 <-- p - f2 */ - uECC_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p, num_words_secp224r1); - uECC_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */ -} - -/* Routine 3.2.7 RP; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rp(uECC_word_t *d1, - uECC_word_t *e1, - uECC_word_t *f1, - const uECC_word_t *c, - const uECC_word_t *r) { - wordcount_t i; - wordcount_t pow2i = 1; - uECC_word_t d0[num_words_secp224r1]; - uECC_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */ - uECC_word_t f0[num_words_secp224r1]; - - uECC_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */ - /* f0 <-- p - c */ - uECC_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p, num_words_secp224r1); - for (i = 0; i <= 6; i++) { - mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0, pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */ - mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0, e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */ - uECC_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ - uECC_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ - uECC_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ - pow2i *= 2; - } -} - -/* Compute a = sqrt(a) (mod curve_p). */ -/* Routine 3.2.8 mp_mod_sqrt_224; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1(uECC_word_t *a, uECC_Curve curve) { - (void)curve; - bitcount_t i; - uECC_word_t e1[num_words_secp224r1]; - uECC_word_t f1[num_words_secp224r1]; - uECC_word_t d0[num_words_secp224r1]; - uECC_word_t e0[num_words_secp224r1]; - uECC_word_t f0[num_words_secp224r1]; - uECC_word_t d1[num_words_secp224r1]; - - /* s = a; using constant instead of random value */ - mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c, s) */ - mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0, f0); /* RS (d1, e1, f1, d0, e0, f0) */ - for (i = 1; i <= 95; i++) { - uECC_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ - uECC_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ - uECC_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ - mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0, f0); /* RS (d1, e1, f1, d0, e0, f0) */ - if (uECC_vli_isZero(d1, num_words_secp224r1)) { /* if d1 == 0 */ - break; - } - } - uECC_vli_modInv(f1, e0, curve_secp224r1.p, num_words_secp224r1); /* f1 <-- 1 / e0 */ - uECC_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <-- d0 / e0 */ -} -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -#if (uECC_OPTIMIZATION_LEVEL > 0) -/* Computes result = product % curve_p - from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -#if uECC_WORD_SIZE == 1 -static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) { - uint8_t tmp[num_words_secp224r1]; - int8_t carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp224r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; - tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0; - tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0; - tmp[12] = product[28]; tmp[13] = product[29]; tmp[14] = product[30]; tmp[15] = product[31]; - tmp[16] = product[32]; tmp[17] = product[33]; tmp[18] = product[34]; tmp[19] = product[35]; - tmp[20] = product[36]; tmp[21] = product[37]; tmp[22] = product[38]; tmp[23] = product[39]; - tmp[24] = product[40]; tmp[25] = product[41]; tmp[26] = product[42]; tmp[27] = product[43]; - carry = uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* s2 */ - tmp[12] = product[44]; tmp[13] = product[45]; tmp[14] = product[46]; tmp[15] = product[47]; - tmp[16] = product[48]; tmp[17] = product[49]; tmp[18] = product[50]; tmp[19] = product[51]; - tmp[20] = product[52]; tmp[21] = product[53]; tmp[22] = product[54]; tmp[23] = product[55]; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* d1 */ - tmp[0] = product[28]; tmp[1] = product[29]; tmp[2] = product[30]; tmp[3] = product[31]; - tmp[4] = product[32]; tmp[5] = product[33]; tmp[6] = product[34]; tmp[7] = product[35]; - tmp[8] = product[36]; tmp[9] = product[37]; tmp[10] = product[38]; tmp[11] = product[39]; - tmp[12] = product[40]; tmp[13] = product[41]; tmp[14] = product[42]; tmp[15] = product[43]; - tmp[16] = product[44]; tmp[17] = product[45]; tmp[18] = product[46]; tmp[19] = product[47]; - tmp[20] = product[48]; tmp[21] = product[49]; tmp[22] = product[50]; tmp[23] = product[51]; - tmp[24] = product[52]; tmp[25] = product[53]; tmp[26] = product[54]; tmp[27] = product[55]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - /* d2 */ - tmp[0] = product[44]; tmp[1] = product[45]; tmp[2] = product[46]; tmp[3] = product[47]; - tmp[4] = product[48]; tmp[5] = product[49]; tmp[6] = product[50]; tmp[7] = product[51]; - tmp[8] = product[52]; tmp[9] = product[53]; tmp[10] = product[54]; tmp[11] = product[55]; - tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp224r1.p, result, num_words_secp224r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); - } - } -} -#elif uECC_WORD_SIZE == 4 -static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t *product) -{ - uint32_t tmp[num_words_secp224r1]; - int carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp224r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = 0; - tmp[3] = product[7]; - tmp[4] = product[8]; - tmp[5] = product[9]; - tmp[6] = product[10]; - carry = uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* s2 */ - tmp[3] = product[11]; - tmp[4] = product[12]; - tmp[5] = product[13]; - tmp[6] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* d1 */ - tmp[0] = product[7]; - tmp[1] = product[8]; - tmp[2] = product[9]; - tmp[3] = product[10]; - tmp[4] = product[11]; - tmp[5] = product[12]; - tmp[6] = product[13]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - /* d2 */ - tmp[0] = product[11]; - tmp[1] = product[12]; - tmp[2] = product[13]; - tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp224r1.p, result, num_words_secp224r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); - } - } -} -#else -static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t *product) -{ - uint64_t tmp[num_words_secp224r1]; - int carry = 0; - - /* t */ - uECC_vli_set(result, product, num_words_secp224r1); - result[num_words_secp224r1 - 1] &= 0xffffffff; - - /* s1 */ - tmp[0] = 0; - tmp[1] = product[3] & 0xffffffff00000000ull; - tmp[2] = product[4]; - tmp[3] = product[5] & 0xffffffff; - uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* s2 */ - tmp[1] = product[5] & 0xffffffff00000000ull; - tmp[2] = product[6]; - tmp[3] = 0; - uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* d1 */ - tmp[0] = (product[3] >> 32) | (product[4] << 32); - tmp[1] = (product[4] >> 32) | (product[5] << 32); - tmp[2] = (product[5] >> 32) | (product[6] << 32); - tmp[3] = product[6] >> 32; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - /* d2 */ - tmp[0] = (product[5] >> 32) | (product[6] << 32); - tmp[1] = product[6] >> 32; - tmp[2] = tmp[3] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); - } while (carry < 0); - } else { - while (uECC_vli_cmp_unsafe(curve_secp224r1.p, result, num_words_secp224r1) != 1) { - uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); - } - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0) */ - -#endif /* uECC_SUPPORTS_secp224r1 */ - -#if uECC_SUPPORTS_secp256r1 - -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp256r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp256r1 = { - num_words_secp256r1, - num_bytes_secp256r1, - 256, /* num_n_bits */ - { BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3), - BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4), - BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77), - BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8), - BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B), - - BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB), - BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B), - BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E), - BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F) }, - { BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B), - BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65), - BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3), - BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp256r1 -#endif -}; - -uECC_Curve uECC_secp256r1(void) { return &curve_secp256r1; } - - -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) -/* Computes result = product % curve_p - from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -#if uECC_WORD_SIZE == 1 -static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { - uint8_t tmp[num_words_secp256r1]; - int8_t carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp256r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; - tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0; - tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0; - tmp[12] = product[44]; tmp[13] = product[45]; tmp[14] = product[46]; tmp[15] = product[47]; - tmp[16] = product[48]; tmp[17] = product[49]; tmp[18] = product[50]; tmp[19] = product[51]; - tmp[20] = product[52]; tmp[21] = product[53]; tmp[22] = product[54]; tmp[23] = product[55]; - tmp[24] = product[56]; tmp[25] = product[57]; tmp[26] = product[58]; tmp[27] = product[59]; - tmp[28] = product[60]; tmp[29] = product[61]; tmp[30] = product[62]; tmp[31] = product[63]; - carry = uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s2 */ - tmp[12] = product[48]; tmp[13] = product[49]; tmp[14] = product[50]; tmp[15] = product[51]; - tmp[16] = product[52]; tmp[17] = product[53]; tmp[18] = product[54]; tmp[19] = product[55]; - tmp[20] = product[56]; tmp[21] = product[57]; tmp[22] = product[58]; tmp[23] = product[59]; - tmp[24] = product[60]; tmp[25] = product[61]; tmp[26] = product[62]; tmp[27] = product[63]; - tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0; - carry += uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s3 */ - tmp[0] = product[32]; tmp[1] = product[33]; tmp[2] = product[34]; tmp[3] = product[35]; - tmp[4] = product[36]; tmp[5] = product[37]; tmp[6] = product[38]; tmp[7] = product[39]; - tmp[8] = product[40]; tmp[9] = product[41]; tmp[10] = product[42]; tmp[11] = product[43]; - tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = product[56]; tmp[25] = product[57]; tmp[26] = product[58]; tmp[27] = product[59]; - tmp[28] = product[60]; tmp[29] = product[61]; tmp[30] = product[62]; tmp[31] = product[63]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s4 */ - tmp[0] = product[36]; tmp[1] = product[37]; tmp[2] = product[38]; tmp[3] = product[39]; - tmp[4] = product[40]; tmp[5] = product[41]; tmp[6] = product[42]; tmp[7] = product[43]; - tmp[8] = product[44]; tmp[9] = product[45]; tmp[10] = product[46]; tmp[11] = product[47]; - tmp[12] = product[52]; tmp[13] = product[53]; tmp[14] = product[54]; tmp[15] = product[55]; - tmp[16] = product[56]; tmp[17] = product[57]; tmp[18] = product[58]; tmp[19] = product[59]; - tmp[20] = product[60]; tmp[21] = product[61]; tmp[22] = product[62]; tmp[23] = product[63]; - tmp[24] = product[52]; tmp[25] = product[53]; tmp[26] = product[54]; tmp[27] = product[55]; - tmp[28] = product[32]; tmp[29] = product[33]; tmp[30] = product[34]; tmp[31] = product[35]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* d1 */ - tmp[0] = product[44]; tmp[1] = product[45]; tmp[2] = product[46]; tmp[3] = product[47]; - tmp[4] = product[48]; tmp[5] = product[49]; tmp[6] = product[50]; tmp[7] = product[51]; - tmp[8] = product[52]; tmp[9] = product[53]; tmp[10] = product[54]; tmp[11] = product[55]; - tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = product[32]; tmp[25] = product[33]; tmp[26] = product[34]; tmp[27] = product[35]; - tmp[28] = product[40]; tmp[29] = product[41]; tmp[30] = product[42]; tmp[31] = product[43]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d2 */ - tmp[0] = product[48]; tmp[1] = product[49]; tmp[2] = product[50]; tmp[3] = product[51]; - tmp[4] = product[52]; tmp[5] = product[53]; tmp[6] = product[54]; tmp[7] = product[55]; - tmp[8] = product[56]; tmp[9] = product[57]; tmp[10] = product[58]; tmp[11] = product[59]; - tmp[12] = product[60]; tmp[13] = product[61]; tmp[14] = product[62]; tmp[15] = product[63]; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = product[36]; tmp[25] = product[37]; tmp[26] = product[38]; tmp[27] = product[39]; - tmp[28] = product[44]; tmp[29] = product[45]; tmp[30] = product[46]; tmp[31] = product[47]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d3 */ - tmp[0] = product[52]; tmp[1] = product[53]; tmp[2] = product[54]; tmp[3] = product[55]; - tmp[4] = product[56]; tmp[5] = product[57]; tmp[6] = product[58]; tmp[7] = product[59]; - tmp[8] = product[60]; tmp[9] = product[61]; tmp[10] = product[62]; tmp[11] = product[63]; - tmp[12] = product[32]; tmp[13] = product[33]; tmp[14] = product[34]; tmp[15] = product[35]; - tmp[16] = product[36]; tmp[17] = product[37]; tmp[18] = product[38]; tmp[19] = product[39]; - tmp[20] = product[40]; tmp[21] = product[41]; tmp[22] = product[42]; tmp[23] = product[43]; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - tmp[28] = product[48]; tmp[29] = product[49]; tmp[30] = product[50]; tmp[31] = product[51]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d4 */ - tmp[0] = product[56]; tmp[1] = product[57]; tmp[2] = product[58]; tmp[3] = product[59]; - tmp[4] = product[60]; tmp[5] = product[61]; tmp[6] = product[62]; tmp[7] = product[63]; - tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0; - tmp[12] = product[36]; tmp[13] = product[37]; tmp[14] = product[38]; tmp[15] = product[39]; - tmp[16] = product[40]; tmp[17] = product[41]; tmp[18] = product[42]; tmp[19] = product[43]; - tmp[20] = product[44]; tmp[21] = product[45]; tmp[22] = product[46]; tmp[23] = product[47]; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - tmp[28] = product[52]; tmp[29] = product[53]; tmp[30] = product[54]; tmp[31] = product[55]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, num_words_secp256r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); - } - } -} -#elif uECC_WORD_SIZE == 4 -static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { - uint32_t tmp[num_words_secp256r1]; - int carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp256r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = 0; - tmp[3] = product[11]; - tmp[4] = product[12]; - tmp[5] = product[13]; - tmp[6] = product[14]; - tmp[7] = product[15]; - carry = uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s2 */ - tmp[3] = product[12]; - tmp[4] = product[13]; - tmp[5] = product[14]; - tmp[6] = product[15]; - tmp[7] = 0; - carry += uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s3 */ - tmp[0] = product[8]; - tmp[1] = product[9]; - tmp[2] = product[10]; - tmp[3] = tmp[4] = tmp[5] = 0; - tmp[6] = product[14]; - tmp[7] = product[15]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s4 */ - tmp[0] = product[9]; - tmp[1] = product[10]; - tmp[2] = product[11]; - tmp[3] = product[13]; - tmp[4] = product[14]; - tmp[5] = product[15]; - tmp[6] = product[13]; - tmp[7] = product[8]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* d1 */ - tmp[0] = product[11]; - tmp[1] = product[12]; - tmp[2] = product[13]; - tmp[3] = tmp[4] = tmp[5] = 0; - tmp[6] = product[8]; - tmp[7] = product[10]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d2 */ - tmp[0] = product[12]; - tmp[1] = product[13]; - tmp[2] = product[14]; - tmp[3] = product[15]; - tmp[4] = tmp[5] = 0; - tmp[6] = product[9]; - tmp[7] = product[11]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d3 */ - tmp[0] = product[13]; - tmp[1] = product[14]; - tmp[2] = product[15]; - tmp[3] = product[8]; - tmp[4] = product[9]; - tmp[5] = product[10]; - tmp[6] = 0; - tmp[7] = product[12]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d4 */ - tmp[0] = product[14]; - tmp[1] = product[15]; - tmp[2] = 0; - tmp[3] = product[9]; - tmp[4] = product[10]; - tmp[5] = product[11]; - tmp[6] = 0; - tmp[7] = product[13]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, num_words_secp256r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); - } - } -} -#else -static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t *product) { - uint64_t tmp[num_words_secp256r1]; - int carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp256r1); - - /* s1 */ - tmp[0] = 0; - tmp[1] = product[5] & 0xffffffff00000000ull; - tmp[2] = product[6]; - tmp[3] = product[7]; - carry = (int)uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s2 */ - tmp[1] = product[6] << 32; - tmp[2] = (product[6] >> 32) | (product[7] << 32); - tmp[3] = product[7] >> 32; - carry += uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s3 */ - tmp[0] = product[4]; - tmp[1] = product[5] & 0xffffffff; - tmp[2] = 0; - tmp[3] = product[7]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s4 */ - tmp[0] = (product[4] >> 32) | (product[5] << 32); - tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000ull); - tmp[2] = product[7]; - tmp[3] = (product[6] >> 32) | (product[4] << 32); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* d1 */ - tmp[0] = (product[5] >> 32) | (product[6] << 32); - tmp[1] = (product[6] >> 32); - tmp[2] = 0; - tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32); - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d2 */ - tmp[0] = product[6]; - tmp[1] = product[7]; - tmp[2] = 0; - tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000ull); - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d3 */ - tmp[0] = (product[6] >> 32) | (product[7] << 32); - tmp[1] = (product[7] >> 32) | (product[4] << 32); - tmp[2] = (product[4] >> 32) | (product[5] << 32); - tmp[3] = (product[6] << 32); - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d4 */ - tmp[0] = product[7]; - tmp[1] = product[4] & 0xffffffff00000000ull; - tmp[2] = product[5]; - tmp[3] = product[6] & 0xffffffff00000000ull; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, num_words_secp256r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); - } - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) */ - -#endif /* uECC_SUPPORTS_secp256r1 */ - -#if uECC_SUPPORTS_secp256k1 - -static void double_jacobian_secp256k1(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve); -static void x_side_secp256k1(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve); -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp256k1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp256k1 = { - num_words_secp256k1, - num_bytes_secp256k1, - 256, /* num_n_bits */ - { BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF), - BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA), - BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59), - BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02), - BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55), - BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79), - - BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C), - BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD), - BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D), - BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48) }, - { BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00) }, - &double_jacobian_secp256k1, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_secp256k1, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp256k1 -#endif -}; - -uECC_Curve uECC_secp256k1(void) { return &curve_secp256k1; } - - -/* Double in place */ -static void double_jacobian_secp256k1(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve) { - /* t1 = X, t2 = Y, t3 = Z */ - uECC_word_t t4[num_words_secp256k1]; - uECC_word_t t5[num_words_secp256k1]; - - if (uECC_vli_isZero(Z1, num_words_secp256k1)) { - return; - } - - uECC_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */ - uECC_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 = A */ - uECC_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */ - uECC_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */ - - uECC_vli_modAdd(Y1, X1, X1, curve->p, num_words_secp256k1); /* t2 = 2*x1^2 */ - uECC_vli_modAdd(Y1, Y1, X1, curve->p, num_words_secp256k1); /* t2 = 3*x1^2 */ - if (uECC_vli_testBit(Y1, 0)) { - uECC_word_t carry = uECC_vli_add(Y1, Y1, curve->p, num_words_secp256k1); - uECC_vli_rshift1(Y1, num_words_secp256k1); - Y1[num_words_secp256k1 - 1] |= carry << (uECC_WORD_BITS - 1); - } else { - uECC_vli_rshift1(Y1, num_words_secp256k1); - } - /* t2 = 3/2*(x1^2) = B */ - - uECC_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */ - uECC_vli_modSub(X1, X1, t4, curve->p, num_words_secp256k1); /* t1 = B^2 - A */ - uECC_vli_modSub(X1, X1, t4, curve->p, num_words_secp256k1); /* t1 = B^2 - 2A = x3 */ - - uECC_vli_modSub(t4, t4, X1, curve->p, num_words_secp256k1); /* t4 = A - x3 */ - uECC_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A - x3) */ - uECC_vli_modSub(Y1, Y1, t5, curve->p, num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */ -} - -/* Computes result = x^3 + b. result must not overlap x. */ -static void x_side_secp256k1(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve) { - uECC_vli_modSquare_fast(result, x, curve); /* r = x^2 */ - uECC_vli_modMult_fast(result, result, x, curve); /* r = x^3 */ - uECC_vli_modAdd(result, result, curve->b, curve->p, num_words_secp256k1); /* r = x^3 + b */ -} - -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1) -static void omega_mult_secp256k1(uECC_word_t *result, const uECC_word_t *right); -static void vli_mmod_fast_secp256k1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp256k1]; - uECC_word_t carry; - - uECC_vli_clear(tmp, num_words_secp256k1); - uECC_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1); - - omega_mult_secp256k1(tmp, product + num_words_secp256k1); /* (Rq, q) = q * c */ - - carry = uECC_vli_add(result, product, tmp, num_words_secp256k1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp256k1); - omega_mult_secp256k1(product, tmp + num_words_secp256k1); /* Rq*c */ - carry += uECC_vli_add(result, result, product, num_words_secp256k1); /* (C1, r) = r + Rq*c */ - - while (carry > 0) { - --carry; - uECC_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); - } - if (uECC_vli_cmp_unsafe(result, curve_secp256k1.p, num_words_secp256k1) > 0) { - uECC_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); - } -} - -#if uECC_WORD_SIZE == 1 -static void omega_mult_secp256k1(uint8_t * result, const uint8_t * right) { - /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - wordcount_t k; - - /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - muladd(0xD1, right[0], &r0, &r1, &r2); - result[0] = r0; - r0 = r1; - r1 = r2; - /* r2 is still 0 */ - - for (k = 1; k < num_words_secp256k1; ++k) { - muladd(0x03, right[k - 1], &r0, &r1, &r2); - muladd(0xD1, right[k], &r0, &r1, &r2); - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2); - result[num_words_secp256k1] = r0; - result[num_words_secp256k1 + 1] = r1; - /* add the 2^32 multiple */ - result[4 + num_words_secp256k1] = - uECC_vli_add(result + 4, result + 4, right, num_words_secp256k1); -} -#elif uECC_WORD_SIZE == 4 -static void omega_mult_secp256k1(uint32_t * result, const uint32_t * right) { - /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - uint32_t carry = 0; - wordcount_t k; - - for (k = 0; k < num_words_secp256k1; ++k) { - uint64_t p = (uint64_t)0x3D1 * right[k] + carry; - result[k] = (uint32_t) p; - carry = p >> 32; - } - result[num_words_secp256k1] = carry; - /* add the 2^32 multiple */ - result[1 + num_words_secp256k1] = - uECC_vli_add(result + 1, result + 1, right, num_words_secp256k1); -} -#else -static void omega_mult_secp256k1(uint64_t * result, const uint64_t * right) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - wordcount_t k; - - /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - for (k = 0; k < num_words_secp256k1; ++k) { - muladd(0x1000003D1ull, right[k], &r0, &r1, &r2); - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - result[num_words_secp256k1] = r0; -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && && !asm_mmod_fast_secp256k1) */ - -#endif /* uECC_SUPPORTS_secp256k1 */ - -#endif /* _UECC_CURVE_SPECIFIC_H_ */ diff --git a/lib/micro-ecc/platform-specific.inc b/lib/micro-ecc/platform-specific.inc deleted file mode 100644 index 7e0373f505..0000000000 --- a/lib/micro-ecc/platform-specific.inc +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_PLATFORM_SPECIFIC_H_ -#define _UECC_PLATFORM_SPECIFIC_H_ - -#include "types.h" - -#if (defined(_WIN32) || defined(_WIN64)) -/* Windows */ - -// use pragma syntax to prevent tweaking the linker script for getting CryptXYZ function -#pragma comment(lib, "crypt32.lib") -#pragma comment(lib, "advapi32.lib") - -#define WIN32_LEAN_AND_MEAN -#include -#include - -static int default_RNG(uint8_t *dest, unsigned size) { - HCRYPTPROV prov; - if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - return 0; - } - - CryptGenRandom(prov, size, (BYTE *)dest); - CryptReleaseContext(prov, 0); - return 1; -} -#define default_RNG_defined 1 - -#elif defined(unix) || defined(__linux__) || defined(__unix__) || defined(__unix) || \ - (defined(__APPLE__) && defined(__MACH__)) || defined(uECC_POSIX) - -/* Some POSIX-like system with /dev/urandom or /dev/random. */ -#include -#include -#include - -#ifndef O_CLOEXEC - #define O_CLOEXEC 0 -#endif - -static int default_RNG(uint8_t *dest, unsigned size) { - int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); - if (fd == -1) { - fd = open("/dev/random", O_RDONLY | O_CLOEXEC); - if (fd == -1) { - return 0; - } - } - - char *ptr = (char *)dest; - size_t left = size; - while (left > 0) { - ssize_t bytes_read = read(fd, ptr, left); - if (bytes_read <= 0) { // read failed - close(fd); - return 0; - } - left -= bytes_read; - ptr += bytes_read; - } - - close(fd); - return 1; -} -#define default_RNG_defined 1 - -#elif defined(RIOT_VERSION) - -#include - -static int default_RNG(uint8_t *dest, unsigned size) { - random_bytes(dest, size); - return 1; -} -#define default_RNG_defined 1 - -#elif defined(NRF52_SERIES) - -#include "app_error.h" -#include "nrf_crypto_rng.h" - -static int default_RNG(uint8_t *dest, unsigned size) -{ - // make sure to call nrf_crypto_init and nrf_crypto_rng_init first - ret_code_t ret_code = nrf_crypto_rng_vector_generate(dest, size); - return (ret_code == NRF_SUCCESS) ? 1 : 0; -} -#define default_RNG_defined 1 - -#endif /* platform */ - -#endif /* _UECC_PLATFORM_SPECIFIC_H_ */ diff --git a/lib/micro-ecc/types.h b/lib/micro-ecc/types.h deleted file mode 100644 index 9ee81438fa..0000000000 --- a/lib/micro-ecc/types.h +++ /dev/null @@ -1,108 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_TYPES_H_ -#define _UECC_TYPES_H_ - -#ifndef uECC_PLATFORM - #if __AVR__ - #define uECC_PLATFORM uECC_avr - #elif defined(__thumb2__) || defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */ - #define uECC_PLATFORM uECC_arm_thumb2 - #elif defined(__thumb__) - #define uECC_PLATFORM uECC_arm_thumb - #elif defined(__arm__) || defined(_M_ARM) - #define uECC_PLATFORM uECC_arm - #elif defined(__aarch64__) - #define uECC_PLATFORM uECC_arm64 - #elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || defined(__I86__) - #define uECC_PLATFORM uECC_x86 - #elif defined(__amd64__) || defined(_M_X64) - #define uECC_PLATFORM uECC_x86_64 - #else - #define uECC_PLATFORM uECC_arch_other - #endif -#endif - -#ifndef uECC_ARM_USE_UMAAL - #if (uECC_PLATFORM == uECC_arm) && (__ARM_ARCH >= 6) - #define uECC_ARM_USE_UMAAL 1 - #elif (uECC_PLATFORM == uECC_arm_thumb2) && (__ARM_ARCH >= 6) && !__ARM_ARCH_7M__ - #define uECC_ARM_USE_UMAAL 1 - #else - #define uECC_ARM_USE_UMAAL 0 - #endif -#endif - -#ifndef uECC_WORD_SIZE - #if uECC_PLATFORM == uECC_avr - #define uECC_WORD_SIZE 1 - #elif (uECC_PLATFORM == uECC_x86_64 || uECC_PLATFORM == uECC_arm64) - #define uECC_WORD_SIZE 8 - #else - #define uECC_WORD_SIZE 4 - #endif -#endif - -#if (uECC_WORD_SIZE != 1) && (uECC_WORD_SIZE != 4) && (uECC_WORD_SIZE != 8) - #error "Unsupported value for uECC_WORD_SIZE" -#endif - -#if ((uECC_PLATFORM == uECC_avr) && (uECC_WORD_SIZE != 1)) - #pragma message ("uECC_WORD_SIZE must be 1 for AVR") - #undef uECC_WORD_SIZE - #define uECC_WORD_SIZE 1 -#endif - -#if ((uECC_PLATFORM == uECC_arm || uECC_PLATFORM == uECC_arm_thumb || \ - uECC_PLATFORM == uECC_arm_thumb2) && \ - (uECC_WORD_SIZE != 4)) - #pragma message ("uECC_WORD_SIZE must be 4 for ARM") - #undef uECC_WORD_SIZE - #define uECC_WORD_SIZE 4 -#endif - -#if defined(__SIZEOF_INT128__) || ((__clang_major__ * 100 + __clang_minor__) >= 302) - #define SUPPORTS_INT128 1 -#else - #define SUPPORTS_INT128 0 -#endif - -typedef int8_t wordcount_t; -typedef int16_t bitcount_t; -typedef int8_t cmpresult_t; - -#if (uECC_WORD_SIZE == 1) - -typedef uint8_t uECC_word_t; -typedef uint16_t uECC_dword_t; - -#define HIGH_BIT_SET 0x80 -#define uECC_WORD_BITS 8 -#define uECC_WORD_BITS_SHIFT 3 -#define uECC_WORD_BITS_MASK 0x07 - -#elif (uECC_WORD_SIZE == 4) - -typedef uint32_t uECC_word_t; -typedef uint64_t uECC_dword_t; - -#define HIGH_BIT_SET 0x80000000 -#define uECC_WORD_BITS 32 -#define uECC_WORD_BITS_SHIFT 5 -#define uECC_WORD_BITS_MASK 0x01F - -#elif (uECC_WORD_SIZE == 8) - -typedef uint64_t uECC_word_t; -#if SUPPORTS_INT128 -typedef unsigned __int128 uECC_dword_t; -#endif - -#define HIGH_BIT_SET 0x8000000000000000ull -#define uECC_WORD_BITS 64 -#define uECC_WORD_BITS_SHIFT 6 -#define uECC_WORD_BITS_MASK 0x03F - -#endif /* uECC_WORD_SIZE */ - -#endif /* _UECC_TYPES_H_ */ diff --git a/lib/micro-ecc/uECC.c b/lib/micro-ecc/uECC.c deleted file mode 100644 index a3d502cf21..0000000000 --- a/lib/micro-ecc/uECC.c +++ /dev/null @@ -1,1669 +0,0 @@ -/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#include "uECC.h" -#include "uECC_vli.h" - -#ifndef uECC_RNG_MAX_TRIES - #define uECC_RNG_MAX_TRIES 64 -#endif - -#if uECC_ENABLE_VLI_API - #define uECC_VLI_API -#else - #define uECC_VLI_API static -#endif - -#if (uECC_PLATFORM == uECC_avr) || \ - (uECC_PLATFORM == uECC_arm) || \ - (uECC_PLATFORM == uECC_arm_thumb) || \ - (uECC_PLATFORM == uECC_arm_thumb2) - #define CONCATX(a, ...) a ## __VA_ARGS__ - #define CONCAT(a, ...) CONCATX(a, __VA_ARGS__) - - #define STRX(a) #a - #define STR(a) STRX(a) - - #define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__)))) - #define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__)))) - #define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__)))) - #define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__)))) - #define EVAL4(...) __VA_ARGS__ - - #define DEC_1 0 - #define DEC_2 1 - #define DEC_3 2 - #define DEC_4 3 - #define DEC_5 4 - #define DEC_6 5 - #define DEC_7 6 - #define DEC_8 7 - #define DEC_9 8 - #define DEC_10 9 - #define DEC_11 10 - #define DEC_12 11 - #define DEC_13 12 - #define DEC_14 13 - #define DEC_15 14 - #define DEC_16 15 - #define DEC_17 16 - #define DEC_18 17 - #define DEC_19 18 - #define DEC_20 19 - #define DEC_21 20 - #define DEC_22 21 - #define DEC_23 22 - #define DEC_24 23 - #define DEC_25 24 - #define DEC_26 25 - #define DEC_27 26 - #define DEC_28 27 - #define DEC_29 28 - #define DEC_30 29 - #define DEC_31 30 - #define DEC_32 31 - - #define DEC(N) CONCAT(DEC_, N) - - #define SECOND_ARG(_, val, ...) val - #define SOME_CHECK_0 ~, 0 - #define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME,) - #define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N)) - - #define EMPTY(...) - #define DEFER(...) __VA_ARGS__ EMPTY() - - #define REPEAT_NAME_0() REPEAT_0 - #define REPEAT_NAME_SOME() REPEAT_SOME - #define REPEAT_0(...) - #define REPEAT_SOME(N, stuff) DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff) stuff - #define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff)) - - #define REPEATM_NAME_0() REPEATM_0 - #define REPEATM_NAME_SOME() REPEATM_SOME - #define REPEATM_0(...) - #define REPEATM_SOME(N, macro) macro(N) \ - DEFER(CONCAT(REPEATM_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), macro) - #define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro)) -#endif - -#include "platform-specific.inc" - -#if (uECC_WORD_SIZE == 1) - #if uECC_SUPPORTS_secp160r1 - #define uECC_MAX_WORDS 21 /* Due to the size of curve_n. */ - #endif - #if uECC_SUPPORTS_secp192r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 24 - #endif - #if uECC_SUPPORTS_secp224r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 28 - #endif - #if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 32 - #endif -#elif (uECC_WORD_SIZE == 4) - #if uECC_SUPPORTS_secp160r1 - #define uECC_MAX_WORDS 6 /* Due to the size of curve_n. */ - #endif - #if uECC_SUPPORTS_secp192r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 6 - #endif - #if uECC_SUPPORTS_secp224r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 7 - #endif - #if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 8 - #endif -#elif (uECC_WORD_SIZE == 8) - #if uECC_SUPPORTS_secp160r1 - #define uECC_MAX_WORDS 3 - #endif - #if uECC_SUPPORTS_secp192r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 3 - #endif - #if uECC_SUPPORTS_secp224r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 4 - #endif - #if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 4 - #endif -#endif /* uECC_WORD_SIZE */ - -#define BITS_TO_WORDS(num_bits) ((num_bits + ((uECC_WORD_SIZE * 8) - 1)) / (uECC_WORD_SIZE * 8)) -#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8) - -struct uECC_Curve_t { - wordcount_t num_words; - wordcount_t num_bytes; - bitcount_t num_n_bits; - uECC_word_t p[uECC_MAX_WORDS]; - uECC_word_t n[uECC_MAX_WORDS]; - uECC_word_t G[uECC_MAX_WORDS * 2]; - uECC_word_t b[uECC_MAX_WORDS]; - void (*double_jacobian)(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve); -#if uECC_SUPPORT_COMPRESSED_POINT - void (*mod_sqrt)(uECC_word_t *a, uECC_Curve curve); -#endif - void (*x_side)(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve); -#if (uECC_OPTIMIZATION_LEVEL > 0) - void (*mmod_fast)(uECC_word_t *result, uECC_word_t *product); -#endif -}; - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN -static void bcopy(uint8_t *dst, - const uint8_t *src, - unsigned num_bytes) { - while (0 != num_bytes) { - num_bytes--; - dst[num_bytes] = src[num_bytes]; - } -} -#endif - -static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -#if (uECC_PLATFORM == uECC_arm || uECC_PLATFORM == uECC_arm_thumb || \ - uECC_PLATFORM == uECC_arm_thumb2) - #include "asm_arm.inc" -#endif - -#if (uECC_PLATFORM == uECC_avr) - #include "asm_avr.inc" -#endif - -#if default_RNG_defined -static uECC_RNG_Function g_rng_function = &default_RNG; -#else -static uECC_RNG_Function g_rng_function = 0; -#endif - -void uECC_set_rng(uECC_RNG_Function rng_function) { - g_rng_function = rng_function; -} - -uECC_RNG_Function uECC_get_rng(void) { - return g_rng_function; -} - -int uECC_curve_private_key_size(uECC_Curve curve) { - return BITS_TO_BYTES(curve->num_n_bits); -} - -int uECC_curve_public_key_size(uECC_Curve curve) { - return 2 * curve->num_bytes; -} - -#if !asm_clear -uECC_VLI_API void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words) { - wordcount_t i; - for (i = 0; i < num_words; ++i) { - vli[i] = 0; - } -} -#endif /* !asm_clear */ - -/* Constant-time comparison to zero - secure way to compare long integers */ -/* Returns 1 if vli == 0, 0 otherwise. */ -uECC_VLI_API uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, wordcount_t num_words) { - uECC_word_t bits = 0; - wordcount_t i; - for (i = 0; i < num_words; ++i) { - bits |= vli[i]; - } - return (bits == 0); -} - -/* Returns nonzero if bit 'bit' of vli is set. */ -uECC_VLI_API uECC_word_t uECC_vli_testBit(const uECC_word_t *vli, bitcount_t bit) { - return (vli[bit >> uECC_WORD_BITS_SHIFT] & ((uECC_word_t)1 << (bit & uECC_WORD_BITS_MASK))); -} - -/* Counts the number of words in vli. */ -static wordcount_t vli_numDigits(const uECC_word_t *vli, const wordcount_t max_words) { - wordcount_t i; - /* Search from the end until we find a non-zero digit. - We do it in reverse because we expect that most digits will be nonzero. */ - for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) { - } - - return (i + 1); -} - -/* Counts the number of bits required to represent vli. */ -uECC_VLI_API bitcount_t uECC_vli_numBits(const uECC_word_t *vli, const wordcount_t max_words) { - uECC_word_t i; - uECC_word_t digit; - - wordcount_t num_digits = vli_numDigits(vli, max_words); - if (num_digits == 0) { - return 0; - } - - digit = vli[num_digits - 1]; - for (i = 0; digit; ++i) { - digit >>= 1; - } - - return (((bitcount_t)(num_digits - 1) << uECC_WORD_BITS_SHIFT) + i); -} - -/* Sets dest = src. */ -#if !asm_set -uECC_VLI_API void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, wordcount_t num_words) { - wordcount_t i; - for (i = 0; i < num_words; ++i) { - dest[i] = src[i]; - } -} -#endif /* !asm_set */ - -/* Returns sign of left - right. */ -static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - wordcount_t i; - for (i = num_words - 1; i >= 0; --i) { - if (left[i] > right[i]) { - return 1; - } else if (left[i] < right[i]) { - return -1; - } - } - return 0; -} - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns one if left == right, zero otherwise. */ -uECC_VLI_API uECC_word_t uECC_vli_equal(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t diff = 0; - wordcount_t i; - for (i = num_words - 1; i >= 0; --i) { - diff |= (left[i] ^ right[i]); - } - return (diff == 0); -} - -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Returns sign of left - right, in constant time. */ -uECC_VLI_API cmpresult_t uECC_vli_cmp(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t neg = !!uECC_vli_sub(tmp, left, right, num_words); - uECC_word_t equal = uECC_vli_isZero(tmp, num_words); - return (!equal - 2 * neg); -} - -/* Computes vli = vli >> 1. */ -#if !asm_rshift1 -uECC_VLI_API void uECC_vli_rshift1(uECC_word_t *vli, wordcount_t num_words) { - uECC_word_t *end = vli; - uECC_word_t carry = 0; - - vli += num_words; - while (vli-- > end) { - uECC_word_t temp = *vli; - *vli = (temp >> 1) | carry; - carry = temp << (uECC_WORD_BITS - 1); - } -} -#endif /* !asm_rshift1 */ - -/* Computes result = left + right, returning carry. Can modify in place. */ -#if !asm_add -uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t carry = 0; - wordcount_t i; - for (i = 0; i < num_words; ++i) { - uECC_word_t sum = left[i] + right[i] + carry; - if (sum != left[i]) { - carry = (sum < left[i]); - } - result[i] = sum; - } - return carry; -} -#endif /* !asm_add */ - -/* Computes result = left - right, returning borrow. Can modify in place. */ -#if !asm_sub -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t borrow = 0; - wordcount_t i; - for (i = 0; i < num_words; ++i) { - uECC_word_t diff = left[i] - right[i] - borrow; - if (diff != left[i]) { - borrow = (diff > left[i]); - } - result[i] = diff; - } - return borrow; -} -#endif /* !asm_sub */ - -#if !asm_mult || (uECC_SQUARE_FUNC && !asm_square) || \ - (uECC_SUPPORTS_secp256k1 && (uECC_OPTIMIZATION_LEVEL > 0) && \ - ((uECC_WORD_SIZE == 1) || (uECC_WORD_SIZE == 8))) -static void muladd(uECC_word_t a, - uECC_word_t b, - uECC_word_t *r0, - uECC_word_t *r1, - uECC_word_t *r2) { -#if uECC_WORD_SIZE == 8 && !SUPPORTS_INT128 - uint64_t a0 = a & 0xffffffffull; - uint64_t a1 = a >> 32; - uint64_t b0 = b & 0xffffffffull; - uint64_t b1 = b >> 32; - - uint64_t i0 = a0 * b0; - uint64_t i1 = a0 * b1; - uint64_t i2 = a1 * b0; - uint64_t i3 = a1 * b1; - - uint64_t p0, p1; - - i2 += (i0 >> 32); - i2 += i1; - if (i2 < i1) { /* overflow */ - i3 += 0x100000000ull; - } - - p0 = (i0 & 0xffffffffull) | (i2 << 32); - p1 = i3 + (i2 >> 32); - - *r0 += p0; - *r1 += (p1 + (*r0 < p0)); - *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0)); -#else - uECC_dword_t p = (uECC_dword_t)a * b; - uECC_dword_t r01 = ((uECC_dword_t)(*r1) << uECC_WORD_BITS) | *r0; - r01 += p; - *r2 += (r01 < p); - *r1 = r01 >> uECC_WORD_BITS; - *r0 = (uECC_word_t)r01; -#endif -} -#endif /* muladd needed */ - -#if !asm_mult -uECC_VLI_API void uECC_vli_mult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - wordcount_t i, k; - - /* Compute each digit of result in sequence, maintaining the carries. */ - for (k = 0; k < num_words; ++k) { - for (i = 0; i <= k; ++i) { - muladd(left[i], right[k - i], &r0, &r1, &r2); - } - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - for (k = num_words; k < num_words * 2 - 1; ++k) { - for (i = (k + 1) - num_words; i < num_words; ++i) { - muladd(left[i], right[k - i], &r0, &r1, &r2); - } - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - result[num_words * 2 - 1] = r0; -} -#endif /* !asm_mult */ - -#if uECC_SQUARE_FUNC - -#if !asm_square -static void mul2add(uECC_word_t a, - uECC_word_t b, - uECC_word_t *r0, - uECC_word_t *r1, - uECC_word_t *r2) { -#if uECC_WORD_SIZE == 8 && !SUPPORTS_INT128 - uint64_t a0 = a & 0xffffffffull; - uint64_t a1 = a >> 32; - uint64_t b0 = b & 0xffffffffull; - uint64_t b1 = b >> 32; - - uint64_t i0 = a0 * b0; - uint64_t i1 = a0 * b1; - uint64_t i2 = a1 * b0; - uint64_t i3 = a1 * b1; - - uint64_t p0, p1; - - i2 += (i0 >> 32); - i2 += i1; - if (i2 < i1) - { /* overflow */ - i3 += 0x100000000ull; - } - - p0 = (i0 & 0xffffffffull) | (i2 << 32); - p1 = i3 + (i2 >> 32); - - *r2 += (p1 >> 63); - p1 = (p1 << 1) | (p0 >> 63); - p0 <<= 1; - - *r0 += p0; - *r1 += (p1 + (*r0 < p0)); - *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0)); -#else - uECC_dword_t p = (uECC_dword_t)a * b; - uECC_dword_t r01 = ((uECC_dword_t)(*r1) << uECC_WORD_BITS) | *r0; - *r2 += (p >> (uECC_WORD_BITS * 2 - 1)); - p *= 2; - r01 += p; - *r2 += (r01 < p); - *r1 = r01 >> uECC_WORD_BITS; - *r0 = (uECC_word_t)r01; -#endif -} - -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - - wordcount_t i, k; - - for (k = 0; k < num_words * 2 - 1; ++k) { - uECC_word_t min = (k < num_words ? 0 : (k + 1) - num_words); - for (i = min; i <= k && i <= k - i; ++i) { - if (i < k-i) { - mul2add(left[i], left[k - i], &r0, &r1, &r2); - } else { - muladd(left[i], left[k - i], &r0, &r1, &r2); - } - } - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - - result[num_words * 2 - 1] = r0; -} -#endif /* !asm_square */ - -#else /* uECC_SQUARE_FUNC */ - -#if uECC_ENABLE_VLI_API -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - uECC_vli_mult(result, left, left, num_words); -} -#endif /* uECC_ENABLE_VLI_API */ - -#endif /* uECC_SQUARE_FUNC */ - -/* Computes result = (left + right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -uECC_VLI_API void uECC_vli_modAdd(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t carry = uECC_vli_add(result, left, right, num_words); - if (carry || uECC_vli_cmp_unsafe(mod, result, num_words) != 1) { - /* result > mod (result = mod + remainder), so subtract mod to get remainder. */ - uECC_vli_sub(result, result, mod, num_words); - } -} - -/* Computes result = (left - right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -uECC_VLI_API void uECC_vli_modSub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t l_borrow = uECC_vli_sub(result, left, right, num_words); - if (l_borrow) { - /* In this case, result == -diff == (max int) - diff. Since -x % d == d - x, - we can get the correct result from result + mod (with overflow). */ - uECC_vli_add(result, result, mod, num_words); - } -} - -/* Computes result = product % mod, where product is 2N words long. */ -/* Currently only designed to work for curve_p or curve_n. */ -uECC_VLI_API void uECC_vli_mmod(uECC_word_t *result, - uECC_word_t *product, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t mod_multiple[2 * uECC_MAX_WORDS]; - uECC_word_t tmp[2 * uECC_MAX_WORDS]; - uECC_word_t *v[2] = {tmp, product}; - uECC_word_t index; - - /* Shift mod so its highest set bit is at the maximum position. */ - bitcount_t shift = (num_words * 2 * uECC_WORD_BITS) - uECC_vli_numBits(mod, num_words); - wordcount_t word_shift = shift / uECC_WORD_BITS; - wordcount_t bit_shift = shift % uECC_WORD_BITS; - uECC_word_t carry = 0; - uECC_vli_clear(mod_multiple, word_shift); - if (bit_shift > 0) { - for(index = 0; index < (uECC_word_t)num_words; ++index) { - mod_multiple[word_shift + index] = (mod[index] << bit_shift) | carry; - carry = mod[index] >> (uECC_WORD_BITS - bit_shift); - } - } else { - uECC_vli_set(mod_multiple + word_shift, mod, num_words); - } - - for (index = 1; shift >= 0; --shift) { - uECC_word_t borrow = 0; - wordcount_t i; - for (i = 0; i < num_words * 2; ++i) { - uECC_word_t diff = v[index][i] - mod_multiple[i] - borrow; - if (diff != v[index][i]) { - borrow = (diff > v[index][i]); - } - v[1 - index][i] = diff; - } - index = !(index ^ borrow); /* Swap the index if there was no borrow */ - uECC_vli_rshift1(mod_multiple, num_words); - mod_multiple[num_words - 1] |= mod_multiple[num_words] << (uECC_WORD_BITS - 1); - uECC_vli_rshift1(mod_multiple + num_words, num_words); - } - uECC_vli_set(result, v[index], num_words); -} - -/* Computes result = (left * right) % mod. */ -uECC_VLI_API void uECC_vli_modMult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_mult(product, left, right, num_words); - uECC_vli_mmod(result, product, mod, num_words); -} - -uECC_VLI_API void uECC_vli_modMult_fast(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - uECC_Curve curve) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_mult(product, left, right, curve->num_words); -#if (uECC_OPTIMIZATION_LEVEL > 0) - curve->mmod_fast(result, product); -#else - uECC_vli_mmod(result, product, curve->p, curve->num_words); -#endif -} - -#if uECC_SQUARE_FUNC - -#if uECC_ENABLE_VLI_API -/* Computes result = left^2 % mod. */ -uECC_VLI_API void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_square(product, left, num_words); - uECC_vli_mmod(result, product, mod, num_words); -} -#endif /* uECC_ENABLE_VLI_API */ - -uECC_VLI_API void uECC_vli_modSquare_fast(uECC_word_t *result, - const uECC_word_t *left, - uECC_Curve curve) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_square(product, left, curve->num_words); -#if (uECC_OPTIMIZATION_LEVEL > 0) - curve->mmod_fast(result, product); -#else - uECC_vli_mmod(result, product, curve->p, curve->num_words); -#endif -} - -#else /* uECC_SQUARE_FUNC */ - -#if uECC_ENABLE_VLI_API -uECC_VLI_API void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_vli_modMult(result, left, left, mod, num_words); -} -#endif /* uECC_ENABLE_VLI_API */ - -uECC_VLI_API void uECC_vli_modSquare_fast(uECC_word_t *result, - const uECC_word_t *left, - uECC_Curve curve) { - uECC_vli_modMult_fast(result, left, left, curve); -} - -#endif /* uECC_SQUARE_FUNC */ - -#define EVEN(vli) (!(vli[0] & 1)) -static void vli_modInv_update(uECC_word_t *uv, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t carry = 0; - if (!EVEN(uv)) { - carry = uECC_vli_add(uv, uv, mod, num_words); - } - uECC_vli_rshift1(uv, num_words); - if (carry) { - uv[num_words - 1] |= HIGH_BIT_SET; - } -} - -/* Computes result = (1 / input) % mod. All VLIs are the same size. - See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" */ -uECC_VLI_API void uECC_vli_modInv(uECC_word_t *result, - const uECC_word_t *input, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t a[uECC_MAX_WORDS], b[uECC_MAX_WORDS], u[uECC_MAX_WORDS], v[uECC_MAX_WORDS]; - cmpresult_t cmpResult; - - if (uECC_vli_isZero(input, num_words)) { - uECC_vli_clear(result, num_words); - return; - } - - uECC_vli_set(a, input, num_words); - uECC_vli_set(b, mod, num_words); - uECC_vli_clear(u, num_words); - u[0] = 1; - uECC_vli_clear(v, num_words); - while ((cmpResult = uECC_vli_cmp_unsafe(a, b, num_words)) != 0) { - if (EVEN(a)) { - uECC_vli_rshift1(a, num_words); - vli_modInv_update(u, mod, num_words); - } else if (EVEN(b)) { - uECC_vli_rshift1(b, num_words); - vli_modInv_update(v, mod, num_words); - } else if (cmpResult > 0) { - uECC_vli_sub(a, a, b, num_words); - uECC_vli_rshift1(a, num_words); - if (uECC_vli_cmp_unsafe(u, v, num_words) < 0) { - uECC_vli_add(u, u, mod, num_words); - } - uECC_vli_sub(u, u, v, num_words); - vli_modInv_update(u, mod, num_words); - } else { - uECC_vli_sub(b, b, a, num_words); - uECC_vli_rshift1(b, num_words); - if (uECC_vli_cmp_unsafe(v, u, num_words) < 0) { - uECC_vli_add(v, v, mod, num_words); - } - uECC_vli_sub(v, v, u, num_words); - vli_modInv_update(v, mod, num_words); - } - } - uECC_vli_set(result, u, num_words); -} - -/* ------ Point operations ------ */ - -#include "curve-specific.inc" - -/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */ -#define EccPoint_isZero(point, curve) uECC_vli_isZero((point), (curve)->num_words * 2) - -/* Point multiplication algorithm using Montgomery's ladder with co-Z coordinates. -From http://eprint.iacr.org/2011/338.pdf -*/ - -/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ -static void apply_z(uECC_word_t * X1, - uECC_word_t * Y1, - const uECC_word_t * const Z, - uECC_Curve curve) { - uECC_word_t t1[uECC_MAX_WORDS]; - - uECC_vli_modSquare_fast(t1, Z, curve); /* z^2 */ - uECC_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */ - uECC_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */ - uECC_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */ -} - -/* P = (x1, y1) => 2P, (x2, y2) => P' */ -static void XYcZ_initial_double(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * X2, - uECC_word_t * Y2, - const uECC_word_t * const initial_Z, - uECC_Curve curve) { - uECC_word_t z[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - if (initial_Z) { - uECC_vli_set(z, initial_Z, num_words); - } else { - uECC_vli_clear(z, num_words); - z[0] = 1; - } - - uECC_vli_set(X2, X1, num_words); - uECC_vli_set(Y2, Y1, num_words); - - apply_z(X1, Y1, z, curve); - curve->double_jacobian(X1, Y1, z, curve); - apply_z(X2, Y2, z, curve); -} - -/* Input P = (x1, y1, Z), Q = (x2, y2, Z) - Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) - or P => P', Q => P + Q -*/ -static void XYcZ_add(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * X2, - uECC_word_t * Y2, - uECC_Curve curve) { - /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - uECC_word_t t5[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ - uECC_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ - uECC_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2 = D */ - - uECC_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D - B */ - uECC_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D - B - C = x3 */ - uECC_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C - B */ - uECC_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */ - uECC_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B - x3 */ - uECC_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 - y1)*(B - x3) */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */ - - uECC_vli_set(X2, t5, num_words); -} - -/* Input P = (x1, y1, Z), Q = (x2, y2, Z) - Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) - or P => P - Q, Q => P + Q -*/ -static void XYcZ_addC(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * X2, - uECC_word_t * Y2, - uECC_Curve curve) { - /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - uECC_word_t t5[uECC_MAX_WORDS]; - uECC_word_t t6[uECC_MAX_WORDS]; - uECC_word_t t7[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ - uECC_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ - uECC_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2 + y1 */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ - - uECC_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C - B */ - uECC_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C - B) = E */ - uECC_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B + C */ - uECC_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2 = D */ - uECC_vli_modSub(X2, X2, t6, curve->p, num_words); /* t3 = D - (B + C) = x3 */ - - uECC_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B - x3 */ - uECC_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 - y1)*(B - x3) */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */ - - uECC_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2 = F */ - uECC_vli_modSub(t7, t7, t6, curve->p, num_words); /* t7 = F - (B + C) = x3' */ - uECC_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 = x3' - B */ - uECC_vli_modMult_fast(t6, t6, t5, curve); /* t6 = (y2+y1)*(x3' - B) */ - uECC_vli_modSub(Y1, t6, Y1, curve->p, num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */ - - uECC_vli_set(X1, t7, num_words); -} - -/* result may overlap point. */ -static void EccPoint_mult(uECC_word_t * result, - const uECC_word_t * point, - const uECC_word_t * scalar, - const uECC_word_t * initial_Z, - bitcount_t num_bits, - uECC_Curve curve) { - /* R0 and R1 */ - uECC_word_t Rx[2][uECC_MAX_WORDS]; - uECC_word_t Ry[2][uECC_MAX_WORDS]; - uECC_word_t z[uECC_MAX_WORDS]; - bitcount_t i; - uECC_word_t nb; - wordcount_t num_words = curve->num_words; - - uECC_vli_set(Rx[1], point, num_words); - uECC_vli_set(Ry[1], point + num_words, num_words); - - XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve); - - for (i = num_bits - 2; i > 0; --i) { - nb = !uECC_vli_testBit(scalar, i); - XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve); - XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve); - } - - nb = !uECC_vli_testBit(scalar, 0); - XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve); - - /* Find final 1/Z value. */ - uECC_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1 - X0 */ - uECC_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1 - X0) */ - uECC_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1 - X0) */ - uECC_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb * (X1 - X0)) */ - /* yP / (xP * Yb * (X1 - X0)) */ - uECC_vli_modMult_fast(z, z, point + num_words, curve); - uECC_vli_modMult_fast(z, z, Rx[1 - nb], curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */ - /* End 1/Z calculation */ - - XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve); - apply_z(Rx[0], Ry[0], z, curve); - - uECC_vli_set(result, Rx[0], num_words); - uECC_vli_set(result + num_words, Ry[0], num_words); -} - -static uECC_word_t regularize_k(const uECC_word_t * const k, - uECC_word_t *k0, - uECC_word_t *k1, - uECC_Curve curve) { - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - bitcount_t num_n_bits = curve->num_n_bits; - uECC_word_t carry = uECC_vli_add(k0, k, curve->n, num_n_words) || - (num_n_bits < ((bitcount_t)num_n_words * uECC_WORD_SIZE * 8) && - uECC_vli_testBit(k0, num_n_bits)); - uECC_vli_add(k1, k0, curve->n, num_n_words); - return carry; -} - -/* Generates a random integer in the range 0 < random < top. - Both random and top have num_words words. */ -uECC_VLI_API int uECC_generate_random_int(uECC_word_t *random, - const uECC_word_t *top, - wordcount_t num_words) { - uECC_word_t mask = (uECC_word_t)-1; - uECC_word_t tries; - bitcount_t num_bits = uECC_vli_numBits(top, num_words); - - if (!g_rng_function) { - return 0; - } - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!g_rng_function((uint8_t *)random, num_words * uECC_WORD_SIZE)) { - return 0; - } - random[num_words - 1] &= mask >> ((bitcount_t)(num_words * uECC_WORD_SIZE * 8 - num_bits)); - if (!uECC_vli_isZero(random, num_words) && - uECC_vli_cmp(top, random, num_words) == 1) { - return 1; - } - } - return 0; -} - -static uECC_word_t EccPoint_compute_public_key(uECC_word_t *result, - uECC_word_t *private_key, - uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {tmp1, tmp2}; - uECC_word_t *initial_Z = 0; - uECC_word_t carry; - - /* Regularize the bitcount for the private key so that attackers cannot use a side channel - attack to learn the number of leading zeros. */ - carry = regularize_k(private_key, tmp1, tmp2, curve); - - /* If an RNG function was specified, try to get a random initial Z value to improve - protection against side-channel attacks. */ - if (g_rng_function) { - if (!uECC_generate_random_int(p2[carry], curve->p, curve->num_words)) { - return 0; - } - initial_Z = p2[carry]; - } - EccPoint_mult(result, curve->G, p2[!carry], initial_Z, curve->num_n_bits + 1, curve); - - if (EccPoint_isZero(result, curve)) { - return 0; - } - return 1; -} - -#if uECC_WORD_SIZE == 1 - -uECC_VLI_API void uECC_vli_nativeToBytes(uint8_t *bytes, - int num_bytes, - const uint8_t *native) { - wordcount_t i; - for (i = 0; i < num_bytes; ++i) { - bytes[i] = native[(num_bytes - 1) - i]; - } -} - -uECC_VLI_API void uECC_vli_bytesToNative(uint8_t *native, - const uint8_t *bytes, - int num_bytes) { - uECC_vli_nativeToBytes(native, num_bytes, bytes); -} - -#else - -uECC_VLI_API void uECC_vli_nativeToBytes(uint8_t *bytes, - int num_bytes, - const uECC_word_t *native) { - int i; - for (i = 0; i < num_bytes; ++i) { - unsigned b = num_bytes - 1 - i; - bytes[i] = native[b / uECC_WORD_SIZE] >> (8 * (b % uECC_WORD_SIZE)); - } -} - -uECC_VLI_API void uECC_vli_bytesToNative(uECC_word_t *native, - const uint8_t *bytes, - int num_bytes) { - int i; - uECC_vli_clear(native, (num_bytes + (uECC_WORD_SIZE - 1)) / uECC_WORD_SIZE); - for (i = 0; i < num_bytes; ++i) { - unsigned b = num_bytes - 1 - i; - native[b / uECC_WORD_SIZE] |= - (uECC_word_t)bytes[i] << (8 * (b % uECC_WORD_SIZE)); - } -} - -#endif /* uECC_WORD_SIZE */ - -int uECC_make_key(uint8_t *public_key, - uint8_t *private_key, - uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_private = (uECC_word_t *)private_key; - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _private[uECC_MAX_WORDS]; - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t tries; - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!uECC_generate_random_int(_private, curve->n, BITS_TO_WORDS(curve->num_n_bits))) { - return 0; - } - - if (EccPoint_compute_public_key(_public, _private, curve)) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(private_key, BITS_TO_BYTES(curve->num_n_bits), _private); - uECC_vli_nativeToBytes(public_key, curve->num_bytes, _public); - uECC_vli_nativeToBytes( - public_key + curve->num_bytes, curve->num_bytes, _public + curve->num_words); -#endif - return 1; - } - } - return 0; -} - -int uECC_shared_secret(const uint8_t *public_key, - const uint8_t *private_key, - uint8_t *secret, - uECC_Curve curve) { - uECC_word_t _public[uECC_MAX_WORDS * 2]; - uECC_word_t _private[uECC_MAX_WORDS]; - - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {_private, tmp}; - uECC_word_t *initial_Z = 0; - uECC_word_t carry; - wordcount_t num_words = curve->num_words; - wordcount_t num_bytes = curve->num_bytes; - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) _private, private_key, num_bytes); - bcopy((uint8_t *) _public, public_key, num_bytes*2); -#else - uECC_vli_bytesToNative(_private, private_key, BITS_TO_BYTES(curve->num_n_bits)); - uECC_vli_bytesToNative(_public, public_key, num_bytes); - uECC_vli_bytesToNative(_public + num_words, public_key + num_bytes, num_bytes); -#endif - - /* Regularize the bitcount for the private key so that attackers cannot use a side channel - attack to learn the number of leading zeros. */ - carry = regularize_k(_private, _private, tmp, curve); - - /* If an RNG function was specified, try to get a random initial Z value to improve - protection against side-channel attacks. */ - if (g_rng_function) { - if (!uECC_generate_random_int(p2[carry], curve->p, num_words)) { - return 0; - } - initial_Z = p2[carry]; - } - - EccPoint_mult(_public, _public, p2[!carry], initial_Z, curve->num_n_bits + 1, curve); -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes); -#else - uECC_vli_nativeToBytes(secret, num_bytes, _public); -#endif - return !EccPoint_isZero(_public, curve); -} - -#if uECC_SUPPORT_COMPRESSED_POINT -void uECC_compress(const uint8_t *public_key, uint8_t *compressed, uECC_Curve curve) { - wordcount_t i; - for (i = 0; i < curve->num_bytes; ++i) { - compressed[i+1] = public_key[i]; - } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01); -#else - compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01); -#endif -} - -void uECC_decompress(const uint8_t *compressed, uint8_t *public_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *point = (uECC_word_t *)public_key; -#else - uECC_word_t point[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t *y = point + curve->num_words; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy(public_key, compressed+1, curve->num_bytes); -#else - uECC_vli_bytesToNative(point, compressed + 1, curve->num_bytes); -#endif - curve->x_side(y, point, curve); - curve->mod_sqrt(y, curve); - - if ((y[0] & 0x01) != (compressed[0] & 0x01)) { - uECC_vli_sub(y, curve->p, y, curve->num_words); - } - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(public_key, curve->num_bytes, point); - uECC_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, y); -#endif -} -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -uECC_VLI_API int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - /* The point at infinity is invalid. */ - if (EccPoint_isZero(point, curve)) { - return 0; - } - - /* x and y must be smaller than p. */ - if (uECC_vli_cmp_unsafe(curve->p, point, num_words) != 1 || - uECC_vli_cmp_unsafe(curve->p, point + num_words, num_words) != 1) { - return 0; - } - - uECC_vli_modSquare_fast(tmp1, point + num_words, curve); - curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */ - - /* Make sure that y^2 == x^3 + ax + b */ - return (int)(uECC_vli_equal(tmp1, tmp2, num_words)); -} - -int uECC_valid_public_key(const uint8_t *public_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); - uECC_vli_bytesToNative( - _public + curve->num_words, public_key + curve->num_bytes, curve->num_bytes); -#endif - return uECC_valid_point(_public, curve); -} - -int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_private = (uECC_word_t *)private_key; - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _private[uECC_MAX_WORDS]; - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_bytesToNative(_private, private_key, BITS_TO_BYTES(curve->num_n_bits)); -#endif - - /* Make sure the private key is in the range [1, n-1]. */ - if (uECC_vli_isZero(_private, BITS_TO_WORDS(curve->num_n_bits))) { - return 0; - } - - if (uECC_vli_cmp(curve->n, _private, BITS_TO_WORDS(curve->num_n_bits)) != 1) { - return 0; - } - - /* Compute public key. */ - if (!EccPoint_compute_public_key(_public, _private, curve)) { - return 0; - } - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(public_key, curve->num_bytes, _public); - uECC_vli_nativeToBytes( - public_key + curve->num_bytes, curve->num_bytes, _public + curve->num_words); -#endif - return 1; -} - - -/* -------- ECDSA code -------- */ - -static void bits2int(uECC_word_t *native, - const uint8_t *bits, - unsigned bits_size, - uECC_Curve curve) { - unsigned num_n_bytes = BITS_TO_BYTES(curve->num_n_bits); - unsigned num_n_words = BITS_TO_WORDS(curve->num_n_bits); - int shift; - uECC_word_t carry; - uECC_word_t *ptr; - - if (bits_size > num_n_bytes) { - bits_size = num_n_bytes; - } - - uECC_vli_clear(native, num_n_words); -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) native, bits, bits_size); -#else - uECC_vli_bytesToNative(native, bits, bits_size); -#endif - if (bits_size * 8 <= (unsigned)curve->num_n_bits) { - return; - } - shift = bits_size * 8 - curve->num_n_bits; - carry = 0; - ptr = native + num_n_words; - while (ptr-- > native) { - uECC_word_t temp = *ptr; - *ptr = (temp >> shift) | carry; - carry = temp << (uECC_WORD_BITS - shift); - } - - /* Reduce mod curve_n */ - if (uECC_vli_cmp_unsafe(curve->n, native, num_n_words) != 1) { - uECC_vli_sub(native, native, curve->n, num_n_words); - } -} - -static int uECC_sign_with_k_internal(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - uECC_word_t *k, - uint8_t *signature, - uECC_Curve curve) { - - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t s[uECC_MAX_WORDS]; - uECC_word_t *k2[2] = {tmp, s}; - uECC_word_t *initial_Z = 0; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *p = (uECC_word_t *)signature; -#else - uECC_word_t p[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t carry; - wordcount_t num_words = curve->num_words; - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - bitcount_t num_n_bits = curve->num_n_bits; - - /* Make sure 0 < k < curve_n */ - if (uECC_vli_isZero(k, num_words) || uECC_vli_cmp(curve->n, k, num_n_words) != 1) { - return 0; - } - - carry = regularize_k(k, tmp, s, curve); - /* If an RNG function was specified, try to get a random initial Z value to improve - protection against side-channel attacks. */ - if (g_rng_function) { - if (!uECC_generate_random_int(k2[carry], curve->p, num_words)) { - return 0; - } - initial_Z = k2[carry]; - } - EccPoint_mult(p, curve->G, k2[!carry], initial_Z, num_n_bits + 1, curve); - if (uECC_vli_isZero(p, num_words)) { - return 0; - } - - /* If an RNG function was specified, get a random number - to prevent side channel analysis of k. */ - if (!g_rng_function) { - uECC_vli_clear(tmp, num_n_words); - tmp[0] = 1; - } else if (!uECC_generate_random_int(tmp, curve->n, num_n_words)) { - return 0; - } - - /* Prevent side channel analysis of uECC_vli_modInv() to determine - bits of k / the private key by premultiplying by a random number */ - uECC_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' = rand * k */ - uECC_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */ - uECC_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1 / k */ - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(signature, curve->num_bytes, p); /* store r */ -#endif - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) tmp, private_key, BITS_TO_BYTES(curve->num_n_bits)); -#else - uECC_vli_bytesToNative(tmp, private_key, BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */ -#endif - - s[num_n_words - 1] = 0; - uECC_vli_set(s, p, num_words); - uECC_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s = r*d */ - - bits2int(tmp, message_hash, hash_size, curve); - uECC_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */ - uECC_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e + r*d) / k */ - if (uECC_vli_numBits(s, num_n_words) > (bitcount_t)curve->num_bytes * 8) { - return 0; - } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s, curve->num_bytes); -#else - uECC_vli_nativeToBytes(signature + curve->num_bytes, curve->num_bytes, s); -#endif - return 1; -} - -/* For testing - sign with an explicitly specified k value */ -int uECC_sign_with_k(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - const uint8_t *k, - uint8_t *signature, - uECC_Curve curve) { - uECC_word_t k2[uECC_MAX_WORDS]; - bits2int(k2, k, BITS_TO_BYTES(curve->num_n_bits), curve); - return uECC_sign_with_k_internal(private_key, message_hash, hash_size, k2, signature, curve); -} - -int uECC_sign(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - uint8_t *signature, - uECC_Curve curve) { - uECC_word_t k[uECC_MAX_WORDS]; - uECC_word_t tries; - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!uECC_generate_random_int(k, curve->n, BITS_TO_WORDS(curve->num_n_bits))) { - return 0; - } - - if (uECC_sign_with_k_internal(private_key, message_hash, hash_size, k, signature, curve)) { - return 1; - } - } - return 0; -} - -/* 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 uECC_HashContext *hash_context, const uint8_t *K) { - uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size; - unsigned i; - for (i = 0; i < hash_context->result_size; ++i) - pad[i] = K[i] ^ 0x36; - for (; i < hash_context->block_size; ++i) - pad[i] = 0x36; - - hash_context->init_hash(hash_context); - hash_context->update_hash(hash_context, pad, hash_context->block_size); -} - -static void HMAC_update(const uECC_HashContext *hash_context, - const uint8_t *message, - unsigned message_size) { - hash_context->update_hash(hash_context, message, message_size); -} - -static void HMAC_finish(const uECC_HashContext *hash_context, - const uint8_t *K, - uint8_t *result) { - uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size; - unsigned i; - for (i = 0; i < hash_context->result_size; ++i) - pad[i] = K[i] ^ 0x5c; - for (; i < hash_context->block_size; ++i) - pad[i] = 0x5c; - - hash_context->finish_hash(hash_context, result); - - hash_context->init_hash(hash_context); - hash_context->update_hash(hash_context, pad, hash_context->block_size); - hash_context->update_hash(hash_context, result, hash_context->result_size); - hash_context->finish_hash(hash_context, result); -} - -/* V = HMAC_K(V) */ -static void update_V(const uECC_HashContext *hash_context, uint8_t *K, uint8_t *V) { - HMAC_init(hash_context, K); - HMAC_update(hash_context, V, hash_context->result_size); - HMAC_finish(hash_context, K, V); -} - -/* Deterministic signing, similar to RFC 6979. Differences are: - * We just use H(m) directly rather than bits2octets(H(m)) - (it is not reduced modulo curve_n). - * We generate a value for k (aka T) directly rather than converting endianness. - - Layout of hash_context->tmp: | | (1 byte overlapped 0x00 or 0x01) / */ -int uECC_sign_deterministic(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - const uECC_HashContext *hash_context, - uint8_t *signature, - uECC_Curve curve) { - uint8_t *K = hash_context->tmp; - uint8_t *V = K + hash_context->result_size; - wordcount_t num_bytes = curve->num_bytes; - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - bitcount_t num_n_bits = curve->num_n_bits; - uECC_word_t tries; - unsigned i; - for (i = 0; i < hash_context->result_size; ++i) { - V[i] = 0x01; - K[i] = 0; - } - - /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */ - HMAC_init(hash_context, K); - V[hash_context->result_size] = 0x00; - HMAC_update(hash_context, V, hash_context->result_size + 1); - HMAC_update(hash_context, private_key, num_bytes); - HMAC_update(hash_context, message_hash, hash_size); - HMAC_finish(hash_context, K, K); - - update_V(hash_context, K, V); - - /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */ - HMAC_init(hash_context, K); - V[hash_context->result_size] = 0x01; - HMAC_update(hash_context, V, hash_context->result_size + 1); - HMAC_update(hash_context, private_key, num_bytes); - HMAC_update(hash_context, message_hash, hash_size); - HMAC_finish(hash_context, K, K); - - update_V(hash_context, K, V); - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - uECC_word_t T[uECC_MAX_WORDS]; - uint8_t *T_ptr = (uint8_t *)T; - wordcount_t T_bytes = 0; - for (;;) { - update_V(hash_context, K, V); - for (i = 0; i < hash_context->result_size; ++i) { - T_ptr[T_bytes++] = V[i]; - if (T_bytes >= num_n_words * uECC_WORD_SIZE) { - goto filled; - } - } - } - filled: - if ((bitcount_t)num_n_words * uECC_WORD_SIZE * 8 > num_n_bits) { - uECC_word_t mask = (uECC_word_t)-1; - T[num_n_words - 1] &= - mask >> ((bitcount_t)(num_n_words * uECC_WORD_SIZE * 8 - num_n_bits)); - } - - if (uECC_sign_with_k_internal(private_key, message_hash, hash_size, T, signature, curve)) { - return 1; - } - - /* K = HMAC_K(V || 0x00) */ - HMAC_init(hash_context, K); - V[hash_context->result_size] = 0x00; - HMAC_update(hash_context, V, hash_context->result_size + 1); - HMAC_finish(hash_context, K, K); - - update_V(hash_context, K, V); - } - return 0; -} - -static bitcount_t smax(bitcount_t a, bitcount_t b) { - return (a > b ? a : b); -} - -int uECC_verify(const uint8_t *public_key, - const uint8_t *message_hash, - unsigned hash_size, - const uint8_t *signature, - uECC_Curve curve) { - uECC_word_t u1[uECC_MAX_WORDS], u2[uECC_MAX_WORDS]; - uECC_word_t z[uECC_MAX_WORDS]; - uECC_word_t sum[uECC_MAX_WORDS * 2]; - uECC_word_t rx[uECC_MAX_WORDS]; - uECC_word_t ry[uECC_MAX_WORDS]; - uECC_word_t tx[uECC_MAX_WORDS]; - uECC_word_t ty[uECC_MAX_WORDS]; - uECC_word_t tz[uECC_MAX_WORDS]; - const uECC_word_t *points[4]; - const uECC_word_t *point; - bitcount_t num_bits; - bitcount_t i; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t r[uECC_MAX_WORDS], s[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - - rx[num_n_words - 1] = 0; - r[num_n_words - 1] = 0; - s[num_n_words - 1] = 0; - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) r, signature, curve->num_bytes); - bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes); -#else - uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); - uECC_vli_bytesToNative( - _public + num_words, public_key + curve->num_bytes, curve->num_bytes); - uECC_vli_bytesToNative(r, signature, curve->num_bytes); - uECC_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes); -#endif - - /* r, s must not be 0. */ - if (uECC_vli_isZero(r, num_words) || uECC_vli_isZero(s, num_words)) { - return 0; - } - - /* r, s must be < n. */ - if (uECC_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 || - uECC_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) { - return 0; - } - - /* Calculate u1 and u2. */ - uECC_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */ - u1[num_n_words - 1] = 0; - bits2int(u1, message_hash, hash_size, curve); - uECC_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */ - uECC_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 = r/s */ - - /* Calculate sum = G + Q. */ - uECC_vli_set(sum, _public, num_words); - uECC_vli_set(sum + num_words, _public + num_words, num_words); - uECC_vli_set(tx, curve->G, num_words); - uECC_vli_set(ty, curve->G + num_words, num_words); - uECC_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */ - XYcZ_add(tx, ty, sum, sum + num_words, curve); - uECC_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */ - apply_z(sum, sum + num_words, z, curve); - - /* Use Shamir's trick to calculate u1*G + u2*Q */ - points[0] = 0; - points[1] = curve->G; - points[2] = _public; - points[3] = sum; - num_bits = smax(uECC_vli_numBits(u1, num_n_words), - uECC_vli_numBits(u2, num_n_words)); - - point = points[(!!uECC_vli_testBit(u1, num_bits - 1)) | - ((!!uECC_vli_testBit(u2, num_bits - 1)) << 1)]; - uECC_vli_set(rx, point, num_words); - uECC_vli_set(ry, point + num_words, num_words); - uECC_vli_clear(z, num_words); - z[0] = 1; - - for (i = num_bits - 2; i >= 0; --i) { - uECC_word_t index; - curve->double_jacobian(rx, ry, z, curve); - - index = (!!uECC_vli_testBit(u1, i)) | ((!!uECC_vli_testBit(u2, i)) << 1); - point = points[index]; - if (point) { - uECC_vli_set(tx, point, num_words); - uECC_vli_set(ty, point + num_words, num_words); - apply_z(tx, ty, z, curve); - uECC_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */ - XYcZ_add(tx, ty, rx, ry, curve); - uECC_vli_modMult_fast(z, z, tz, curve); - } - } - - uECC_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */ - apply_z(rx, ry, z, curve); - - /* v = x1 (mod n) */ - if (uECC_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) { - uECC_vli_sub(rx, rx, curve->n, num_n_words); - } - - /* Accept only if v == r. */ - return (int)(uECC_vli_equal(rx, r, num_words)); -} - -#if uECC_ENABLE_VLI_API - -unsigned uECC_curve_num_words(uECC_Curve curve) { - return curve->num_words; -} - -unsigned uECC_curve_num_bytes(uECC_Curve curve) { - return curve->num_bytes; -} - -unsigned uECC_curve_num_bits(uECC_Curve curve) { - return curve->num_bytes * 8; -} - -unsigned uECC_curve_num_n_words(uECC_Curve curve) { - return BITS_TO_WORDS(curve->num_n_bits); -} - -unsigned uECC_curve_num_n_bytes(uECC_Curve curve) { - return BITS_TO_BYTES(curve->num_n_bits); -} - -unsigned uECC_curve_num_n_bits(uECC_Curve curve) { - return curve->num_n_bits; -} - -const uECC_word_t *uECC_curve_p(uECC_Curve curve) { - return curve->p; -} - -const uECC_word_t *uECC_curve_n(uECC_Curve curve) { - return curve->n; -} - -const uECC_word_t *uECC_curve_G(uECC_Curve curve) { - return curve->G; -} - -const uECC_word_t *uECC_curve_b(uECC_Curve curve) { - return curve->b; -} - -#if uECC_SUPPORT_COMPRESSED_POINT -void uECC_vli_mod_sqrt(uECC_word_t *a, uECC_Curve curve) { - curve->mod_sqrt(a, curve); -} -#endif - -void uECC_vli_mmod_fast(uECC_word_t *result, uECC_word_t *product, uECC_Curve curve) { -#if (uECC_OPTIMIZATION_LEVEL > 0) - curve->mmod_fast(result, product); -#else - uECC_vli_mmod(result, product, curve->p, curve->num_words); -#endif -} - -void uECC_point_mult(uECC_word_t *result, - const uECC_word_t *point, - const uECC_word_t *scalar, - uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {tmp1, tmp2}; - uECC_word_t carry = regularize_k(scalar, tmp1, tmp2, curve); - - EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits + 1, curve); -} - -#endif /* uECC_ENABLE_VLI_API */ diff --git a/lib/micro-ecc/uECC.h b/lib/micro-ecc/uECC.h deleted file mode 100644 index dcbdbfa8b4..0000000000 --- a/lib/micro-ecc/uECC.h +++ /dev/null @@ -1,367 +0,0 @@ -/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_H_ -#define _UECC_H_ - -#include - -/* Platform selection options. -If uECC_PLATFORM is not defined, the code will try to guess it based on compiler macros. -Possible values for uECC_PLATFORM are defined below: */ -#define uECC_arch_other 0 -#define uECC_x86 1 -#define uECC_x86_64 2 -#define uECC_arm 3 -#define uECC_arm_thumb 4 -#define uECC_arm_thumb2 5 -#define uECC_arm64 6 -#define uECC_avr 7 - -/* If desired, you can define uECC_WORD_SIZE as appropriate for your platform (1, 4, or 8 bytes). -If uECC_WORD_SIZE is not explicitly defined then it will be automatically set based on your -platform. */ - -/* Optimization level; trade speed for code size. - Larger values produce code that is faster but larger. - Currently supported values are 0 - 4; 0 is unusably slow for most applications. - Optimization level 4 currently only has an effect ARM platforms where more than one - curve is enabled. */ -#ifndef uECC_OPTIMIZATION_LEVEL - #define uECC_OPTIMIZATION_LEVEL 2 -#endif - -/* uECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a specific function to be -used for (scalar) squaring instead of the generic multiplication function. This can make things -faster somewhat faster, but increases the code size. */ -#ifndef uECC_SQUARE_FUNC - #define uECC_SQUARE_FUNC 0 -#endif - -/* uECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will switch to native -little-endian format for *all* arrays passed in and out of the public API. This includes public -and private keys, shared secrets, signatures and message hashes. -Using this switch reduces the amount of call stack memory used by uECC, since less intermediate -translations are required. -Note that this will *only* work on native little-endian processors and it will treat the uint8_t -arrays passed into the public API as word arrays, therefore requiring the provided byte arrays -to be word aligned on architectures that do not support unaligned accesses. -IMPORTANT: Keys and signatures generated with uECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible -with keys and signatures generated with uECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use -the same endianness. */ -#ifndef uECC_VLI_NATIVE_LITTLE_ENDIAN - #define uECC_VLI_NATIVE_LITTLE_ENDIAN 0 -#endif - -/* Curve support selection. Set to 0 to remove that curve. */ -#ifndef uECC_SUPPORTS_secp160r1 - #define uECC_SUPPORTS_secp160r1 1 -#endif -#ifndef uECC_SUPPORTS_secp192r1 - #define uECC_SUPPORTS_secp192r1 1 -#endif -#ifndef uECC_SUPPORTS_secp224r1 - #define uECC_SUPPORTS_secp224r1 1 -#endif -#ifndef uECC_SUPPORTS_secp256r1 - #define uECC_SUPPORTS_secp256r1 1 -#endif -#ifndef uECC_SUPPORTS_secp256k1 - #define uECC_SUPPORTS_secp256k1 1 -#endif - -/* Specifies whether compressed point format is supported. - Set to 0 to disable point compression/decompression functions. */ -#ifndef uECC_SUPPORT_COMPRESSED_POINT - #define uECC_SUPPORT_COMPRESSED_POINT 1 -#endif - -struct uECC_Curve_t; -typedef const struct uECC_Curve_t * uECC_Curve; - -#ifdef __cplusplus -extern "C" -{ -#endif - -#if uECC_SUPPORTS_secp160r1 -uECC_Curve uECC_secp160r1(void); -#endif -#if uECC_SUPPORTS_secp192r1 -uECC_Curve uECC_secp192r1(void); -#endif -#if uECC_SUPPORTS_secp224r1 -uECC_Curve uECC_secp224r1(void); -#endif -#if uECC_SUPPORTS_secp256r1 -uECC_Curve uECC_secp256r1(void); -#endif -#if uECC_SUPPORTS_secp256k1 -uECC_Curve uECC_secp256k1(void); -#endif - -/* uECC_RNG_Function type -The RNG function should fill 'size' random bytes into 'dest'. It should return 1 if -'dest' was filled with random data, or 0 if the random data could not be generated. -The filled-in values should be either truly random, or from a cryptographically-secure PRNG. - -A correctly functioning RNG function must be set (using uECC_set_rng()) before calling -uECC_make_key() or uECC_sign(). - -Setting a correctly functioning RNG function improves the resistance to side-channel attacks -for uECC_shared_secret() and uECC_sign_deterministic(). - -A correct RNG function is set by default when building for Windows, Linux, or OS X. -If you are building on another POSIX-compliant system that supports /dev/random or /dev/urandom, -you can define uECC_POSIX to use the predefined RNG. For embedded platforms there is no predefined -RNG function; you must provide your own. -*/ -typedef int (*uECC_RNG_Function)(uint8_t *dest, unsigned size); - -/* uECC_set_rng() function. -Set the function that will be used to generate random bytes. The RNG function should -return 1 if the random data was generated, or 0 if the random data could not be generated. - -On platforms where there is no predefined RNG function (eg embedded platforms), this must -be called before uECC_make_key() or uECC_sign() are used. - -Inputs: - rng_function - The function that will be used to generate random bytes. -*/ -void uECC_set_rng(uECC_RNG_Function rng_function); - -/* uECC_get_rng() function. - -Returns the function that will be used to generate random bytes. -*/ -uECC_RNG_Function uECC_get_rng(void); - -/* uECC_curve_private_key_size() function. - -Returns the size of a private key for the curve in bytes. -*/ -int uECC_curve_private_key_size(uECC_Curve curve); - -/* uECC_curve_public_key_size() function. - -Returns the size of a public key for the curve in bytes. -*/ -int uECC_curve_public_key_size(uECC_Curve curve); - -/* uECC_make_key() function. -Create a public/private key pair. - -Outputs: - public_key - Will be filled in with the public key. Must be at least 2 * the curve size - (in bytes) long. For example, if the curve is secp256r1, public_key must be 64 - bytes long. - private_key - Will be filled in with the private key. Must be as long as the curve order; this - is typically the same as the curve size, except for secp160r1. For example, if the - curve is secp256r1, private_key must be 32 bytes long. - - For secp160r1, private_key must be 21 bytes long! Note that the first byte will - almost always be 0 (there is about a 1 in 2^80 chance of it being non-zero). - -Returns 1 if the key pair was generated successfully, 0 if an error occurred. -*/ -int uECC_make_key(uint8_t *public_key, uint8_t *private_key, uECC_Curve curve); - -/* uECC_shared_secret() function. -Compute a shared secret given your secret key and someone else's public key. If the public key -is not from a trusted source and has not been previously verified, you should verify it first -using uECC_valid_public_key(). -Note: It is recommended that you hash the result of uECC_shared_secret() before using it for -symmetric encryption or HMAC. - -Inputs: - public_key - The public key of the remote party. - private_key - Your private key. - -Outputs: - secret - Will be filled in with the shared secret value. Must be the same size as the - curve size; for example, if the curve is secp256r1, secret must be 32 bytes long. - -Returns 1 if the shared secret was generated successfully, 0 if an error occurred. -*/ -int uECC_shared_secret(const uint8_t *public_key, - const uint8_t *private_key, - uint8_t *secret, - uECC_Curve curve); - -#if uECC_SUPPORT_COMPRESSED_POINT -/* uECC_compress() function. -Compress a public key. - -Inputs: - public_key - The public key to compress. - -Outputs: - compressed - Will be filled in with the compressed public key. Must be at least - (curve size + 1) bytes long; for example, if the curve is secp256r1, - compressed must be 33 bytes long. -*/ -void uECC_compress(const uint8_t *public_key, uint8_t *compressed, uECC_Curve curve); - -/* uECC_decompress() function. -Decompress a compressed public key. - -Inputs: - compressed - The compressed public key. - -Outputs: - public_key - Will be filled in with the decompressed public key. -*/ -void uECC_decompress(const uint8_t *compressed, uint8_t *public_key, uECC_Curve curve); -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -/* uECC_valid_public_key() function. -Check to see if a public key is valid. - -Note that you are not required to check for a valid public key before using any other uECC -functions. However, you may wish to avoid spending CPU time computing a shared secret or -verifying a signature using an invalid public key. - -Inputs: - public_key - The public key to check. - -Returns 1 if the public key is valid, 0 if it is invalid. -*/ -int uECC_valid_public_key(const uint8_t *public_key, uECC_Curve curve); - -/* uECC_compute_public_key() function. -Compute the corresponding public key for a private key. - -Inputs: - private_key - The private key to compute the public key for - -Outputs: - public_key - Will be filled in with the corresponding public key - -Returns 1 if the key was computed successfully, 0 if an error occurred. -*/ -int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, uECC_Curve curve); - -/* uECC_sign() function. -Generate an ECDSA signature for a given hash value. - -Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and pass it in to -this function along with your private key. - -Inputs: - private_key - Your private key. - message_hash - The hash of the message to sign. - hash_size - The size of message_hash in bytes. - -Outputs: - signature - Will be filled in with the signature value. Must be at least 2 * curve size long. - For example, if the curve is secp256r1, signature must be 64 bytes long. - -Returns 1 if the signature generated successfully, 0 if an error occurred. -*/ -int uECC_sign(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - uint8_t *signature, - uECC_Curve curve); - -/* uECC_HashContext structure. -This is used to pass in an arbitrary hash function to uECC_sign_deterministic(). -The structure will be used for multiple hash computations; each time a new hash -is computed, init_hash() will be called, followed by one or more calls to -update_hash(), and finally a call to finish_hash() to produce the resulting hash. - -The intention is that you will create a structure that includes uECC_HashContext -followed by any hash-specific data. For example: - -typedef struct SHA256_HashContext { - uECC_HashContext uECC; - SHA256_CTX ctx; -} SHA256_HashContext; - -void init_SHA256(uECC_HashContext *base) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Init(&context->ctx); -} - -void update_SHA256(uECC_HashContext *base, - const uint8_t *message, - unsigned message_size) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Update(&context->ctx, message, message_size); -} - -void finish_SHA256(uECC_HashContext *base, uint8_t *hash_result) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Final(hash_result, &context->ctx); -} - -... when signing ... -{ - uint8_t tmp[32 + 32 + 64]; - SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp}}; - uECC_sign_deterministic(key, message_hash, &ctx.uECC, signature); -} -*/ -typedef struct uECC_HashContext { - void (*init_hash)(const struct uECC_HashContext *context); - void (*update_hash)(const struct uECC_HashContext *context, - const uint8_t *message, - unsigned message_size); - void (*finish_hash)(const struct uECC_HashContext *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. */ -} uECC_HashContext; - -/* uECC_sign_deterministic() function. -Generate an ECDSA signature for a given hash value, using a deterministic algorithm -(see RFC 6979). You do not need to set the RNG using uECC_set_rng() before calling -this function; however, if the RNG is defined it will improve resistance to side-channel -attacks. - -Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and pass it to -this function along with your private key and a hash context. Note that the message_hash -does not need to be computed with the same hash function used by hash_context. - -Inputs: - private_key - Your private key. - message_hash - The hash of the message to sign. - hash_size - The size of message_hash in bytes. - hash_context - A hash context to use. - -Outputs: - signature - Will be filled in with the signature value. - -Returns 1 if the signature generated successfully, 0 if an error occurred. -*/ -int uECC_sign_deterministic(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - const uECC_HashContext *hash_context, - uint8_t *signature, - uECC_Curve curve); - -/* uECC_verify() function. -Verify an ECDSA signature. - -Usage: Compute the hash of the signed data using the same hash as the signer and -pass it to this function along with the signer's public key and the signature values (r and s). - -Inputs: - public_key - The signer's public key. - message_hash - The hash of the signed data. - hash_size - The size of message_hash in bytes. - signature - The signature value. - -Returns 1 if the signature is valid, 0 if it is invalid. -*/ -int uECC_verify(const uint8_t *public_key, - const uint8_t *message_hash, - unsigned hash_size, - const uint8_t *signature, - uECC_Curve curve); - -#ifdef __cplusplus -} /* end of extern "C" */ -#endif - -#endif /* _UECC_H_ */ diff --git a/lib/micro-ecc/uECC_vli.h b/lib/micro-ecc/uECC_vli.h deleted file mode 100644 index 864cc33356..0000000000 --- a/lib/micro-ecc/uECC_vli.h +++ /dev/null @@ -1,172 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_VLI_H_ -#define _UECC_VLI_H_ - -#include "uECC.h" -#include "types.h" - -/* Functions for raw large-integer manipulation. These are only available - if uECC.c is compiled with uECC_ENABLE_VLI_API defined to 1. */ -#ifndef uECC_ENABLE_VLI_API - #define uECC_ENABLE_VLI_API 0 -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -#if uECC_ENABLE_VLI_API - -void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words); - -/* Constant-time comparison to zero - secure way to compare long integers */ -/* Returns 1 if vli == 0, 0 otherwise. */ -uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, wordcount_t num_words); - -/* Returns nonzero if bit 'bit' of vli is set. */ -uECC_word_t uECC_vli_testBit(const uECC_word_t *vli, bitcount_t bit); - -/* Counts the number of bits required to represent vli. */ -bitcount_t uECC_vli_numBits(const uECC_word_t *vli, const wordcount_t max_words); - -/* Sets dest = src. */ -void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, wordcount_t num_words); - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns one if left == right, zero otherwise */ -uECC_word_t uECC_vli_equal(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns sign of left - right, in constant time. */ -cmpresult_t uECC_vli_cmp(const uECC_word_t *left, const uECC_word_t *right, wordcount_t num_words); - -/* Computes vli = vli >> 1. */ -void uECC_vli_rshift1(uECC_word_t *vli, wordcount_t num_words); - -/* Computes result = left + right, returning carry. Can modify in place. */ -uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Computes result = left - right, returning borrow. Can modify in place. */ -uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Computes result = left * right. Result must be 2 * num_words long. */ -void uECC_vli_mult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Computes result = left^2. Result must be 2 * num_words long. */ -void uECC_vli_square(uECC_word_t *result, const uECC_word_t *left, wordcount_t num_words); - -/* Computes result = (left + right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -void uECC_vli_modAdd(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = (left - right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -void uECC_vli_modSub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = product % mod, where product is 2N words long. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_mmod(uECC_word_t *result, - uECC_word_t *product, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Calculates result = product (mod curve->p), where product is up to - 2 * curve->num_words long. */ -void uECC_vli_mmod_fast(uECC_word_t *result, uECC_word_t *product, uECC_Curve curve); - -/* Computes result = (left * right) % mod. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modMult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = (left * right) % curve->p. */ -void uECC_vli_modMult_fast(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - uECC_Curve curve); - -/* Computes result = left^2 % mod. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = left^2 % curve->p. */ -void uECC_vli_modSquare_fast(uECC_word_t *result, const uECC_word_t *left, uECC_Curve curve); - -/* Computes result = (1 / input) % mod.*/ -void uECC_vli_modInv(uECC_word_t *result, - const uECC_word_t *input, - const uECC_word_t *mod, - wordcount_t num_words); - -#if uECC_SUPPORT_COMPRESSED_POINT -/* Calculates a = sqrt(a) (mod curve->p) */ -void uECC_vli_mod_sqrt(uECC_word_t *a, uECC_Curve curve); -#endif - -/* Converts an integer in uECC native format to big-endian bytes. */ -void uECC_vli_nativeToBytes(uint8_t *bytes, int num_bytes, const uECC_word_t *native); -/* Converts big-endian bytes to an integer in uECC native format. */ -void uECC_vli_bytesToNative(uECC_word_t *native, const uint8_t *bytes, int num_bytes); - -unsigned uECC_curve_num_words(uECC_Curve curve); -unsigned uECC_curve_num_bytes(uECC_Curve curve); -unsigned uECC_curve_num_bits(uECC_Curve curve); -unsigned uECC_curve_num_n_words(uECC_Curve curve); -unsigned uECC_curve_num_n_bytes(uECC_Curve curve); -unsigned uECC_curve_num_n_bits(uECC_Curve curve); - -const uECC_word_t *uECC_curve_p(uECC_Curve curve); -const uECC_word_t *uECC_curve_n(uECC_Curve curve); -const uECC_word_t *uECC_curve_G(uECC_Curve curve); -const uECC_word_t *uECC_curve_b(uECC_Curve curve); - -int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve); - -/* Multiplies a point by a scalar. Points are represented by the X coordinate followed by - the Y coordinate in the same array, both coordinates are curve->num_words long. Note - that scalar must be curve->num_n_words long (NOT curve->num_words). */ -void uECC_point_mult(uECC_word_t *result, - const uECC_word_t *point, - const uECC_word_t *scalar, - uECC_Curve curve); - -/* Generates a random integer in the range 0 < random < top. - Both random and top have num_words words. */ -int uECC_generate_random_int(uECC_word_t *random, - const uECC_word_t *top, - wordcount_t num_words); - -#endif /* uECC_ENABLE_VLI_API */ - -#ifdef __cplusplus -} /* end of extern "C" */ -#endif - -#endif /* _UECC_VLI_H_ */ diff --git a/lib/microtar.scons b/lib/microtar.scons index 6ee36d403c..54949fb42b 100644 --- a/lib/microtar.scons +++ b/lib/microtar.scons @@ -14,7 +14,7 @@ libenv.Append( CPPDEFINES=["MICROTAR_DISABLE_API_CHECKS"], ) -sources = libenv.GlobRecursive("*.c", "microtar/src") +sources = [File("microtar/src/microtar.c")] lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/misc.scons b/lib/misc.scons deleted file mode 100644 index 92fa5a106f..0000000000 --- a/lib/misc.scons +++ /dev/null @@ -1,58 +0,0 @@ -from fbt.util import GLOB_FILE_EXCLUSION - -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/fnv1a_hash", - "#/lib/heatshrink", - "#/lib/micro-ecc", - "#/lib/nanopb", - "#/lib/u8g2", - ], - CPPDEFINES=[ - "PB_ENABLE_MALLOC", - ], - SDK_HEADERS=[ - File("micro-ecc/uECC.h"), - File("nanopb/pb.h"), - File("nanopb/pb_decode.h"), - File("nanopb/pb_encode.h"), - ], -) - - -libenv = env.Clone(FW_LIB_NAME="misc") -libenv.ApplyLibFlags() - -sources = [] - -libs_recurse = [ - "micro-ecc", - "u8g2", - "update_util", -] - -for lib in libs_recurse: - sources += libenv.GlobRecursive("*.c*", lib) - -libs_plain = [ - "nanopb", -] - -for lib in libs_plain: - sources += Glob( - lib + "/*.c*", - exclude=GLOB_FILE_EXCLUSION, - source=True, - ) - -sources += Glob( - "heatshrink/heatshrink_*.c*", - exclude=GLOB_FILE_EXCLUSION, - source=True, -) - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/lib/mlib.scons b/lib/mlib.scons new file mode 100644 index 0000000000..2bdd372895 --- /dev/null +++ b/lib/mlib.scons @@ -0,0 +1,27 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/mlib", + ], + SDK_HEADERS=[ + *( + File(f"#/lib/mlib/m-{name}.h") + for name in ( + "algo", + "array", + "bptree", + "core", + "deque", + "dict", + "list", + "rbtree", + "tuple", + "variant", + ) + ), + ], + CPPDEFINES=[ + '"M_MEMORY_FULL(x)=abort()"', + ], +) diff --git a/lib/music_worker/SConscript b/lib/music_worker/SConscript index 36d01d8596..0439286ceb 100644 --- a/lib/music_worker/SConscript +++ b/lib/music_worker/SConscript @@ -7,6 +7,9 @@ env.Append( SDK_HEADERS=[ File("music_worker.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="music_worker") diff --git a/lib/nanopb.scons b/lib/nanopb.scons new file mode 100644 index 0000000000..43e828b85e --- /dev/null +++ b/lib/nanopb.scons @@ -0,0 +1,31 @@ +from fbt.util import GLOB_FILE_EXCLUSION + +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/nanopb", + ], + CPPDEFINES=[ + "PB_ENABLE_MALLOC", + ], + SDK_HEADERS=[ + File("nanopb/pb.h"), + File("nanopb/pb_decode.h"), + File("nanopb/pb_encode.h"), + ], +) + + +libenv = env.Clone(FW_LIB_NAME="nanopb") +libenv.ApplyLibFlags() + +sources = Glob( + "nanopb/*.c*", + exclude=GLOB_FILE_EXCLUSION, + source=True, +) + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 605a8639dd..21f2fb49f6 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -4,6 +4,9 @@ env.Append( CPPPATH=[ "#/lib/nfc", ], + LINT_SOURCES=[ + Dir("."), + ], SDK_HEADERS=[ # Main File("nfc.h"), diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 129dcdf5e1..8e65eca5a5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -56,7 +56,7 @@ bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { } bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) { - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t bytes_free : 3 * BITS_IN_BYTE; } MfDesfireFreeMemoryLayout; @@ -74,7 +74,7 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu } bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { bool is_master_key_changeable : 1; bool is_free_directory_list : 1; bool is_free_create_delete : 1; @@ -143,30 +143,30 @@ bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBu bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf) { bool parsed = false; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint8_t type; uint8_t comm; uint16_t access_rights; } MfDesfireFileSettingsHeader; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t size : 3 * BITS_IN_BYTE; } MfDesfireFileSettingsData; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t lo_limit; uint32_t hi_limit; uint32_t limited_credit_value; uint8_t limited_credit_enabled; } MfDesfireFileSettingsValue; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t size : 3 * BITS_IN_BYTE; uint32_t max : 3 * BITS_IN_BYTE; uint32_t cur : 3 * BITS_IN_BYTE; } MfDesfireFileSettingsRecord; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { MfDesfireFileSettingsHeader header; union { MfDesfireFileSettingsData data; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h index 747f5937ad..4786b18253 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -130,7 +130,7 @@ typedef enum { MfUltralightMirrorUidCounter, } MfUltralightMirrorConf; -typedef struct __attribute__((packed)) { +typedef struct FURI_PACKED { union { uint8_t value; struct { diff --git a/lib/print/SConscript b/lib/print/SConscript index f34c8152fa..819e60bf07 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -100,6 +100,9 @@ env.Append( SDK_HEADERS=[ File("wrappers.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="print") diff --git a/lib/pulse_reader/SConscript b/lib/pulse_reader/SConscript index f00851a20d..a134783798 100644 --- a/lib/pulse_reader/SConscript +++ b/lib/pulse_reader/SConscript @@ -7,6 +7,9 @@ env.Append( SDK_HEADERS=[ File("pulse_reader.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="pulse_reader") diff --git a/lib/qrcode/qrcode.c b/lib/qrcode/qrcode.c deleted file mode 100644 index fb5bd8a6e7..0000000000 --- a/lib/qrcode/qrcode.c +++ /dev/null @@ -1,975 +0,0 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - -#include "qrcode.h" - -#include -#include - -#pragma mark - Error Correction Lookup tables - -#if LOCK_VERSION == 0 - -static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, - 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, - 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium - {7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, - 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, - 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low - {17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, - 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, - 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High - {13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, - 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, - 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile -}; - -static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, - 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - {1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, - 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - {1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, - 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High - {1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, - 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile -}; - -static const uint16_t NUM_RAW_DATA_MODULES[40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 208, - 359, - 567, - 807, - 1079, - 1383, - 1568, - 1936, - 2336, - 2768, - 3232, - 3728, - 4256, - 4651, - 5243, - 5867, - 6523, - // 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 7211, - 7931, - 8683, - 9252, - 10068, - 10916, - 11796, - 12708, - 13652, - 14628, - 15371, - 16411, - 17483, - 18587, - // 32, 33, 34, 35, 36, 37, 38, 39, 40 - 19723, - 20891, - 22091, - 23008, - 24272, - 25568, - 26896, - 28256, - 29648}; - -// @TODO: Put other LOCK_VERSIONS here -#elif LOCK_VERSION == 3 - -static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = {26, 15, 44, 36}; - -static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = {1, 1, 2, 2}; - -static const uint16_t NUM_RAW_DATA_MODULES = 567; - -#else - -#error Unsupported LOCK_VERSION (add it...) - -#endif - -static int max(int a, int b) { - if(a > b) { - return a; - } - return b; -} - -/* -static int abs(int value) { - if (value < 0) { return -value; } - return value; -} -*/ - -#pragma mark - Mode testing and conversion - -static int8_t getAlphanumeric(char c) { - if(c >= '0' && c <= '9') { - return (c - '0'); - } - if(c >= 'A' && c <= 'Z') { - return (c - 'A' + 10); - } - - switch(c) { - case ' ': - return 36; - case '$': - return 37; - case '%': - return 38; - case '*': - return 39; - case '+': - return 40; - case '-': - return 41; - case '.': - return 42; - case '/': - return 43; - case ':': - return 44; - } - - return -1; -} - -static bool isAlphanumeric(const char* text, uint16_t length) { - while(length != 0) { - if(getAlphanumeric(text[--length]) == -1) { - return false; - } - } - return true; -} - -static bool isNumeric(const char* text, uint16_t length) { - while(length != 0) { - char c = text[--length]; - if(c < '0' || c > '9') { - return false; - } - } - return true; -} - -#pragma mark - Counting - -// We store the following tightly packed (less 8) in modeInfo -// <=9 <=26 <= 40 -// NUMERIC ( 10, 12, 14); -// ALPHANUMERIC ( 9, 11, 13); -// BYTE ( 8, 16, 16); -static char getModeBits(uint8_t version, uint8_t mode) { - // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits - // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) - unsigned int modeInfo = 0x7bbb80a; - -#if LOCK_VERSION == 0 || LOCK_VERSION > 9 - if(version > 9) { - modeInfo >>= 9; - } -#endif - -#if LOCK_VERSION == 0 || LOCK_VERSION > 26 - if(version > 26) { - modeInfo >>= 9; - } -#endif - - char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); - if(result == 15) { - result = 16; - } - - return result; -} - -#pragma mark - BitBucket - -typedef struct BitBucket { - uint32_t bitOffsetOrWidth; - uint16_t capacityBytes; - uint8_t* data; -} BitBucket; - -/* -void bb_dump(BitBucket *bitBuffer) { - printf("Buffer: "); - for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) { - printf("%02x", bitBuffer->data[i]); - if ((i % 4) == 3) { printf(" "); } - } - printf("\n"); -} -*/ - -static uint16_t bb_getGridSizeBytes(uint8_t size) { - return (((size * size) + 7) / 8); -} - -static uint16_t bb_getBufferSizeBytes(uint32_t bits) { - return ((bits + 7) / 8); -} - -static void bb_initBuffer(BitBucket* bitBuffer, uint8_t* data, int32_t capacityBytes) { - bitBuffer->bitOffsetOrWidth = 0; - bitBuffer->capacityBytes = capacityBytes; - bitBuffer->data = data; - - memset(data, 0, bitBuffer->capacityBytes); -} - -static void bb_initGrid(BitBucket* bitGrid, uint8_t* data, uint8_t size) { - bitGrid->bitOffsetOrWidth = size; - bitGrid->capacityBytes = bb_getGridSizeBytes(size); - bitGrid->data = data; - - memset(data, 0, bitGrid->capacityBytes); -} - -static void bb_appendBits(BitBucket* bitBuffer, uint32_t val, uint8_t length) { - uint32_t offset = bitBuffer->bitOffsetOrWidth; - for(int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } - bitBuffer->bitOffsetOrWidth = offset; -} -/* -void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) { - for (int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } -} -*/ -static void bb_setBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool on) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - if(on) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static void bb_invertBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool invert) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); - if(on ^ invert) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static bool bb_getBit(BitBucket* bitGrid, uint8_t x, uint8_t y) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} - -#pragma mark - Drawing Patterns - -// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical -// properties, calling applyMask(m) twice with the same value is equivalent to no change at all. -// This means it is possible to apply a mask, undo it, and try another mask. Note that a final -// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). -static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; - - for(uint8_t y = 0; y < size; y++) { - for(uint8_t x = 0; x < size; x++) { - if(bb_getBit(isFunction, x, y)) { - continue; - } - - bool invert = 0; - switch(mask) { - case 0: - invert = (x + y) % 2 == 0; - break; - case 1: - invert = y % 2 == 0; - break; - case 2: - invert = x % 3 == 0; - break; - case 3: - invert = (x + y) % 3 == 0; - break; - case 4: - invert = (x / 3 + y / 2) % 2 == 0; - break; - case 5: - invert = x * y % 2 + x * y % 3 == 0; - break; - case 6: - invert = (x * y % 2 + x * y % 3) % 2 == 0; - break; - case 7: - invert = ((x + y) % 2 + x * y % 3) % 2 == 0; - break; - } - bb_invertBit(modules, x, y, invert); - } - } -} - -static void - setFunctionModule(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y, bool on) { - bb_setBit(modules, x, y, on); - bb_setBit(isFunction, x, y, true); -} - -// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). -static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) { - uint8_t size = modules->bitOffsetOrWidth; - - for(int8_t i = -4; i <= 4; i++) { - for(int8_t j = -4; j <= 4; j++) { - uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm - int16_t xx = x + j, yy = y + i; - if(0 <= xx && xx < size && 0 <= yy && yy < size) { - setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); - } - } - } -} - -// Draws a 5*5 alignment pattern, with the center module at (x, y). -static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) { - for(int8_t i = -2; i <= 2; i++) { - for(int8_t j = -2; j <= 2; j++) { - setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1); - } - } -} - -// Draws two copies of the format bits (with its own error correction code) -// based on the given mask and this object's error correction level field. -static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ecc, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; - - // Calculate error correction code and pack bits - uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3 - uint32_t rem = data; - for(int i = 0; i < 10; i++) { - rem = (rem << 1) ^ ((rem >> 9) * 0x537); - } - - data = data << 10 | rem; - data ^= 0x5412; // uint15 - - // Draw first copy - for(uint8_t i = 0; i <= 5; i++) { - setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); - setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); - setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); - - for(int8_t i = 9; i < 15; i++) { - setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); - } - - // Draw second copy - for(int8_t i = 0; i <= 7; i++) { - setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); - } - - for(int8_t i = 8; i < 15; i++) { - setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, size - 8, true); -} - -// Draws two copies of the version bits (with its own error correction code), -// based on this object's version field (which only has an effect for 7 <= version <= 40). -static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t version) { - int8_t size = modules->bitOffsetOrWidth; - -#if LOCK_VERSION != 0 && LOCK_VERSION < 7 - return; - -#else - if(version < 7) { - return; - } - - // Calculate error correction code and pack bits - uint32_t rem = version; // version is uint6, in the range [7, 40] - for(uint8_t i = 0; i < 12; i++) { - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - } - - uint32_t data = version << 12 | rem; // uint18 - - // Draw two copies - for(uint8_t i = 0; i < 18; i++) { - bool bit = ((data >> i) & 1) != 0; - uint8_t a = size - 11 + i % 3, b = i / 3; - setFunctionModule(modules, isFunction, a, b, bit); - setFunctionModule(modules, isFunction, b, a, bit); - } - -#endif -} - -static void - drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint8_t version, uint8_t ecc) { - uint8_t size = modules->bitOffsetOrWidth; - - // Draw the horizontal and vertical timing patterns - for(uint8_t i = 0; i < size; i++) { - setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); - setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(modules, isFunction, 3, 3); - drawFinderPattern(modules, isFunction, size - 4, 3); - drawFinderPattern(modules, isFunction, 3, size - 4); - -#if LOCK_VERSION == 0 || LOCK_VERSION > 1 - - if(version > 1) { - // Draw the numerous alignment patterns - - uint8_t alignCount = version / 7 + 2; - uint8_t step; - if(version != 32) { - step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * - 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 - } else { // C-C-C-Combo breaker! - step = 26; - } - - uint8_t alignPositionIndex = alignCount - 1; - uint8_t alignPosition[alignCount]; - - alignPosition[0] = 6; - - uint8_t size = version * 4 + 17; - for(uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) { - alignPosition[alignPositionIndex--] = pos; - } - - for(uint8_t i = 0; i < alignCount; i++) { - for(uint8_t j = 0; j < alignCount; j++) { - if((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || - (i == alignCount - 1 && j == 0)) { - continue; // Skip the three finder corners - } else { - drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); - } - } - } - } - -#endif - - // Draw configuration data - drawFormatBits( - modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor - drawVersion(modules, isFunction, version); -} - -// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire -// data area of this QR Code symbol. Function modules need to be marked off before this is called. -static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBucket* codewords) { - uint32_t bitLength = codewords->bitOffsetOrWidth; - uint8_t* data = codewords->data; - - uint8_t size = modules->bitOffsetOrWidth; - - // Bit index into the data - uint32_t i = 0; - - // Do the funny zigzag scan - for(int16_t right = size - 1; right >= 1; - right -= 2) { // Index of right column in each column pair - if(right == 6) { - right = 5; - } - - for(uint8_t vert = 0; vert < size; vert++) { // Vertical counter - for(int j = 0; j < 2; j++) { - uint8_t x = right - j; // Actual x coordinate - bool upwards = ((right & 2) == 0) ^ (x < 6); - uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - if(!bb_getBit(isFunction, x, y) && i < bitLength) { - bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); - i++; - } - // If there are any remainder bits (0 to 7), they are already - // set to 0/false/white when the grid of modules was initialized - } - } - } -} - -#pragma mark - Penalty Calculation - -#define PENALTY_N1 3 -#define PENALTY_N2 3 -#define PENALTY_N3 40 -#define PENALTY_N4 10 - -// Calculates and returns the penalty score based on state of this QR Code's current modules. -// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. -// @TODO: This can be optimized by working with the bytes instead of bits. -static uint32_t getPenaltyScore(BitBucket* modules) { - uint32_t result = 0; - - uint8_t size = modules->bitOffsetOrWidth; - - // Adjacent modules in row having same color - for(uint8_t y = 0; y < size; y++) { - bool colorX = bb_getBit(modules, 0, y); - for(uint8_t x = 1, runX = 1; x < size; x++) { - bool cx = bb_getBit(modules, x, y); - if(cx != colorX) { - colorX = cx; - runX = 1; - - } else { - runX++; - if(runX == 5) { - result += PENALTY_N1; - } else if(runX > 5) { - result++; - } - } - } - } - - // Adjacent modules in column having same color - for(uint8_t x = 0; x < size; x++) { - bool colorY = bb_getBit(modules, x, 0); - for(uint8_t y = 1, runY = 1; y < size; y++) { - bool cy = bb_getBit(modules, x, y); - if(cy != colorY) { - colorY = cy; - runY = 1; - } else { - runY++; - if(runY == 5) { - result += PENALTY_N1; - } else if(runY > 5) { - result++; - } - } - } - } - - uint16_t black = 0; - for(uint8_t y = 0; y < size; y++) { - uint16_t bitsRow = 0, bitsCol = 0; - for(uint8_t x = 0; x < size; x++) { - bool color = bb_getBit(modules, x, y); - - // 2*2 blocks of modules having same color - if(x > 0 && y > 0) { - bool colorUL = bb_getBit(modules, x - 1, y - 1); - bool colorUR = bb_getBit(modules, x, y - 1); - bool colorL = bb_getBit(modules, x - 1, y); - if(color == colorUL && color == colorUR && color == colorL) { - result += PENALTY_N2; - } - } - - // Finder-like pattern in rows and columns - bitsRow = ((bitsRow << 1) & 0x7FF) | color; - bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x); - - // Needs 11 bits accumulated - if(x >= 10) { - if(bitsRow == 0x05D || bitsRow == 0x5D0) { - result += PENALTY_N3; - } - if(bitsCol == 0x05D || bitsCol == 0x5D0) { - result += PENALTY_N3; - } - } - - // Balance of black and white modules - if(color) { - black++; - } - } - } - - // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% - uint16_t total = size * size; - for(uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) { - result += PENALTY_N4; - } - - return result; -} - -#pragma mark - Reed-Solomon Generator - -static uint8_t rs_multiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication - uint16_t z = 0; - for(int8_t i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - return z; -} - -static void rs_init(uint8_t degree, uint8_t* coeff) { - memset(coeff, 0, degree); - coeff[degree - 1] = 1; - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // drop the highest term, and store the rest of the coefficients in order of descending powers. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint16_t root = 1; - for(uint8_t i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for(uint8_t j = 0; j < degree; j++) { - coeff[j] = rs_multiply(coeff[j], root); - if(j + 1 < degree) { - coeff[j] ^= coeff[j + 1]; - } - } - root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) - } -} - -static void rs_getRemainder( - uint8_t degree, - uint8_t* coeff, - uint8_t* data, - uint8_t length, - uint8_t* result, - uint8_t stride) { - // Compute the remainder by performing polynomial division - - //for (uint8_t i = 0; i < degree; i++) { result[] = 0; } - //memset(result, 0, degree); - - for(uint8_t i = 0; i < length; i++) { - uint8_t factor = data[i] ^ result[0]; - for(uint8_t j = 1; j < degree; j++) { - result[(j - 1) * stride] = result[j * stride]; - } - result[(degree - 1) * stride] = 0; - - for(uint8_t j = 0; j < degree; j++) { - result[j * stride] ^= rs_multiply(coeff[j], factor); - } - } -} - -#pragma mark - QrCode - -static int8_t encodeDataCodewords( - BitBucket* dataCodewords, - const uint8_t* text, - uint16_t length, - uint8_t version) { - int8_t mode = MODE_BYTE; - - if(isNumeric((char*)text, length)) { - mode = MODE_NUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for(uint16_t i = 0; i < length; i++) { - accumData = accumData * 10 + ((char)(text[i]) - '0'); - accumCount++; - if(accumCount == 3) { - bb_appendBits(dataCodewords, accumData, 10); - accumData = 0; - accumCount = 0; - } - } - - // 1 or 2 digits remaining - if(accumCount > 0) { - bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); - } - - } else if(isAlphanumeric((char*)text, length)) { - mode = MODE_ALPHANUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for(uint16_t i = 0; i < length; i++) { - accumData = accumData * 45 + getAlphanumeric((char)(text[i])); - accumCount++; - if(accumCount == 2) { - bb_appendBits(dataCodewords, accumData, 11); - accumData = 0; - accumCount = 0; - } - } - - // 1 character remaining - if(accumCount > 0) { - bb_appendBits(dataCodewords, accumData, 6); - } - - } else { - bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE)); - for(uint16_t i = 0; i < length; i++) { - bb_appendBits(dataCodewords, (char)(text[i]), 8); - } - } - - //bb_setBits(dataCodewords, length, 4, getModeBits(version, mode)); - - return mode; -} - -static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket* data) { - // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - -#if LOCK_VERSION == 0 - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; -#else - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; -#endif - - uint8_t blockEccLen = totalEcc / numBlocks; - uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; - uint8_t shortBlockLen = moduleCount / 8 / numBlocks; - - uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; - - uint8_t result[data->capacityBytes]; - memset(result, 0, sizeof(result)); - - uint8_t coeff[blockEccLen]; - rs_init(blockEccLen, coeff); - - uint16_t offset = 0; - uint8_t* dataBytes = data->data; - - // Interleave all short blocks - for(uint8_t i = 0; i < shortDataBlockLen; i++) { - uint16_t index = i; - uint8_t stride = shortDataBlockLen; - for(uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if(blockNum == numShortBlocks) { - stride++; - } -#endif - index += stride; - } - } - - // Version less than 5 only have short blocks -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - { - // Interleave long blocks - uint16_t index = shortDataBlockLen * (numShortBlocks + 1); - uint8_t stride = shortDataBlockLen; - for(uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - - if(blockNum == 0) { - stride++; - } - index += stride; - } - } -#endif - - // Add all ecc blocks, interleaved - uint8_t blockSize = shortDataBlockLen; - for(uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if(blockNum == numShortBlocks) { - blockSize++; - } -#endif - rs_getRemainder( - blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); - dataBytes += blockSize; - } - - memcpy(data->data, result, data->capacityBytes); - data->bitOffsetOrWidth = moduleCount; -} - -// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) -// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) -static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); - -#pragma mark - Public QRCode functions - -uint16_t qrcode_getBufferSize(uint8_t version) { - return bb_getGridSizeBytes(4 * version + 17); -} - -// @TODO: Return error if data is too big. -int8_t qrcode_initBytes( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - uint8_t* data, - uint16_t length) { - uint8_t size = version * 4 + 17; - qrcode->version = version; - qrcode->size = size; - qrcode->ecc = ecc; - qrcode->modules = modules; - - uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; - -#if LOCK_VERSION == 0 - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; - uint16_t dataCapacity = - moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; -#else - version = LOCK_VERSION; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits]; -#endif - - struct BitBucket codewords; - uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)]; - bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes)); - - // Place the data code words into the buffer - int8_t mode = encodeDataCodewords(&codewords, data, length, version); - - if(mode < 0) { - return -1; - } - qrcode->mode = mode; - - // Add terminator and pad up to a byte if applicable - uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth; - if(padding > 4) { - padding = 4; - } - bb_appendBits(&codewords, 0, padding); - bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); - - // Pad with alternate bytes until data capacity is reached - for(uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); - padByte ^= 0xEC ^ 0x11) { - bb_appendBits(&codewords, padByte, 8); - } - - BitBucket modulesGrid; - bb_initGrid(&modulesGrid, modules, size); - - BitBucket isFunctionGrid; - uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)]; - bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); - - // Draw function patterns, draw all codewords, do masking - drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); - performErrorCorrection(version, eccFormatBits, &codewords); - drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); - - // Find the best (lowest penalty) mask - uint8_t mask = 0; - int32_t minPenalty = INT32_MAX; - for(uint8_t i = 0; i < 8; i++) { - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); - applyMask(&modulesGrid, &isFunctionGrid, i); - int penalty = getPenaltyScore(&modulesGrid); - if(penalty < minPenalty) { - mask = i; - minPenalty = penalty; - } - applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR - } - - qrcode->mask = mask; - - // Overwrite old format bits - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); - - // Apply the final choice of mask - applyMask(&modulesGrid, &isFunctionGrid, mask); - - return 0; -} - -int8_t qrcode_initText( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - const char* data) { - return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); -} - -bool qrcode_getModule(QRCode* qrcode, uint8_t x, uint8_t y) { - if(x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) { - return false; - } - - uint32_t offset = y * qrcode->size + x; - return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} diff --git a/lib/qrcode/qrcode.h b/lib/qrcode/qrcode.h deleted file mode 100644 index 6d637ba04d..0000000000 --- a/lib/qrcode/qrcode.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - -#ifndef __QRCODE_H_ -#define __QRCODE_H_ - -#ifndef __cplusplus -typedef unsigned char bool; -static const bool false = 0; -static const bool true = 1; -#endif - -#include - -// QR Code Format Encoding -#define MODE_NUMERIC 0 -#define MODE_ALPHANUMERIC 1 -#define MODE_BYTE 2 - -// Error Correction Code Levels -#define ECC_LOW 0 -#define ECC_MEDIUM 1 -#define ECC_QUARTILE 2 -#define ECC_HIGH 3 - -// If set to non-zero, this library can ONLY produce QR codes at that version -// This saves a lot of dynamic memory, as the codeword tables are skipped -#ifndef LOCK_VERSION -#define LOCK_VERSION 0 -#endif - -typedef struct QRCode { - uint8_t version; - uint8_t size; - uint8_t ecc; - uint8_t mode; - uint8_t mask; - uint8_t* modules; -} QRCode; - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -uint16_t qrcode_getBufferSize(uint8_t version); - -int8_t qrcode_initText( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - const char* data); -int8_t qrcode_initBytes( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - uint8_t* data, - uint16_t length); - -bool qrcode_getModule(QRCode* qrcode, uint8_t x, uint8_t y); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* __QRCODE_H_ */ diff --git a/lib/signal_reader/SConscript b/lib/signal_reader/SConscript index ea73144201..386e3419d7 100644 --- a/lib/signal_reader/SConscript +++ b/lib/signal_reader/SConscript @@ -7,11 +7,14 @@ env.Append( SDK_HEADERS=[ File("signal_reader.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="signal_reader") libenv.ApplyLibFlags() -libenv.Append(CCFLAGS=["-O3", "-funroll-loops", "-Ofast"]) +libenv.AppendUnique(CCFLAGS=["-O3", "-funroll-loops", "-Ofast"]) sources = libenv.GlobRecursive("*.c*") diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 35850aa83e..d0bc2a2543 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -4,6 +4,9 @@ env.Append( CPPPATH=[ "#/lib/subghz", ], + LINT_SOURCES=[ + Dir("."), + ], SDK_HEADERS=[ File("environment.h"), File("receiver.h"), diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index de77f0ccf2..14f8de0646 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -7,6 +7,9 @@ env.Append( CPPPATH=[ "#/lib/toolbox", ], + LINT_SOURCES=[ + Dir("."), + ], SDK_HEADERS=[ File("api_lock.h"), File("compress.h"), @@ -14,10 +17,8 @@ env.Append( File("manchester_encoder.h"), File("path.h"), File("name_generator.h"), - File("sha256.h"), File("crc32_calc.h"), File("dir_walk.h"), - File("md5.h"), File("args.h"), File("saved_struct.h"), File("version.h"), diff --git a/lib/toolbox/md5.c b/lib/toolbox/md5.c deleted file mode 100644 index a907d52e3b..0000000000 --- a/lib/toolbox/md5.c +++ /dev/null @@ -1,299 +0,0 @@ -/******************************************************************************* -* Portions COPYRIGHT 2015 STMicroelectronics * -* Portions Copyright (C) 2006-2013, Brainspark B.V. * -*******************************************************************************/ - -/* - * RFC 1321 compliant MD5 implementation - * - * Copyright (C) 2006-2013, Brainspark B.V. - * - * This file is part of PolarSSL (http://www.polarssl.org) - * Lead Maintainer: Paul Bakker - * - * All rights reserved. - * - * 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 2 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -/* - * The MD5 algorithm was designed by Ron Rivest in 1991. - * - * http://www.ietf.org/rfc/rfc1321.txt - */ - -/** - ****************************************************************************** - * @file md5.c - * @author MCD Application Team - * @brief This file has been modified to support the hardware Cryptographic and - * Hash processors embedded in STM32F415xx/417xx/437xx/439xx/756xx devices. - * This support is activated by defining the "USE_STM32F4XX_HW_CRYPTO" - * or "USE_STM32F7XX_HW_CRYPTO" macro in PolarSSL config.h file. - ****************************************************************************** - * @attention - * - * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/software_license_agreement_liberty_v2 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ****************************************************************************** - */ - -#include "md5.h" - -/* - * 32-bit integer manipulation macros (little endian) - */ -#ifndef GET_UINT32_LE -#define GET_UINT32_LE(n, b, i) \ - { \ - (n) = ((uint32_t)(b)[(i)]) | ((uint32_t)(b)[(i) + 1] << 8) | \ - ((uint32_t)(b)[(i) + 2] << 16) | ((uint32_t)(b)[(i) + 3] << 24); \ - } -#endif - -#ifndef PUT_UINT32_LE -#define PUT_UINT32_LE(n, b, i) \ - { \ - (b)[(i)] = (unsigned char)((n)); \ - (b)[(i) + 1] = (unsigned char)((n) >> 8); \ - (b)[(i) + 2] = (unsigned char)((n) >> 16); \ - (b)[(i) + 3] = (unsigned char)((n) >> 24); \ - } -#endif - -/* - * MD5 context setup - */ -void md5_starts(md5_context* ctx) { - ctx->total[0] = 0; - ctx->total[1] = 0; - - ctx->state[0] = 0x67452301; - ctx->state[1] = 0xEFCDAB89; - ctx->state[2] = 0x98BADCFE; - ctx->state[3] = 0x10325476; -} - -void md5_process(md5_context* ctx, const unsigned char data[64]) { - uint32_t X[16], A, B, C, D; - - GET_UINT32_LE(X[0], data, 0); - GET_UINT32_LE(X[1], data, 4); - GET_UINT32_LE(X[2], data, 8); - GET_UINT32_LE(X[3], data, 12); - GET_UINT32_LE(X[4], data, 16); - GET_UINT32_LE(X[5], data, 20); - GET_UINT32_LE(X[6], data, 24); - GET_UINT32_LE(X[7], data, 28); - GET_UINT32_LE(X[8], data, 32); - GET_UINT32_LE(X[9], data, 36); - GET_UINT32_LE(X[10], data, 40); - GET_UINT32_LE(X[11], data, 44); - GET_UINT32_LE(X[12], data, 48); - GET_UINT32_LE(X[13], data, 52); - GET_UINT32_LE(X[14], data, 56); - GET_UINT32_LE(X[15], data, 60); - -#define S(x, n) (((x) << (n)) | (((x)&0xFFFFFFFF) >> (32 - (n)))) - -#define P(a, b, c, d, k, s, t) \ - { \ - a += F(b, c, d) + X[k] + t; \ - a = S(a, s) + b; \ - } - - A = ctx->state[0]; - B = ctx->state[1]; - C = ctx->state[2]; - D = ctx->state[3]; - -#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) - - P(A, B, C, D, 0, 7, 0xD76AA478); - P(D, A, B, C, 1, 12, 0xE8C7B756); - P(C, D, A, B, 2, 17, 0x242070DB); - P(B, C, D, A, 3, 22, 0xC1BDCEEE); - P(A, B, C, D, 4, 7, 0xF57C0FAF); - P(D, A, B, C, 5, 12, 0x4787C62A); - P(C, D, A, B, 6, 17, 0xA8304613); - P(B, C, D, A, 7, 22, 0xFD469501); - P(A, B, C, D, 8, 7, 0x698098D8); - P(D, A, B, C, 9, 12, 0x8B44F7AF); - P(C, D, A, B, 10, 17, 0xFFFF5BB1); - P(B, C, D, A, 11, 22, 0x895CD7BE); - P(A, B, C, D, 12, 7, 0x6B901122); - P(D, A, B, C, 13, 12, 0xFD987193); - P(C, D, A, B, 14, 17, 0xA679438E); - P(B, C, D, A, 15, 22, 0x49B40821); - -#undef F - -#define F(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) - - P(A, B, C, D, 1, 5, 0xF61E2562); - P(D, A, B, C, 6, 9, 0xC040B340); - P(C, D, A, B, 11, 14, 0x265E5A51); - P(B, C, D, A, 0, 20, 0xE9B6C7AA); - P(A, B, C, D, 5, 5, 0xD62F105D); - P(D, A, B, C, 10, 9, 0x02441453); - P(C, D, A, B, 15, 14, 0xD8A1E681); - P(B, C, D, A, 4, 20, 0xE7D3FBC8); - P(A, B, C, D, 9, 5, 0x21E1CDE6); - P(D, A, B, C, 14, 9, 0xC33707D6); - P(C, D, A, B, 3, 14, 0xF4D50D87); - P(B, C, D, A, 8, 20, 0x455A14ED); - P(A, B, C, D, 13, 5, 0xA9E3E905); - P(D, A, B, C, 2, 9, 0xFCEFA3F8); - P(C, D, A, B, 7, 14, 0x676F02D9); - P(B, C, D, A, 12, 20, 0x8D2A4C8A); - -#undef F - -#define F(x, y, z) ((x) ^ (y) ^ (z)) - - P(A, B, C, D, 5, 4, 0xFFFA3942); - P(D, A, B, C, 8, 11, 0x8771F681); - P(C, D, A, B, 11, 16, 0x6D9D6122); - P(B, C, D, A, 14, 23, 0xFDE5380C); - P(A, B, C, D, 1, 4, 0xA4BEEA44); - P(D, A, B, C, 4, 11, 0x4BDECFA9); - P(C, D, A, B, 7, 16, 0xF6BB4B60); - P(B, C, D, A, 10, 23, 0xBEBFBC70); - P(A, B, C, D, 13, 4, 0x289B7EC6); - P(D, A, B, C, 0, 11, 0xEAA127FA); - P(C, D, A, B, 3, 16, 0xD4EF3085); - P(B, C, D, A, 6, 23, 0x04881D05); - P(A, B, C, D, 9, 4, 0xD9D4D039); - P(D, A, B, C, 12, 11, 0xE6DB99E5); - P(C, D, A, B, 15, 16, 0x1FA27CF8); - P(B, C, D, A, 2, 23, 0xC4AC5665); - -#undef F - -#define F(x, y, z) ((y) ^ ((x) | ~(z))) - - P(A, B, C, D, 0, 6, 0xF4292244); - P(D, A, B, C, 7, 10, 0x432AFF97); - P(C, D, A, B, 14, 15, 0xAB9423A7); - P(B, C, D, A, 5, 21, 0xFC93A039); - P(A, B, C, D, 12, 6, 0x655B59C3); - P(D, A, B, C, 3, 10, 0x8F0CCC92); - P(C, D, A, B, 10, 15, 0xFFEFF47D); - P(B, C, D, A, 1, 21, 0x85845DD1); - P(A, B, C, D, 8, 6, 0x6FA87E4F); - P(D, A, B, C, 15, 10, 0xFE2CE6E0); - P(C, D, A, B, 6, 15, 0xA3014314); - P(B, C, D, A, 13, 21, 0x4E0811A1); - P(A, B, C, D, 4, 6, 0xF7537E82); - P(D, A, B, C, 11, 10, 0xBD3AF235); - P(C, D, A, B, 2, 15, 0x2AD7D2BB); - P(B, C, D, A, 9, 21, 0xEB86D391); - -#undef F - - ctx->state[0] += A; - ctx->state[1] += B; - ctx->state[2] += C; - ctx->state[3] += D; -} - -/* - * MD5 process buffer - */ -void md5_update(md5_context* ctx, const unsigned char* input, size_t ilen) { - size_t fill; - uint32_t left; - - if(ilen <= 0) return; - - left = ctx->total[0] & 0x3F; - fill = 64 - left; - - ctx->total[0] += (uint32_t)ilen; - ctx->total[0] &= 0xFFFFFFFF; - - if(ctx->total[0] < (uint32_t)ilen) ctx->total[1]++; - - if(left && ilen >= fill) { - memcpy((void*)(ctx->buffer + left), input, fill); - md5_process(ctx, ctx->buffer); - input += fill; - ilen -= fill; - left = 0; - } - - while(ilen >= 64) { - md5_process(ctx, input); - input += 64; - ilen -= 64; - } - - if(ilen > 0) { - memcpy((void*)(ctx->buffer + left), input, ilen); - } -} - -static const unsigned char md5_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -/* - * MD5 final digest - */ -void md5_finish(md5_context* ctx, unsigned char output[16]) { - uint32_t last, padn; - uint32_t high, low; - unsigned char msglen[8]; - - high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); - low = (ctx->total[0] << 3); - - PUT_UINT32_LE(low, msglen, 0); - PUT_UINT32_LE(high, msglen, 4); - - last = ctx->total[0] & 0x3F; - padn = (last < 56) ? (56 - last) : (120 - last); - - md5_update(ctx, md5_padding, padn); - md5_update(ctx, msglen, 8); - - PUT_UINT32_LE(ctx->state[0], output, 0); - PUT_UINT32_LE(ctx->state[1], output, 4); - PUT_UINT32_LE(ctx->state[2], output, 8); - PUT_UINT32_LE(ctx->state[3], output, 12); -} - -/* - * output = MD5( input buffer ) - */ -void md5(const unsigned char* input, size_t ilen, unsigned char output[16]) { - md5_context ctx; - - md5_starts(&ctx); - md5_update(&ctx, input, ilen); - md5_finish(&ctx, output); - - memset(&ctx, 0, sizeof(md5_context)); //-V597 -} diff --git a/lib/toolbox/md5.h b/lib/toolbox/md5.h deleted file mode 100644 index fe53db8d3e..0000000000 --- a/lib/toolbox/md5.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - * \file md5.h - * - * \brief MD5 message digest algorithm (hash function) - * - * Copyright (C) 2006-2013, Brainspark B.V. - * - * This file is part of PolarSSL (http://www.polarssl.org) - * Lead Maintainer: Paul Bakker - * - * All rights reserved. - * - * 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 2 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include - -/** - * \brief MD5 context structure - */ -typedef struct { - uint32_t total[2]; /*!< number of bytes processed */ - uint32_t state[4]; /*!< intermediate digest state */ - unsigned char buffer[64]; /*!< data block being processed */ -} md5_context; - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \brief MD5 context setup - * - * \param ctx context to be initialized - */ -void md5_starts(md5_context* ctx); - -/** - * \brief MD5 process buffer - * - * \param ctx MD5 context - * \param input buffer holding the data - * \param ilen length of the input data - */ -void md5_update(md5_context* ctx, const unsigned char* input, size_t ilen); - -/** - * \brief MD5 final digest - * - * \param ctx MD5 context - * \param output MD5 checksum result - */ -void md5_finish(md5_context* ctx, unsigned char output[16]); - -/* Internal use */ -void md5_process(md5_context* ctx, const unsigned char data[64]); - -/** - * \brief Output = MD5( input buffer ) - * - * \param input buffer holding the data - * \param ilen length of the input data - * \param output MD5 checksum result - */ -void md5(const unsigned char* input, size_t ilen, unsigned char output[16]); - -#ifdef __cplusplus -} -#endif diff --git a/lib/toolbox/md5_calc.c b/lib/toolbox/md5_calc.c index 7f335a33f2..59c9403e81 100644 --- a/lib/toolbox/md5_calc.c +++ b/lib/toolbox/md5_calc.c @@ -1,24 +1,38 @@ -#include "md5.h" #include "md5_calc.h" +#include +#include +#include + bool md5_calc_file(File* file, const char* path, unsigned char output[16], FS_Error* file_error) { - bool result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); + if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + if(file_error != NULL) { + *file_error = storage_file_get_error(file); + } + return false; + } - if(result) { - const size_t size_to_read = 512; - uint8_t* data = malloc(size_to_read); - md5_context* md5_ctx = malloc(sizeof(md5_context)); - - md5_starts(md5_ctx); - while(true) { - size_t read_size = storage_file_read(file, data, size_to_read); - if(read_size == 0) break; - md5_update(md5_ctx, data, read_size); + const size_t size_to_read = 512; + uint8_t* data = malloc(size_to_read); + bool result = true; + + mbedtls_md5_context* md5_ctx = malloc(sizeof(mbedtls_md5_context)); + mbedtls_md5_init(md5_ctx); + mbedtls_md5_starts(md5_ctx); + while(true) { + size_t read_size = storage_file_read(file, data, size_to_read); + if(storage_file_get_error(file) != FSE_OK) { + result = false; + break; + } + if(read_size == 0) { + break; } - md5_finish(md5_ctx, output); - free(md5_ctx); - free(data); + mbedtls_md5_update(md5_ctx, data, read_size); } + mbedtls_md5_finish(md5_ctx, output); + free(md5_ctx); + free(data); if(file_error != NULL) { *file_error = storage_file_get_error(file); diff --git a/lib/toolbox/sha256.c b/lib/toolbox/sha256.c deleted file mode 100644 index ff4984439d..0000000000 --- a/lib/toolbox/sha256.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * sha256.c -- Compute SHA-256 hash - * - * Just for little endian architecture. - * - * Code taken from: - * http://gladman.plushost.co.uk/oldsite/cryptography_technology/sha/index.php - * - * File names are sha2.c, sha2.h, brg_types.h, brg_endian.h - * in the archive sha2-07-01-07.zip. - * - * Code is modified in the style of PolarSSL API. - * - * See original copyright notice below. - */ -/* - --------------------------------------------------------------------------- - Copyright (c) 2002, Dr Brian Gladman, Worcester, UK. All rights reserved. - - LICENSE TERMS - - The free distribution and use of this software in both source and binary - form is allowed (with or without changes) provided that: - - 1. distributions of this source code include the above copyright - notice, this list of conditions and the following disclaimer; - - 2. distributions in binary form include the above copyright - notice, this list of conditions and the following disclaimer - in the documentation and/or other associated materials; - - 3. the copyright holder's name is not used to endorse products - built using this software without specific written permission. - - ALTERNATIVELY, provided that this notice is retained in full, this product - may be distributed under the terms of the GNU General Public License (GPL), - in which case the provisions of the GPL apply INSTEAD OF those given above. - - DISCLAIMER - - This software is provided 'as is' with no explicit or implied warranties - in respect of its properties, including, but not limited to, correctness - and/or fitness for purpose. - --------------------------------------------------------------------------- - Issue Date: 01/08/2005 -*/ - -#include -#include -#include -#include "sha256.h" - -#define SHA256_MASK (SHA256_BLOCK_SIZE - 1) - -static void memcpy_output_bswap32(unsigned char* dst, const uint32_t* p) { - int i; - uint32_t q = 0; - - for(i = 0; i < 32; i++) { - if((i & 3) == 0) q = __builtin_bswap32(p[i >> 2]); /* bswap32 is GCC extention */ - dst[i] = q >> ((i & 3) * 8); - } -} - -#define rotr32(x, n) (((x) >> n) | ((x) << (32 - (n)))) - -#define ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) -#define maj(x, y, z) (((x) & (y)) | ((z) & ((x) ^ (y)))) - -/* round transforms for SHA256 compression functions */ -#define vf(n, i) v[((n) - (i)) & 7] - -#define hf(i) (p[(i)&15] += g_1(p[((i) + 14) & 15]) + p[((i) + 9) & 15] + g_0(p[((i) + 1) & 15])) - -#define v_cycle0(i) \ - p[i] = __builtin_bswap32(p[i]); \ - vf(7, i) += p[i] + k_0[i] + s_1(vf(4, i)) + ch(vf(4, i), vf(5, i), vf(6, i)); \ - vf(3, i) += vf(7, i); \ - vf(7, i) += s_0(vf(0, i)) + maj(vf(0, i), vf(1, i), vf(2, i)) - -#define v_cycle(i, j) \ - vf(7, i) += hf(i) + k_0[i + j] + s_1(vf(4, i)) + ch(vf(4, i), vf(5, i), vf(6, i)); \ - vf(3, i) += vf(7, i); \ - vf(7, i) += s_0(vf(0, i)) + maj(vf(0, i), vf(1, i), vf(2, i)) - -#define s_0(x) (rotr32((x), 2) ^ rotr32((x), 13) ^ rotr32((x), 22)) -#define s_1(x) (rotr32((x), 6) ^ rotr32((x), 11) ^ rotr32((x), 25)) -#define g_0(x) (rotr32((x), 7) ^ rotr32((x), 18) ^ ((x) >> 3)) -#define g_1(x) (rotr32((x), 17) ^ rotr32((x), 19) ^ ((x) >> 10)) -#define k_0 k256 - -static const uint32_t k256[64] = { - 0X428A2F98, 0X71374491, 0XB5C0FBCF, 0XE9B5DBA5, 0X3956C25B, 0X59F111F1, 0X923F82A4, 0XAB1C5ED5, - 0XD807AA98, 0X12835B01, 0X243185BE, 0X550C7DC3, 0X72BE5D74, 0X80DEB1FE, 0X9BDC06A7, 0XC19BF174, - 0XE49B69C1, 0XEFBE4786, 0X0FC19DC6, 0X240CA1CC, 0X2DE92C6F, 0X4A7484AA, 0X5CB0A9DC, 0X76F988DA, - 0X983E5152, 0XA831C66D, 0XB00327C8, 0XBF597FC7, 0XC6E00BF3, 0XD5A79147, 0X06CA6351, 0X14292967, - 0X27B70A85, 0X2E1B2138, 0X4D2C6DFC, 0X53380D13, 0X650A7354, 0X766A0ABB, 0X81C2C92E, 0X92722C85, - 0XA2BFE8A1, 0XA81A664B, 0XC24B8B70, 0XC76C51A3, 0XD192E819, 0XD6990624, 0XF40E3585, 0X106AA070, - 0X19A4C116, 0X1E376C08, 0X2748774C, 0X34B0BCB5, 0X391C0CB3, 0X4ED8AA4A, 0X5B9CCA4F, 0X682E6FF3, - 0X748F82EE, 0X78A5636F, 0X84C87814, 0X8CC70208, 0X90BEFFFA, 0XA4506CEB, 0XBEF9A3F7, 0XC67178F2, -}; - -void sha256_process(sha256_context* ctx) { - uint32_t i; - uint32_t* p = ctx->wbuf; - uint32_t v[8]; - - memcpy(v, ctx->state, 8 * sizeof(uint32_t)); - - v_cycle0(0); - v_cycle0(1); - v_cycle0(2); - v_cycle0(3); - v_cycle0(4); - v_cycle0(5); - v_cycle0(6); - v_cycle0(7); - v_cycle0(8); - v_cycle0(9); - v_cycle0(10); - v_cycle0(11); - v_cycle0(12); - v_cycle0(13); - v_cycle0(14); - v_cycle0(15); - - for(i = 16; i < 64; i += 16) { - v_cycle(0, i); - v_cycle(1, i); - v_cycle(2, i); - v_cycle(3, i); - v_cycle(4, i); - v_cycle(5, i); - v_cycle(6, i); - v_cycle(7, i); - v_cycle(8, i); - v_cycle(9, i); - v_cycle(10, i); - v_cycle(11, i); - v_cycle(12, i); - v_cycle(13, i); - v_cycle(14, i); - v_cycle(15, i); - } - - ctx->state[0] += v[0]; - ctx->state[1] += v[1]; - ctx->state[2] += v[2]; - ctx->state[3] += v[3]; - ctx->state[4] += v[4]; - ctx->state[5] += v[5]; - ctx->state[6] += v[6]; - ctx->state[7] += v[7]; -} - -void sha256_update(sha256_context* ctx, const unsigned char* input, unsigned int ilen) { - uint32_t left = (ctx->total[0] & SHA256_MASK); - uint32_t fill = SHA256_BLOCK_SIZE - left; - - ctx->total[0] += ilen; - if(ctx->total[0] < ilen) ctx->total[1]++; - - while(ilen >= fill) { - memcpy(((unsigned char*)ctx->wbuf) + left, input, fill); - sha256_process(ctx); - input += fill; - ilen -= fill; - left = 0; - fill = SHA256_BLOCK_SIZE; - } - - memcpy(((unsigned char*)ctx->wbuf) + left, input, ilen); -} - -void sha256_finish(sha256_context* ctx, unsigned char output[32]) { - uint32_t last = (ctx->total[0] & SHA256_MASK); - - ctx->wbuf[last >> 2] = __builtin_bswap32(ctx->wbuf[last >> 2]); - ctx->wbuf[last >> 2] &= 0xffffff80UL << (8 * (~last & 3)); - ctx->wbuf[last >> 2] |= 0x00000080UL << (8 * (~last & 3)); - ctx->wbuf[last >> 2] = __builtin_bswap32(ctx->wbuf[last >> 2]); - - if(last > SHA256_BLOCK_SIZE - 9) { - if(last < 60) ctx->wbuf[15] = 0; - sha256_process(ctx); - last = 0; - } else - last = (last >> 2) + 1; - - while(last < 14) ctx->wbuf[last++] = 0; - - ctx->wbuf[14] = __builtin_bswap32((ctx->total[0] >> 29) | (ctx->total[1] << 3)); - ctx->wbuf[15] = __builtin_bswap32(ctx->total[0] << 3); - sha256_process(ctx); - - memcpy_output_bswap32(output, ctx->state); - memset(ctx, 0, sizeof(sha256_context)); -} - -static const uint32_t initial_state[8] = { - 0x6a09e667, - 0xbb67ae85, - 0x3c6ef372, - 0xa54ff53a, - 0x510e527f, - 0x9b05688c, - 0x1f83d9ab, - 0x5be0cd19}; - -void sha256_start(sha256_context* ctx) { - ctx->total[0] = ctx->total[1] = 0; - memcpy(ctx->state, initial_state, 8 * sizeof(uint32_t)); -} - -void sha256(const unsigned char* input, unsigned int ilen, unsigned char output[32]) { - sha256_context ctx; - - sha256_start(&ctx); - sha256_update(&ctx, input, ilen); - sha256_finish(&ctx, output); -} diff --git a/lib/toolbox/sha256.h b/lib/toolbox/sha256.h deleted file mode 100644 index c544d3ebb1..0000000000 --- a/lib/toolbox/sha256.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define SHA256_DIGEST_SIZE 32 -#define SHA256_BLOCK_SIZE 64 - -typedef struct { - uint32_t total[2]; - uint32_t state[8]; - uint32_t wbuf[16]; -} sha256_context; - -void sha256(const unsigned char* input, unsigned int ilen, unsigned char output[32]); -void sha256_start(sha256_context* ctx); -void sha256_finish(sha256_context* ctx, unsigned char output[32]); -void sha256_update(sha256_context* ctx, const unsigned char* input, unsigned int ilen); -void sha256_process(sha256_context* ctx); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/u8g2/SConscript b/lib/u8g2/SConscript new file mode 100644 index 0000000000..dacdfbacb4 --- /dev/null +++ b/lib/u8g2/SConscript @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/u8g2", + ], + LINT_SOURCES=[ + Dir("."), + ], +) + + +libenv = env.Clone(FW_LIB_NAME="u8g2") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/update_util/SConscript b/lib/update_util/SConscript new file mode 100644 index 0000000000..c818973d97 --- /dev/null +++ b/lib/update_util/SConscript @@ -0,0 +1,16 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], +) + +libenv = env.Clone(FW_LIB_NAME="update_util") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index dadf6dc0c8..7e0aec5ea6 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -125,7 +125,6 @@ def LoadAppManifest(env, entry): app_manifest_file_path = manifest_glob[0].rfile().abspath env["APPMGR"].load_manifest(app_manifest_file_path, entry) - env.Append(PY_LINT_SOURCES=[app_manifest_file_path]) except FlipperManifestException as e: if not GetOption("silent"): warn(WarningOnByDefault, str(e)) diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index e64167b3dc..0dd5d0feb9 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -32,9 +32,9 @@ def generate(env): "${TARGET.dir.posix}", "--dir", "${ROOT_DIR}", - "${VERSIONCOMSTR}", ] - ] + ], + "${VERSIONCOMSTR}", ), emitter=_version_emitter, ), diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7835718def..cd89b554af 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,47.0,, +Version,+,48.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -77,8 +77,13 @@ Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, +Header,+,lib/mbedtls/include/mbedtls/ecp.h,, +Header,+,lib/mbedtls/include/mbedtls/md.h,, +Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, -Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mbedtls/include/mbedtls/sha256.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -136,13 +141,11 @@ Header,+,lib/toolbox/float_tools.h,, Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, -Header,+,lib/toolbox/md5.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, -Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, @@ -452,7 +455,6 @@ Function,-,_system_r,int,"_reent*, const char*" Function,-,_tempnam_r,char*,"_reent*, const char*, const char*" Function,-,_tmpfile_r,FILE*,_reent* Function,-,_tmpnam_r,char*,"_reent*, char*" -Function,-,_tzset_r,void,_reent* Function,-,_ungetc_r,int,"_reent*, int, FILE*" Function,-,_unsetenv_r,int,"_reent*, const char*" Function,-,_vasiprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" @@ -499,8 +501,6 @@ Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" -Function,-,asctime,char*,const tm* -Function,-,asctime_r,char*,"const tm*, char*" Function,-,asin,double,double Function,-,asinf,float,float Function,-,asinh,double,double @@ -611,7 +611,7 @@ Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" Function,-,bzero,void,"void*, size_t" -Function,-,calloc,void*,"size_t, size_t" +Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* Function,+,canvas_current_font_height,uint8_t,const Canvas* @@ -665,7 +665,6 @@ Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" -Function,-,clock,clock_t, Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -689,8 +688,6 @@ Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* -Function,-,ctime,char*,const time_t* -Function,-,ctime_r,char*,"const time_t*, char*" Function,-,cuserid,char*,char* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* @@ -716,7 +713,6 @@ Function,+,dialog_message_set_icon,void,"DialogMessage*, const Icon*, uint8_t, u Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" -Function,-,difftime,double,"time_t, time_t" Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* @@ -1518,8 +1514,6 @@ Function,-,getenv,char*,const char* Function,-,gets,char*,char* Function,-,getsubopt,int,"char**, char**, char**" Function,-,getw,int,FILE* -Function,-,gmtime,tm*,const time_t* -Function,-,gmtime_r,tm*,"const time_t*, tm*" Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* @@ -1637,8 +1631,6 @@ Function,+,locale_get_time_format,LocaleTimeFormat, Function,+,locale_set_date_format,void,LocaleDateFormat Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits Function,+,locale_set_time_format,void,LocaleTimeFormat -Function,-,localtime,tm*,const time_t* -Function,-,localtime_r,tm*,"const time_t*, tm*" Function,-,log,double,double Function,-,log10,double,double Function,-,log10f,float,float @@ -1682,29 +1674,167 @@ Function,-,mbedtls_des_init,void,mbedtls_des_context* Function,-,mbedtls_des_key_check_key_parity,int,const unsigned char[8] Function,-,mbedtls_des_key_check_weak,int,const unsigned char[8] Function,-,mbedtls_des_key_set_parity,void,unsigned char[8] -Function,-,mbedtls_des_self_test,int,int Function,-,mbedtls_des_setkey,void,"uint32_t[32], const unsigned char[8]" Function,-,mbedtls_des_setkey_dec,int,"mbedtls_des_context*, const unsigned char[8]" Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_ecdh_calc_secret,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" +Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_make_public,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_read_params,int,"mbedtls_ecdh_context*, const unsigned char**, const unsigned char*" +Function,-,mbedtls_ecdh_read_public,int,"mbedtls_ecdh_context*, const unsigned char*, size_t" +Function,-,mbedtls_ecdh_setup,int,"mbedtls_ecdh_context*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecdsa_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdsa_free,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_from_keypair,int,"mbedtls_ecdsa_context*, const mbedtls_ecp_keypair*" +Function,-,mbedtls_ecdsa_genkey,int,"mbedtls_ecdsa_context*, mbedtls_ecp_group_id, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_init,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_read_signature,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t" +Function,-,mbedtls_ecdsa_read_signature_restartable,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_sign,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_sign_restartable,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_verify,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_ecdsa_verify_restartable,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_write_signature,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_write_signature_restartable,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecp_check_privkey,int,"const mbedtls_ecp_group*, const mbedtls_mpi*" +Function,-,mbedtls_ecp_check_pub_priv,int,"const mbedtls_ecp_keypair*, const mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_check_pubkey,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_copy,int,"mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_curve_info_from_grp_id,const mbedtls_ecp_curve_info*,mbedtls_ecp_group_id +Function,-,mbedtls_ecp_curve_info_from_name,const mbedtls_ecp_curve_info*,const char* +Function,-,mbedtls_ecp_curve_info_from_tls_id,const mbedtls_ecp_curve_info*,uint16_t +Function,-,mbedtls_ecp_curve_list,const mbedtls_ecp_curve_info*, +Function,-,mbedtls_ecp_export,int,"const mbedtls_ecp_keypair*, mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*" +Function,-,mbedtls_ecp_gen_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair_base,int,"mbedtls_ecp_group*, const mbedtls_ecp_point*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_privkey,int,"const mbedtls_ecp_group*, mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_get_type,mbedtls_ecp_curve_type,const mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_copy,int,"mbedtls_ecp_group*, const mbedtls_ecp_group*" +Function,-,mbedtls_ecp_group_free,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, +Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_muladd,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_muladd_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_point_cmp,int,"const mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_point_free,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_init,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" +Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" -Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" +Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" +Function,-,mbedtls_md,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md5,int,"const unsigned char*, size_t, unsigned char[16]" +Function,-,mbedtls_md5_clone,void,"mbedtls_md5_context*, const mbedtls_md5_context*" +Function,-,mbedtls_md5_finish,int,"mbedtls_md5_context*, unsigned char[16]" +Function,-,mbedtls_md5_free,void,mbedtls_md5_context* +Function,-,mbedtls_md5_init,void,mbedtls_md5_context* +Function,-,mbedtls_md5_starts,int,mbedtls_md5_context* +Function,-,mbedtls_md5_update,int,"mbedtls_md5_context*, const unsigned char*, size_t" +Function,-,mbedtls_md_clone,int,"mbedtls_md_context_t*, const mbedtls_md_context_t*" +Function,-,mbedtls_md_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_free,void,mbedtls_md_context_t* +Function,-,mbedtls_md_get_name,const char*,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_size,unsigned char,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_type,mbedtls_md_type_t,const mbedtls_md_info_t* +Function,-,mbedtls_md_hmac,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md_hmac_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_hmac_reset,int,mbedtls_md_context_t* +Function,-,mbedtls_md_hmac_starts,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_hmac_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_info_from_ctx,const mbedtls_md_info_t*,const mbedtls_md_context_t* +Function,-,mbedtls_md_info_from_string,const mbedtls_md_info_t*,const char* +Function,-,mbedtls_md_info_from_type,const mbedtls_md_info_t*,mbedtls_md_type_t +Function,-,mbedtls_md_init,void,mbedtls_md_context_t* +Function,-,mbedtls_md_list,const int*, +Function,-,mbedtls_md_setup,int,"mbedtls_md_context_t*, const mbedtls_md_info_t*, int" +Function,-,mbedtls_md_starts,int,mbedtls_md_context_t* +Function,-,mbedtls_md_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_add_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_add_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_add_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_bitlen,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_cmp_abs,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_cmp_int,int,"const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_cmp_mpi,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_copy,int,"mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_div_int,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_div_mpi,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_exp_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_fill_random,int,"mbedtls_mpi*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_free,void,mbedtls_mpi* +Function,-,mbedtls_mpi_gcd,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_gen_prime,int,"mbedtls_mpi*, size_t, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_get_bit,int,"const mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_grow,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_init,void,mbedtls_mpi* +Function,-,mbedtls_mpi_inv_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_is_prime_ext,int,"const mbedtls_mpi*, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_lsb,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_lset,int,"mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_lt_mpi_ct,int,"const mbedtls_mpi*, const mbedtls_mpi*, unsigned*" +Function,-,mbedtls_mpi_mod_int,int,"mbedtls_mpi_uint*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_mod_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_mul_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_uint" +Function,-,mbedtls_mpi_mul_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_random,int,"mbedtls_mpi*, mbedtls_mpi_sint, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_read_binary,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_binary_le,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_string,int,"mbedtls_mpi*, int, const char*" +Function,-,mbedtls_mpi_safe_cond_assign,int,"mbedtls_mpi*, const mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_safe_cond_swap,int,"mbedtls_mpi*, mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_set_bit,int,"mbedtls_mpi*, size_t, unsigned char" +Function,-,mbedtls_mpi_shift_l,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shift_r,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shrink,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_size,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_sub_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_sub_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_sub_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_swap,void,"mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_write_binary,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_binary_le,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_string,int,"const mbedtls_mpi*, int, char*, size_t, size_t*" Function,-,mbedtls_platform_zeroize,void,"void*, size_t" Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* Function,-,mbedtls_sha1_init,void,mbedtls_sha1_context* -Function,-,mbedtls_sha1_self_test,int,int Function,-,mbedtls_sha1_starts,int,mbedtls_sha1_context* Function,-,mbedtls_sha1_update,int,"mbedtls_sha1_context*, const unsigned char*, size_t" +Function,-,mbedtls_sha256,int,"const unsigned char*, size_t, unsigned char*, int" +Function,-,mbedtls_sha256_clone,void,"mbedtls_sha256_context*, const mbedtls_sha256_context*" +Function,-,mbedtls_sha256_finish,int,"mbedtls_sha256_context*, unsigned char*" +Function,-,mbedtls_sha256_free,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_init,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_starts,int,"mbedtls_sha256_context*, int" +Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned char*, size_t" Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" -Function,+,md5,void,"const unsigned char*, size_t, unsigned char[16]" -Function,+,md5_finish,void,"md5_context*, unsigned char[16]" -Function,+,md5_process,void,"md5_context*, const unsigned char[64]" -Function,+,md5_starts,void,md5_context* -Function,+,md5_update,void,"md5_context*, const unsigned char*, size_t" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -1737,7 +1867,6 @@ Function,-,mkostemps,int,"char*, int, int" Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* -Function,-,mktime,time_t,tm* Function,-,modf,double,"double, double*" Function,-,modff,float,"float, float*" Function,-,modfl,long double,"long double, long double*" @@ -2003,11 +2132,6 @@ Function,-,setkey,void,const char* Function,-,setlinebuf,int,FILE* Function,-,setstate,char*,char* Function,-,setvbuf,int,"FILE*, char*, int, size_t" -Function,+,sha256,void,"const unsigned char*, unsigned int, unsigned char[32]" -Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" -Function,+,sha256_process,void,sha256_context* -Function,+,sha256_start,void,sha256_context* -Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" Function,+,signal_reader_free,void,SignalReader* Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" @@ -2171,8 +2295,6 @@ Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" -Function,-,strftime,size_t,"char*, size_t, const char*, const tm*" -Function,-,strftime_l,size_t,"char*, size_t, const char*, const tm*, locale_t" Function,+,string_stream_alloc,Stream*, Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" @@ -2187,8 +2309,6 @@ Function,-,strndup,char*,"const char*, size_t" Function,-,strnlen,size_t,"const char*, size_t" Function,-,strnstr,char*,"const char*, const char*, size_t" Function,-,strpbrk,char*,"const char*, const char*" -Function,-,strptime,char*,"const char*, const char*, tm*" -Function,-,strptime_l,char*,"const char*, const char*, tm*, locale_t" Function,+,strrchr,char*,"const char*, int" Function,-,strsep,char*,"char**, const char*" Function,-,strsignal,char*,int @@ -2263,7 +2383,6 @@ Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double -Function,-,time,time_t,time_t* Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" Function,-,tmpfile,FILE*, @@ -2277,25 +2396,6 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double -Function,-,tzset,void, -Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_curve_private_key_size,int,uECC_Curve -Function,-,uECC_curve_public_key_size,int,uECC_Curve -Function,-,uECC_decompress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_get_rng,uECC_RNG_Function, -Function,-,uECC_make_key,int,"uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_secp160r1,uECC_Curve, -Function,-,uECC_secp192r1,uECC_Curve, -Function,-,uECC_secp224r1,uECC_Curve, -Function,-,uECC_secp256k1,uECC_Curve, -Function,+,uECC_secp256r1,uECC_Curve, -Function,+,uECC_set_rng,void,uECC_RNG_Function -Function,-,uECC_shared_secret,int,"const uint8_t*, const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uECC_Curve" -Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" -Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" -Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* @@ -2428,13 +2528,10 @@ Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, Variable,+,_ctype_,const char[], -Variable,-,_daylight,int, Variable,+,_global_impure_ptr,_reent*, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, -Variable,-,_timezone,long, -Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f18/target.json b/targets/f18/target.json index 19de9dd615..e021a5b229 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -18,7 +18,6 @@ "hwdrivers", "fatfs", "littlefs", - "flipperformat", "toolbox", "digital_signal", "signal_reader", @@ -28,12 +27,15 @@ "assets", "one_wire", "music_worker", - "misc", + "mbedtls", "flipper_application", - "flipperformat", "toolbox", + "u8g2", + "nanopb", + "update_util", + "heatshrink", + "flipperformat", "flipper18" - ], "excluded_sources": [ "furi_hal_infrared.c", @@ -63,4 +65,4 @@ "ibutton", "infrared" ] -} +} \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f31349f9c9..45c98ae1a5 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,47.0,, +Version,+,48.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -90,8 +90,13 @@ Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, +Header,+,lib/mbedtls/include/mbedtls/ecp.h,, +Header,+,lib/mbedtls/include/mbedtls/md.h,, +Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, -Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mbedtls/include/mbedtls/sha256.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -200,13 +205,11 @@ Header,+,lib/toolbox/float_tools.h,, Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, -Header,+,lib/toolbox/md5.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, -Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, @@ -521,7 +524,6 @@ Function,-,_system_r,int,"_reent*, const char*" Function,-,_tempnam_r,char*,"_reent*, const char*, const char*" Function,-,_tmpfile_r,FILE*,_reent* Function,-,_tmpnam_r,char*,"_reent*, char*" -Function,-,_tzset_r,void,_reent* Function,-,_ungetc_r,int,"_reent*, int, FILE*" Function,-,_unsetenv_r,int,"_reent*, const char*" Function,-,_vasiprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" @@ -568,8 +570,6 @@ Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" -Function,-,asctime,char*,const tm* -Function,-,asctime_r,char*,"const tm*, char*" Function,-,asin,double,double Function,-,asinf,float,float Function,-,asinh,double,double @@ -700,7 +700,7 @@ Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" Function,-,bzero,void,"void*, size_t" -Function,-,calloc,void*,"size_t, size_t" +Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* Function,+,canvas_current_font_height,uint8_t,const Canvas* @@ -754,7 +754,6 @@ Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" -Function,-,clock,clock_t, Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -778,8 +777,6 @@ Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* -Function,-,ctime,char*,const time_t* -Function,-,ctime_r,char*,"const time_t*, char*" Function,-,cuserid,char*,char* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* @@ -805,7 +802,6 @@ Function,+,dialog_message_set_icon,void,"DialogMessage*, const Icon*, uint8_t, u Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" -Function,-,difftime,double,"time_t, time_t" Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* @@ -1714,8 +1710,6 @@ Function,-,getenv,char*,const char* Function,-,gets,char*,char* Function,-,getsubopt,int,"char**, char**, char**" Function,-,getw,int,FILE* -Function,-,gmtime,tm*,const time_t* -Function,-,gmtime_r,tm*,"const time_t*, tm*" Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* @@ -2036,8 +2030,6 @@ Function,+,locale_get_time_format,LocaleTimeFormat, Function,+,locale_set_date_format,void,LocaleDateFormat Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits Function,+,locale_set_time_format,void,LocaleTimeFormat -Function,-,localtime,tm*,const time_t* -Function,-,localtime_r,tm*,"const time_t*, tm*" Function,-,log,double,double Function,-,log10,double,double Function,-,log10f,float,float @@ -2081,29 +2073,167 @@ Function,-,mbedtls_des_init,void,mbedtls_des_context* Function,-,mbedtls_des_key_check_key_parity,int,const unsigned char[8] Function,-,mbedtls_des_key_check_weak,int,const unsigned char[8] Function,-,mbedtls_des_key_set_parity,void,unsigned char[8] -Function,-,mbedtls_des_self_test,int,int Function,-,mbedtls_des_setkey,void,"uint32_t[32], const unsigned char[8]" Function,-,mbedtls_des_setkey_dec,int,"mbedtls_des_context*, const unsigned char[8]" Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_ecdh_calc_secret,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" +Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_make_public,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_read_params,int,"mbedtls_ecdh_context*, const unsigned char**, const unsigned char*" +Function,-,mbedtls_ecdh_read_public,int,"mbedtls_ecdh_context*, const unsigned char*, size_t" +Function,-,mbedtls_ecdh_setup,int,"mbedtls_ecdh_context*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecdsa_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdsa_free,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_from_keypair,int,"mbedtls_ecdsa_context*, const mbedtls_ecp_keypair*" +Function,-,mbedtls_ecdsa_genkey,int,"mbedtls_ecdsa_context*, mbedtls_ecp_group_id, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_init,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_read_signature,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t" +Function,-,mbedtls_ecdsa_read_signature_restartable,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_sign,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_sign_restartable,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_verify,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_ecdsa_verify_restartable,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_write_signature,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_write_signature_restartable,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecp_check_privkey,int,"const mbedtls_ecp_group*, const mbedtls_mpi*" +Function,-,mbedtls_ecp_check_pub_priv,int,"const mbedtls_ecp_keypair*, const mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_check_pubkey,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_copy,int,"mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_curve_info_from_grp_id,const mbedtls_ecp_curve_info*,mbedtls_ecp_group_id +Function,-,mbedtls_ecp_curve_info_from_name,const mbedtls_ecp_curve_info*,const char* +Function,-,mbedtls_ecp_curve_info_from_tls_id,const mbedtls_ecp_curve_info*,uint16_t +Function,-,mbedtls_ecp_curve_list,const mbedtls_ecp_curve_info*, +Function,-,mbedtls_ecp_export,int,"const mbedtls_ecp_keypair*, mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*" +Function,-,mbedtls_ecp_gen_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair_base,int,"mbedtls_ecp_group*, const mbedtls_ecp_point*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_privkey,int,"const mbedtls_ecp_group*, mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_get_type,mbedtls_ecp_curve_type,const mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_copy,int,"mbedtls_ecp_group*, const mbedtls_ecp_group*" +Function,-,mbedtls_ecp_group_free,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, +Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_muladd,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_muladd_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_point_cmp,int,"const mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_point_free,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_init,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" +Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" -Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" +Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" +Function,-,mbedtls_md,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md5,int,"const unsigned char*, size_t, unsigned char[16]" +Function,-,mbedtls_md5_clone,void,"mbedtls_md5_context*, const mbedtls_md5_context*" +Function,-,mbedtls_md5_finish,int,"mbedtls_md5_context*, unsigned char[16]" +Function,-,mbedtls_md5_free,void,mbedtls_md5_context* +Function,-,mbedtls_md5_init,void,mbedtls_md5_context* +Function,-,mbedtls_md5_starts,int,mbedtls_md5_context* +Function,-,mbedtls_md5_update,int,"mbedtls_md5_context*, const unsigned char*, size_t" +Function,-,mbedtls_md_clone,int,"mbedtls_md_context_t*, const mbedtls_md_context_t*" +Function,-,mbedtls_md_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_free,void,mbedtls_md_context_t* +Function,-,mbedtls_md_get_name,const char*,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_size,unsigned char,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_type,mbedtls_md_type_t,const mbedtls_md_info_t* +Function,-,mbedtls_md_hmac,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md_hmac_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_hmac_reset,int,mbedtls_md_context_t* +Function,-,mbedtls_md_hmac_starts,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_hmac_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_info_from_ctx,const mbedtls_md_info_t*,const mbedtls_md_context_t* +Function,-,mbedtls_md_info_from_string,const mbedtls_md_info_t*,const char* +Function,-,mbedtls_md_info_from_type,const mbedtls_md_info_t*,mbedtls_md_type_t +Function,-,mbedtls_md_init,void,mbedtls_md_context_t* +Function,-,mbedtls_md_list,const int*, +Function,-,mbedtls_md_setup,int,"mbedtls_md_context_t*, const mbedtls_md_info_t*, int" +Function,-,mbedtls_md_starts,int,mbedtls_md_context_t* +Function,-,mbedtls_md_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_add_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_add_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_add_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_bitlen,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_cmp_abs,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_cmp_int,int,"const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_cmp_mpi,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_copy,int,"mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_div_int,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_div_mpi,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_exp_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_fill_random,int,"mbedtls_mpi*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_free,void,mbedtls_mpi* +Function,-,mbedtls_mpi_gcd,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_gen_prime,int,"mbedtls_mpi*, size_t, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_get_bit,int,"const mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_grow,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_init,void,mbedtls_mpi* +Function,-,mbedtls_mpi_inv_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_is_prime_ext,int,"const mbedtls_mpi*, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_lsb,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_lset,int,"mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_lt_mpi_ct,int,"const mbedtls_mpi*, const mbedtls_mpi*, unsigned*" +Function,-,mbedtls_mpi_mod_int,int,"mbedtls_mpi_uint*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_mod_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_mul_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_uint" +Function,-,mbedtls_mpi_mul_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_random,int,"mbedtls_mpi*, mbedtls_mpi_sint, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_read_binary,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_binary_le,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_string,int,"mbedtls_mpi*, int, const char*" +Function,-,mbedtls_mpi_safe_cond_assign,int,"mbedtls_mpi*, const mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_safe_cond_swap,int,"mbedtls_mpi*, mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_set_bit,int,"mbedtls_mpi*, size_t, unsigned char" +Function,-,mbedtls_mpi_shift_l,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shift_r,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shrink,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_size,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_sub_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_sub_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_sub_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_swap,void,"mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_write_binary,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_binary_le,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_string,int,"const mbedtls_mpi*, int, char*, size_t, size_t*" Function,-,mbedtls_platform_zeroize,void,"void*, size_t" -Function,+,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" +Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* Function,-,mbedtls_sha1_init,void,mbedtls_sha1_context* -Function,-,mbedtls_sha1_self_test,int,int Function,-,mbedtls_sha1_starts,int,mbedtls_sha1_context* Function,-,mbedtls_sha1_update,int,"mbedtls_sha1_context*, const unsigned char*, size_t" +Function,-,mbedtls_sha256,int,"const unsigned char*, size_t, unsigned char*, int" +Function,-,mbedtls_sha256_clone,void,"mbedtls_sha256_context*, const mbedtls_sha256_context*" +Function,-,mbedtls_sha256_finish,int,"mbedtls_sha256_context*, unsigned char*" +Function,-,mbedtls_sha256_free,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_init,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_starts,int,"mbedtls_sha256_context*, int" +Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned char*, size_t" Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" -Function,+,md5,void,"const unsigned char*, size_t, unsigned char[16]" -Function,+,md5_finish,void,"md5_context*, unsigned char[16]" -Function,+,md5_process,void,"md5_context*, const unsigned char[64]" -Function,+,md5_starts,void,md5_context* -Function,+,md5_update,void,"md5_context*, const unsigned char*, size_t" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -2255,7 +2385,6 @@ Function,-,mkostemps,int,"char*, int, int" Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* -Function,-,mktime,time_t,tm* Function,-,modf,double,"double, double*" Function,-,modff,float,"float, float*" Function,-,modfl,long double,"long double, long double*" @@ -2593,11 +2722,6 @@ Function,-,setkey,void,const char* Function,-,setlinebuf,int,FILE* Function,-,setstate,char*,char* Function,-,setvbuf,int,"FILE*, char*, int, size_t" -Function,+,sha256,void,"const unsigned char*, unsigned int, unsigned char[32]" -Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" -Function,+,sha256_process,void,sha256_context* -Function,+,sha256_start,void,sha256_context* -Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" Function,+,signal_reader_free,void,SignalReader* Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" @@ -2800,8 +2924,6 @@ Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" -Function,-,strftime,size_t,"char*, size_t, const char*, const tm*" -Function,-,strftime_l,size_t,"char*, size_t, const char*, const tm*, locale_t" Function,+,string_stream_alloc,Stream*, Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" @@ -2816,8 +2938,6 @@ Function,-,strndup,char*,"const char*, size_t" Function,-,strnlen,size_t,"const char*, size_t" Function,-,strnstr,char*,"const char*, const char*, size_t" Function,-,strpbrk,char*,"const char*, const char*" -Function,-,strptime,char*,"const char*, const char*, tm*" -Function,-,strptime_l,char*,"const char*, const char*, tm*, locale_t" Function,+,strrchr,char*,"const char*, int" Function,-,strsep,char*,"char**, const char*" Function,-,strsignal,char*,int @@ -3047,7 +3167,6 @@ Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double -Function,-,time,time_t,time_t* Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" Function,-,tmpfile,FILE*, @@ -3061,25 +3180,6 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double -Function,-,tzset,void, -Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_curve_private_key_size,int,uECC_Curve -Function,-,uECC_curve_public_key_size,int,uECC_Curve -Function,-,uECC_decompress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_get_rng,uECC_RNG_Function, -Function,-,uECC_make_key,int,"uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_secp160r1,uECC_Curve, -Function,-,uECC_secp192r1,uECC_Curve, -Function,-,uECC_secp224r1,uECC_Curve, -Function,-,uECC_secp256k1,uECC_Curve, -Function,+,uECC_secp256r1,uECC_Curve, -Function,+,uECC_set_rng,void,uECC_RNG_Function -Function,-,uECC_shared_secret,int,"const uint8_t*, const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uECC_Curve" -Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" -Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" -Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* @@ -3212,13 +3312,10 @@ Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, Variable,+,_ctype_,const char[], -Variable,-,_daylight,int, Variable,+,_global_impure_ptr,_reent*, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, -Variable,-,_timezone,long, -Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/furi_hal/furi_hal_usb_ccid.c b/targets/f7/furi_hal/furi_hal_usb_ccid.c index 5c35c69f88..e24713ced5 100644 --- a/targets/f7/furi_hal/furi_hal_usb_ccid.c +++ b/targets/f7/furi_hal/furi_hal_usb_ccid.c @@ -39,12 +39,12 @@ struct CcidIntfDescriptor { struct usb_ccid_descriptor ccid_desc; struct usb_endpoint_descriptor ccid_bulk_in; struct usb_endpoint_descriptor ccid_bulk_out; -} __attribute__((packed)); +} FURI_PACKED; struct CcidConfigDescriptor { struct usb_config_descriptor config; struct CcidIntfDescriptor intf_0; -} __attribute__((packed)); +} FURI_PACKED; enum CCID_Features_Auto_t { CCID_Features_Auto_None = 0x0, @@ -255,7 +255,7 @@ typedef struct ccid_bulk_message_header { uint32_t dwLength; uint8_t bSlot; uint8_t bSeq; -} __attribute__((packed)) ccid_bulk_message_header_t; +} FURI_PACKED ccid_bulk_message_header_t; uint8_t SendBuffer[sizeof(ccid_bulk_message_header_t) + CCID_DATABLOCK_SIZE]; diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index 4c4f727bad..e9cb51e203 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -35,13 +35,13 @@ struct CdcIadDescriptor { struct CdcConfigDescriptorSingle { struct usb_config_descriptor config; struct CdcIadDescriptor iad_0; -} __attribute__((packed)); +} FURI_PACKED; struct CdcConfigDescriptorDual { struct usb_config_descriptor config; struct CdcIadDescriptor iad_0; struct CdcIadDescriptor iad_1; -} __attribute__((packed)); +} FURI_PACKED; static const struct usb_string_descriptor dev_manuf_desc = USB_STRING_DESC("Flipper Devices Inc."); diff --git a/targets/f7/furi_hal/furi_hal_usb_hid.c b/targets/f7/furi_hal/furi_hal_usb_hid.c index 334aa01026..b744e5ef72 100644 --- a/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -24,7 +24,7 @@ struct HidIntfDescriptor { struct HidConfigDescriptor { struct usb_config_descriptor config; struct HidIntfDescriptor intf_0; -} __attribute__((packed)); +} FURI_PACKED; enum HidReportId { ReportIdKeyboard = 1, @@ -199,7 +199,7 @@ struct HidReportMouse { int8_t x; int8_t y; int8_t wheel; -} __attribute__((packed)); +} FURI_PACKED; struct HidReportKB { uint8_t report_id; @@ -208,23 +208,23 @@ struct HidReportKB { uint8_t reserved; uint8_t btn[HID_KB_MAX_KEYS]; } boot; -} __attribute__((packed)); +} FURI_PACKED; struct HidReportConsumer { uint8_t report_id; uint16_t btn[HID_CONSUMER_MAX_KEYS]; -} __attribute__((packed)); +} FURI_PACKED; struct HidReportLED { uint8_t report_id; uint8_t led_state; -} __attribute__((packed)); +} FURI_PACKED; static struct HidReport { struct HidReportKB keyboard; struct HidReportMouse mouse; struct HidReportConsumer consumer; -} __attribute__((packed)) hid_report; +} FURI_PACKED hid_report; static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx); static void hid_deinit(usbd_device* dev); diff --git a/targets/f7/furi_hal/furi_hal_usb_u2f.c b/targets/f7/furi_hal/furi_hal_usb_u2f.c index fe711512a1..cc7e23b77e 100644 --- a/targets/f7/furi_hal/furi_hal_usb_u2f.c +++ b/targets/f7/furi_hal/furi_hal_usb_u2f.c @@ -25,7 +25,7 @@ struct HidIadDescriptor { struct HidConfigDescriptor { struct usb_config_descriptor config; struct HidIadDescriptor iad_0; -} __attribute__((packed)); +} FURI_PACKED; /* HID report: FIDO U2F */ static const uint8_t hid_u2f_report_desc[] = { diff --git a/targets/f7/target.json b/targets/f7/target.json index 63b5cdb927..7a816828c7 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -25,7 +25,6 @@ "fatfs", "littlefs", "subghz", - "flipperformat", "toolbox", "nfc", "digital_signal", @@ -39,12 +38,15 @@ "one_wire", "ibutton", "music_worker", - "misc", "mbedtls", "lfrfid", "flipper_application", - "flipperformat", "toolbox", + "u8g2", + "nanopb", + "update_util", + "heatshrink", + "flipperformat", "flipper7" ] } \ No newline at end of file From c1e0d02afc8131e782aa3f2e5c1bbd6f43890790 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Fri, 1 Dec 2023 10:42:00 +0100 Subject: [PATCH 090/111] 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 --- .../helpers/protocol_support/st25tb/st25tb.c | 4 +- lib/nfc/SConscript | 1 + lib/nfc/protocols/st25tb/st25tb.c | 20 ++ lib/nfc/protocols/st25tb/st25tb.h | 5 +- lib/nfc/protocols/st25tb/st25tb_poller.c | 154 +++++++++++-- lib/nfc/protocols/st25tb/st25tb_poller.h | 40 +++- lib/nfc/protocols/st25tb/st25tb_poller_i.c | 192 +++++++++------- lib/nfc/protocols/st25tb/st25tb_poller_i.h | 33 ++- lib/nfc/protocols/st25tb/st25tb_poller_sync.c | 211 ++++++++++++++++++ lib/nfc/protocols/st25tb/st25tb_poller_sync.h | 20 ++ targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 11 +- 12 files changed, 565 insertions(+), 128 deletions(-) create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_sync.c create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_sync.h diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index 32b2f47757..e22af48b34 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -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); diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 21f2fb49f6..d2cfbe2fb6 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -42,6 +42,7 @@ env.Append( File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"), File("protocols/mf_classic/mf_classic_poller_sync.h"), + File("protocols/st25tb/st25tb_poller_sync.h"), # Misc File("helpers/nfc_util.h"), File("helpers/iso14443_crc.h"), diff --git a/lib/nfc/protocols/st25tb/st25tb.c b/lib/nfc/protocols/st25tb/st25tb.c index d3fac7eeac..785cf831d9 100644 --- a/lib/nfc/protocols/st25tb/st25tb.c +++ b/lib/nfc/protocols/st25tb/st25tb.c @@ -232,3 +232,23 @@ St25tbData* st25tb_get_base_data(const St25tbData* data) { UNUSED(data); furi_crash("No base data"); } + +St25tbType st25tb_get_type_from_uid(const uint8_t* uid) { + switch(uid[2] >> 2) { + case 0x0: + case 0x3: + return St25tbTypeX4k; + case 0x4: + return St25tbTypeX512; + case 0x6: + return St25tbType512Ac; + case 0x7: + return St25tbType04k; + case 0xc: + return St25tbType512At; + case 0xf: + return St25tbType02k; + default: + furi_crash("unsupported st25tb type"); + } +} diff --git a/lib/nfc/protocols/st25tb/st25tb.h b/lib/nfc/protocols/st25tb/st25tb.h index 1edb296ca1..ed02dc2b20 100644 --- a/lib/nfc/protocols/st25tb/st25tb.h +++ b/lib/nfc/protocols/st25tb/st25tb.h @@ -1,6 +1,5 @@ #pragma once -#include #include #ifdef __cplusplus @@ -27,6 +26,7 @@ typedef enum { St25tbErrorFieldOff, St25tbErrorWrongCrc, St25tbErrorTimeout, + St25tbErrorWriteFailed, } St25tbError; typedef enum { @@ -44,7 +44,6 @@ typedef struct { St25tbType type; uint32_t blocks[ST25TB_MAX_BLOCKS]; uint32_t system_otp_block; - uint8_t chip_id; } St25tbData; extern const NfcDeviceBase nfc_device_st25tb; @@ -75,6 +74,8 @@ bool st25tb_set_uid(St25tbData* data, const uint8_t* uid, size_t uid_len); St25tbData* st25tb_get_base_data(const St25tbData* data); +St25tbType st25tb_get_type_from_uid(const uint8_t* uid); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c index 2bc5dd9410..fd6dc4f09f 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -1,13 +1,12 @@ -#include "protocols/nfc_protocol.h" -#include "protocols/st25tb/st25tb.h" +#include "st25tb_poller.h" #include "st25tb_poller_i.h" #include -#include - #define TAG "ST25TBPoller" +typedef NfcCommand (*St25tbPollerStateHandler)(St25tbPoller* instance); + const St25tbData* st25tb_poller_get_data(St25tbPoller* instance) { furi_assert(instance); furi_assert(instance->data); @@ -20,6 +19,7 @@ static St25tbPoller* st25tb_poller_alloc(Nfc* nfc) { St25tbPoller* instance = malloc(sizeof(St25tbPoller)); instance->nfc = nfc; + instance->state = St25tbPollerStateSelect; instance->tx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); instance->rx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); @@ -60,6 +60,128 @@ static void instance->context = context; } +static NfcCommand st25tb_poller_select_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + St25tbError error = st25tb_poller_select(instance, NULL); + if(error != St25tbErrorNone) { + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + break; + } + + instance->st25tb_event.type = St25tbPollerEventTypeReady; + instance->st25tb_event.data->ready.type = instance->data->type; + command = instance->callback(instance->general_event, instance->context); + instance->state = St25tbPollerStateRequestMode; + } while(false); + + return command; +} + +static NfcCommand st25tb_poller_request_mode_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->st25tb_event.type = St25tbPollerEventTypeRequestMode; + command = instance->callback(instance->general_event, instance->context); + + St25tbPollerEventDataModeRequest* mode_request_data = + &instance->st25tb_event_data.mode_request; + + furi_assert(mode_request_data->mode < St25tbPollerModeNum); + + if(mode_request_data->mode == St25tbPollerModeRead) { + instance->state = St25tbPollerStateRead; + instance->poller_ctx.read.current_block = 0; + } else { + instance->state = St25tbPollerStateWrite; + instance->poller_ctx.write.block_number = + mode_request_data->params.write_params.block_number; + instance->poller_ctx.write.block_data = mode_request_data->params.write_params.block_data; + } + + return command; +} + +static NfcCommand st25tb_poller_read_handler(St25tbPoller* instance) { + St25tbError error = St25tbErrorNone; + + do { + uint8_t total_blocks = st25tb_get_block_count(instance->data->type); + uint8_t* current_block = &instance->poller_ctx.read.current_block; + if(*current_block == total_blocks) { + error = st25tb_poller_read_block( + instance, &instance->data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + if(error != St25tbErrorNone) { + FURI_LOG_E(TAG, "Failed to read OTP block"); + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + break; + } else { + instance->state = St25tbPollerStateSuccess; + break; + } + } else { + error = st25tb_poller_read_block( + instance, &instance->data->blocks[*current_block], *current_block); + if(error != St25tbErrorNone) { + FURI_LOG_E(TAG, "Failed to read block %d", *current_block); + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + break; + } + + *current_block += 1; + } + } while(false); + + return NfcCommandContinue; +} + +static NfcCommand st25tb_poller_write_handler(St25tbPoller* instance) { + St25tbPollerWriteContext* write_ctx = &instance->poller_ctx.write; + St25tbError error = + st25tb_poller_write_block(instance, write_ctx->block_data, write_ctx->block_number); + + if(error == St25tbErrorNone) { + instance->state = St25tbPollerStateSuccess; + } else { + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + } + + return NfcCommandContinue; +} + +NfcCommand st25tb_poller_success_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->st25tb_event.type = St25tbPollerEventTypeSuccess; + command = instance->callback(instance->general_event, instance->context); + furi_delay_ms(100); + instance->state = St25tbPollerStateRequestMode; + + return command; +} + +NfcCommand st25tb_poller_failure_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->st25tb_event.type = St25tbPollerEventTypeFailure; + command = instance->callback(instance->general_event, instance->context); + furi_delay_ms(100); + instance->state = St25tbPollerStateSelect; + + return command; +} + +static St25tbPollerStateHandler st25tb_poller_state_handlers[St25tbPollerStateNum] = { + [St25tbPollerStateSelect] = st25tb_poller_select_handler, + [St25tbPollerStateRequestMode] = st25tb_poller_request_mode_handler, + [St25tbPollerStateRead] = st25tb_poller_read_handler, + [St25tbPollerStateWrite] = st25tb_poller_write_handler, + [St25tbPollerStateSuccess] = st25tb_poller_success_handler, + [St25tbPollerStateFailure] = st25tb_poller_failure_handler, +}; + static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -69,26 +191,10 @@ static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { NfcEvent* nfc_event = event.event_data; NfcCommand command = NfcCommandContinue; - if(nfc_event->type == NfcEventTypePollerReady) { - if(instance->state != St25tbPollerStateActivated) { - St25tbError error = st25tb_poller_activate(instance, instance->data); + furi_assert(instance->state < St25tbPollerStateNum); - if(error == St25tbErrorNone) { - instance->st25tb_event.type = St25tbPollerEventTypeReady; - instance->st25tb_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - } else { - instance->st25tb_event.type = St25tbPollerEventTypeError; - instance->st25tb_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - // Add delay to switch context - furi_delay_ms(100); - } - } else { - instance->st25tb_event.type = St25tbPollerEventTypeReady; - instance->st25tb_event_data.error = St25tbErrorNone; - command = instance->callback(instance->general_event, instance->context); - } + if(nfc_event->type == NfcEventTypePollerReady) { + command = st25tb_poller_state_handlers[instance->state](instance); } return command; @@ -103,7 +209,7 @@ static bool st25tb_poller_detect(NfcGenericEvent event, void* context) { bool protocol_detected = false; St25tbPoller* instance = context; NfcEvent* nfc_event = event.event_data; - furi_assert(instance->state == St25tbPollerStateIdle); + furi_assert(instance->state == St25tbPollerStateSelect); if(nfc_event->type == NfcEventTypePollerReady) { St25tbError error = st25tb_poller_initiate(instance, NULL); diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.h b/lib/nfc/protocols/st25tb/st25tb_poller.h index d3b85e3066..87687b7eba 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller.h @@ -3,8 +3,6 @@ #include "st25tb.h" #include -#include - #ifdef __cplusplus extern "C" { #endif @@ -12,11 +10,40 @@ extern "C" { typedef struct St25tbPoller St25tbPoller; typedef enum { - St25tbPollerEventTypeError, St25tbPollerEventTypeReady, + St25tbPollerEventTypeRequestMode, + St25tbPollerEventTypeFailure, + St25tbPollerEventTypeSuccess, } St25tbPollerEventType; typedef struct { + St25tbType type; +} St25tbPollerReadyData; + +typedef enum { + St25tbPollerModeRead, + St25tbPollerModeWrite, + + St25tbPollerModeNum, +} St25tbPollerMode; + +typedef struct { + uint8_t block_number; + uint32_t block_data; +} St25tbPollerEventDataModeRequestWriteParams; + +typedef union { + St25tbPollerEventDataModeRequestWriteParams write_params; +} St25tbPollerEventDataModeRequestParams; + +typedef struct { + St25tbPollerMode mode; + St25tbPollerEventDataModeRequestParams params; +} St25tbPollerEventDataModeRequest; + +typedef union { + St25tbPollerReadyData ready; + St25tbPollerEventDataModeRequest mode_request; St25tbError error; } St25tbPollerEventData; @@ -31,15 +58,18 @@ St25tbError st25tb_poller_send_frame( BitBuffer* rx_buffer, uint32_t fwt); -St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id); +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id_ptr); -St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data); +St25tbError st25tb_poller_select(St25tbPoller* instance, uint8_t* chip_id_ptr); St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid); St25tbError st25tb_poller_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); +St25tbError + st25tb_poller_write_block(St25tbPoller* instance, uint32_t block, uint8_t block_number); + St25tbError st25tb_poller_halt(St25tbPoller* instance); #ifdef __cplusplus diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.c b/lib/nfc/protocols/st25tb/st25tb_poller_i.c index 76c9a8b1fa..adb8626a30 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.c @@ -1,8 +1,5 @@ #include "st25tb_poller_i.h" -#include "bit_buffer.h" -#include "core/core_defines.h" -#include "protocols/st25tb/st25tb.h" #include #define TAG "ST25TBPoller" @@ -18,17 +15,7 @@ static St25tbError st25tb_poller_process_error(NfcError error) { } } -static St25tbError st25tb_poller_prepare_trx(St25tbPoller* instance) { - furi_assert(instance); - - if(instance->state == St25tbPollerStateIdle) { - return st25tb_poller_activate(instance, NULL); - } - - return St25tbErrorNone; -} - -static St25tbError st25tb_poller_frame_exchange( +St25tbError st25tb_poller_send_frame( St25tbPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, @@ -48,7 +35,7 @@ static St25tbError st25tb_poller_frame_exchange( NfcError error = nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); if(error != NfcErrorNone) { - FURI_LOG_D(TAG, "error during trx: %d", error); + FURI_LOG_T(TAG, "error during trx: %d", error); ret = st25tb_poller_process_error(error); break; } @@ -65,32 +52,11 @@ static St25tbError st25tb_poller_frame_exchange( return ret; } -St25tbType st25tb_get_type_from_uid(const uint8_t uid[ST25TB_UID_SIZE]) { - switch(uid[2] >> 2) { - case 0x0: - case 0x3: - return St25tbTypeX4k; - case 0x4: - return St25tbTypeX512; - case 0x6: - return St25tbType512Ac; - case 0x7: - return St25tbType04k; - case 0xc: - return St25tbType512At; - case 0xf: - return St25tbType02k; - default: - furi_crash("unsupported st25tb type"); - } -} - -St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id) { +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id_ptr) { // Send Initiate() furi_assert(instance); furi_assert(instance->nfc); - instance->state = St25tbPollerStateInitiateInProgress; bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); bit_buffer_append_byte(instance->tx_buffer, 0x06); @@ -98,77 +64,90 @@ St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id) { St25tbError ret; do { - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { - FURI_LOG_D(TAG, "Unexpected Initiate response size"); + FURI_LOG_E(TAG, "Unexpected Initiate response size"); ret = St25tbErrorCommunication; break; } - if(chip_id) { - *chip_id = bit_buffer_get_byte(instance->rx_buffer, 0); + uint8_t chip_id = bit_buffer_get_byte(instance->rx_buffer, 0); + FURI_LOG_D(TAG, "Got chip_id=0x%02X", chip_id); + if(chip_id_ptr) { + *chip_id_ptr = bit_buffer_get_byte(instance->rx_buffer, 0); } } while(false); return ret; } -St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data) { +St25tbError st25tb_poller_select(St25tbPoller* instance, uint8_t* chip_id_ptr) { furi_assert(instance); furi_assert(instance->nfc); - st25tb_reset(data); - St25tbError ret; do { - ret = st25tb_poller_initiate(instance, &data->chip_id); - if(ret != St25tbErrorNone) { - break; - } + uint8_t chip_id; - instance->state = St25tbPollerStateActivationInProgress; + if(chip_id_ptr != NULL) { + chip_id = *chip_id_ptr; + } else { + ret = st25tb_poller_initiate(instance, &chip_id); + if(ret != St25tbErrorNone) { + break; + } + } bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); // Send Select(Chip_ID), let's just assume that collisions won't ever happen :D bit_buffer_append_byte(instance->tx_buffer, 0x0E); - bit_buffer_append_byte(instance->tx_buffer, data->chip_id); + bit_buffer_append_byte(instance->tx_buffer, chip_id); - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { - instance->state = St25tbPollerStateActivationFailed; break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { - FURI_LOG_D(TAG, "Unexpected Select response size"); - instance->state = St25tbPollerStateActivationFailed; + FURI_LOG_E(TAG, "Unexpected Select response size"); ret = St25tbErrorCommunication; break; } - if(bit_buffer_get_byte(instance->rx_buffer, 0) != data->chip_id) { - FURI_LOG_D(TAG, "ChipID mismatch"); - instance->state = St25tbPollerStateActivationFailed; + if(bit_buffer_get_byte(instance->rx_buffer, 0) != chip_id) { + FURI_LOG_E(TAG, "ChipID mismatch"); ret = St25tbErrorColResFailed; break; } - instance->state = St25tbPollerStateActivated; - ret = st25tb_poller_get_uid(instance, data->uid); + ret = st25tb_poller_get_uid(instance, instance->data->uid); if(ret != St25tbErrorNone) { - instance->state = St25tbPollerStateActivationFailed; break; } - data->type = st25tb_get_type_from_uid(data->uid); + instance->data->type = st25tb_get_type_from_uid(instance->data->uid); + } while(false); + + return ret; +} + +St25tbError st25tb_poller_read(St25tbPoller* instance, St25tbData* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + St25tbError ret; + + memcpy(data, instance->data, sizeof(St25tbData)); + + do { bool read_blocks = true; for(uint8_t i = 0; i < st25tb_get_block_count(data->type); i++) { ret = st25tb_poller_read_block(instance, &data->blocks[i], i); @@ -181,6 +160,9 @@ St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data) { break; } ret = st25tb_poller_read_block(instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + if(ret != St25tbErrorNone) { + break; + } } while(false); return ret; @@ -198,15 +180,14 @@ St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid) { bit_buffer_append_byte(instance->tx_buffer, 0x0B); - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_UID_SIZE) { - FURI_LOG_D(TAG, "Unexpected Get_UID() response size"); - instance->state = St25tbPollerStateActivationFailed; + FURI_LOG_E(TAG, "Unexpected Get_UID() response size"); ret = St25tbErrorCommunication; break; } @@ -215,6 +196,17 @@ St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid) { FURI_SWAP(uid[1], uid[6]); FURI_SWAP(uid[2], uid[5]); FURI_SWAP(uid[3], uid[4]); + FURI_LOG_I( + TAG, + "Got tag with uid: %02X %02X %02X %02X %02X %02X %02X %02X", + uid[0], + uid[1], + uid[2], + uid[3], + uid[4], + uid[5], + uid[6], + uid[7]); } while(false); return ret; } @@ -227,7 +219,7 @@ St25tbError furi_assert( (block_number <= st25tb_get_block_count(instance->data->type)) || block_number == ST25TB_SYSTEM_OTP_BLOCK); - FURI_LOG_D(TAG, "reading block %d", block_number); + FURI_LOG_T(TAG, "reading block %d", block_number); bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); @@ -236,60 +228,88 @@ St25tbError bit_buffer_append_byte(instance->tx_buffer, block_number); St25tbError ret; do { - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_BLOCK_SIZE) { - FURI_LOG_D(TAG, "Unexpected Read_block(Addr) response size"); + FURI_LOG_E(TAG, "Unexpected Read_block(Addr) response size"); ret = St25tbErrorCommunication; break; } bit_buffer_write_bytes(instance->rx_buffer, block, ST25TB_BLOCK_SIZE); - FURI_LOG_D(TAG, "read result: %08lX", *block); + FURI_LOG_D(TAG, "Read_block(%d) result: %08lX", block_number, *block); } while(false); return ret; } -St25tbError st25tb_poller_halt(St25tbPoller* instance) { +St25tbError + st25tb_poller_write_block(St25tbPoller* instance, uint32_t block, uint8_t block_number) { furi_assert(instance); - + furi_assert(instance->nfc); + furi_assert( + (block_number <= st25tb_get_block_count(instance->data->type)) || + block_number == ST25TB_SYSTEM_OTP_BLOCK); + FURI_LOG_T(TAG, "writing block %d", block_number); bit_buffer_reset(instance->tx_buffer); - bit_buffer_reset(instance->rx_buffer); - - // Send Completion() - bit_buffer_append_byte(instance->tx_buffer, 0x0F); + // Send Write_block(Addr, Data) + bit_buffer_append_byte(instance->tx_buffer, 0x09); + bit_buffer_append_byte(instance->tx_buffer, block_number); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&block, ST25TB_BLOCK_SIZE); St25tbError ret; - do { - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); - if(ret != St25tbErrorTimeout) { + if(ret != St25tbErrorTimeout) { // tag doesn't ack writes so timeout are expected. break; } - instance->state = St25tbPollerStateIdle; + furi_delay_ms(7); // 7ms is the max programming time as per datasheet + + uint32_t block_check; + ret = st25tb_poller_read_block(instance, &block_check, block_number); + if(ret != St25tbErrorNone) { + FURI_LOG_E(TAG, "write verification failed: read error"); + break; + } + if(block_check != block) { + FURI_LOG_E( + TAG, + "write verification failed: wrote %08lX but read back %08lX", + block, + block_check); + ret = St25tbErrorWriteFailed; + break; + } + FURI_LOG_D(TAG, "wrote %08lX to block %d", block, block_number); } while(false); return ret; } -St25tbError st25tb_poller_send_frame( - St25tbPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt) { +St25tbError st25tb_poller_halt(St25tbPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Completion() + bit_buffer_append_byte(instance->tx_buffer, 0x0F); + St25tbError ret; do { - ret = st25tb_poller_prepare_trx(instance); - if(ret != St25tbErrorNone) break; + ret = st25tb_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorTimeout) { + break; + } - ret = st25tb_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + instance->state = St25tbPollerStateSelect; } while(false); return ret; diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.h b/lib/nfc/protocols/st25tb/st25tb_poller_i.h index 27218d7b44..e16feb7812 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.h @@ -1,8 +1,9 @@ #pragma once -#include "protocols/st25tb/st25tb.h" #include "st25tb_poller.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -10,14 +11,30 @@ extern "C" { #define ST25TB_POLLER_MAX_BUFFER_SIZE (16U) typedef enum { - St25tbPollerStateIdle, - St25tbPollerStateInitiateInProgress, - St25tbPollerStateInitiateFailed, - St25tbPollerStateActivationInProgress, - St25tbPollerStateActivationFailed, - St25tbPollerStateActivated, + St25tbPollerStateSelect, + St25tbPollerStateRequestMode, + St25tbPollerStateRead, + St25tbPollerStateWrite, + St25tbPollerStateSuccess, + St25tbPollerStateFailure, + + St25tbPollerStateNum, } St25tbPollerState; +typedef struct { + uint8_t current_block; +} St25tbPollerReadContext; + +typedef struct { + uint8_t block_number; + uint32_t block_data; +} St25tbPollerWriteContext; + +typedef union { + St25tbPollerReadContext read; + St25tbPollerWriteContext write; +} St25tbPollerContext; + struct St25tbPoller { Nfc* nfc; St25tbPollerState state; @@ -25,6 +42,8 @@ struct St25tbPoller { BitBuffer* tx_buffer; BitBuffer* rx_buffer; + St25tbPollerContext poller_ctx; + NfcGenericEvent general_event; St25tbPollerEvent st25tb_event; St25tbPollerEventData st25tb_event_data; diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_sync.c b/lib/nfc/protocols/st25tb/st25tb_poller_sync.c new file mode 100644 index 0000000000..3cd0b37929 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_sync.c @@ -0,0 +1,211 @@ +#include "st25tb_poller_sync.h" +#include "st25tb_poller_i.h" + +#define ST25TB_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0) + +typedef enum { + St25tbPollerCmdTypeDetectType, + St25tbPollerCmdTypeReadBlock, + St25tbPollerCmdTypeWriteBlock, + + St25tbPollerCmdTypeNum, +} St25tbPollerCmdType; + +typedef struct { + St25tbType* type; +} St25tbPollerCmdDetectTypeData; + +typedef struct { + St25tbData* data; +} St25tbPollerCmdReadData; + +typedef struct { + uint8_t block_num; + uint32_t* block; +} St25tbPollerCmdReadBlockData; + +typedef struct { + uint8_t block_num; + uint32_t block; +} St25tbPollerCmdWriteBlockData; + +typedef union { + St25tbPollerCmdDetectTypeData detect_type; + St25tbPollerCmdReadData read; + St25tbPollerCmdReadBlockData read_block; + St25tbPollerCmdWriteBlockData write_block; +} St25tbPollerCmdData; + +typedef struct { + FuriThreadId thread_id; + St25tbError error; + St25tbPollerCmdType cmd_type; + St25tbPollerCmdData cmd_data; +} St25tbPollerSyncContext; + +typedef St25tbError (*St25tbPollerCmdHandler)(St25tbPoller* poller, St25tbPollerCmdData* data); + +static St25tbError st25tb_poller_detect_handler(St25tbPoller* poller, St25tbPollerCmdData* data) { + uint8_t uid[ST25TB_UID_SIZE]; + St25tbError error = st25tb_poller_get_uid(poller, uid); + if(error == St25tbErrorNone) { + *data->detect_type.type = st25tb_get_type_from_uid(uid); + } + return error; +} + +static St25tbError + st25tb_poller_read_block_handler(St25tbPoller* poller, St25tbPollerCmdData* data) { + return st25tb_poller_read_block(poller, data->read_block.block, data->read_block.block_num); +} + +static St25tbError + st25tb_poller_write_block_handler(St25tbPoller* poller, St25tbPollerCmdData* data) { + return st25tb_poller_write_block(poller, data->write_block.block, data->write_block.block_num); +} + +static St25tbPollerCmdHandler st25tb_poller_cmd_handlers[St25tbPollerCmdTypeNum] = { + [St25tbPollerCmdTypeDetectType] = st25tb_poller_detect_handler, + [St25tbPollerCmdTypeReadBlock] = st25tb_poller_read_block_handler, + [St25tbPollerCmdTypeWriteBlock] = st25tb_poller_write_block_handler, +}; + +static NfcCommand st25tb_poller_cmd_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolSt25tb); + + St25tbPollerSyncContext* poller_context = context; + St25tbPoller* st25tb_poller = event.instance; + St25tbPollerEvent* st25tb_event = event.event_data; + + if(st25tb_event->type == St25tbPollerEventTypeReady) { + poller_context->error = st25tb_poller_cmd_handlers[poller_context->cmd_type]( + st25tb_poller, &poller_context->cmd_data); + } else { + poller_context->error = st25tb_event->data->error; + } + + furi_thread_flags_set(poller_context->thread_id, ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + + return NfcCommandStop; +} + +static St25tbError st25tb_poller_cmd_execute(Nfc* nfc, St25tbPollerSyncContext* poller_ctx) { + furi_assert(nfc); + furi_assert(poller_ctx->cmd_type < St25tbPollerCmdTypeNum); + poller_ctx->thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolSt25tb); + nfc_poller_start(poller, st25tb_poller_cmd_callback, poller_ctx); + furi_thread_flags_wait(ST25TB_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_ctx->error; +} + +St25tbError st25tb_poller_sync_read_block(Nfc* nfc, uint8_t block_num, uint32_t* block) { + furi_assert(block); + St25tbPollerSyncContext poller_context = { + .cmd_type = St25tbPollerCmdTypeReadBlock, + .cmd_data = + { + .read_block = + { + .block = block, + .block_num = block_num, + }, + }, + }; + return st25tb_poller_cmd_execute(nfc, &poller_context); +} + +St25tbError st25tb_poller_sync_write_block(Nfc* nfc, uint8_t block_num, uint32_t block) { + St25tbPollerSyncContext poller_context = { + .cmd_type = St25tbPollerCmdTypeWriteBlock, + .cmd_data = + { + .write_block = + { + .block = block, + .block_num = block_num, + }, + }, + }; + return st25tb_poller_cmd_execute(nfc, &poller_context); +} + +St25tbError st25tb_poller_sync_detect_type(Nfc* nfc, St25tbType* type) { + furi_assert(type); + St25tbPollerSyncContext poller_context = { + .cmd_type = St25tbPollerCmdTypeDetectType, + .cmd_data = + { + .detect_type = + { + .type = type, + }, + }, + }; + return st25tb_poller_cmd_execute(nfc, &poller_context); +} + +static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolSt25tb); + + St25tbPollerSyncContext* poller_context = context; + St25tbPollerEvent* st25tb_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + if(st25tb_event->type == St25tbPollerEventTypeRequestMode) { + st25tb_event->data->mode_request.mode = St25tbPollerModeRead; + } else if( + st25tb_event->type == St25tbPollerEventTypeSuccess || + st25tb_event->type == St25tbPollerEventTypeFailure) { + if(st25tb_event->type == St25tbPollerEventTypeSuccess) { + memcpy( + poller_context->cmd_data.read.data, + st25tb_poller_get_data(event.instance), + sizeof(St25tbData)); + } else { + poller_context->error = st25tb_event->data->error; + } + command = NfcCommandStop; + furi_thread_flags_set(poller_context->thread_id, ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + } + + return command; +} + +St25tbError st25tb_poller_sync_read(Nfc* nfc, St25tbData* data) { + furi_assert(nfc); + furi_assert(data); + + St25tbPollerSyncContext poller_context = { + .thread_id = furi_thread_get_current_id(), + .cmd_data = + { + .read = + { + .data = data, + }, + }, + }; + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolSt25tb); + nfc_poller_start(poller, nfc_scene_read_poller_callback_st25tb, &poller_context); + furi_thread_flags_wait(ST25TB_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_context.error; +} \ No newline at end of file diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_sync.h b/lib/nfc/protocols/st25tb/st25tb_poller_sync.h new file mode 100644 index 0000000000..ecd994b398 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_sync.h @@ -0,0 +1,20 @@ +#pragma once + +#include "st25tb.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +St25tbError st25tb_poller_sync_read_block(Nfc* nfc, uint8_t block_num, uint32_t* block); + +St25tbError st25tb_poller_sync_write_block(Nfc* nfc, uint8_t block_num, uint32_t block); + +St25tbError st25tb_poller_sync_detect_type(Nfc* nfc, St25tbType* type); + +St25tbError st25tb_poller_sync_read(Nfc* nfc, St25tbData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index cd89b554af..e735e2c6dd 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,48.0,, +Version,+,49.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 45c98ae1a5..e25254dddd 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,48.0,, +Version,+,49.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -145,6 +145,7 @@ Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, Header,+,lib/nfc/protocols/slix/slix.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, +Header,+,lib/nfc/protocols/st25tb/st25tb_poller_sync.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -2810,15 +2811,21 @@ Function,+,st25tb_free,void,St25tbData* Function,+,st25tb_get_base_data,St25tbData*,const St25tbData* Function,+,st25tb_get_block_count,uint8_t,St25tbType Function,+,st25tb_get_device_name,const char*,"const St25tbData*, NfcDeviceNameType" +Function,+,st25tb_get_type_from_uid,St25tbType,const uint8_t* Function,+,st25tb_get_uid,const uint8_t*,"const St25tbData*, size_t*" Function,+,st25tb_is_equal,_Bool,"const St25tbData*, const St25tbData*" Function,+,st25tb_load,_Bool,"St25tbData*, FlipperFormat*, uint32_t" -Function,+,st25tb_poller_activate,St25tbError,"St25tbPoller*, St25tbData*" Function,+,st25tb_poller_get_uid,St25tbError,"St25tbPoller*, uint8_t*" Function,+,st25tb_poller_halt,St25tbError,St25tbPoller* Function,+,st25tb_poller_initiate,St25tbError,"St25tbPoller*, uint8_t*" Function,+,st25tb_poller_read_block,St25tbError,"St25tbPoller*, uint32_t*, uint8_t" +Function,+,st25tb_poller_select,St25tbError,"St25tbPoller*, uint8_t*" Function,+,st25tb_poller_send_frame,St25tbError,"St25tbPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,st25tb_poller_sync_detect_type,St25tbError,"Nfc*, St25tbType*" +Function,+,st25tb_poller_sync_read,St25tbError,"Nfc*, St25tbData*" +Function,+,st25tb_poller_sync_read_block,St25tbError,"Nfc*, uint8_t, uint32_t*" +Function,+,st25tb_poller_sync_write_block,St25tbError,"Nfc*, uint8_t, uint32_t" +Function,+,st25tb_poller_write_block,St25tbError,"St25tbPoller*, uint32_t, uint8_t" Function,+,st25tb_reset,void,St25tbData* Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" From b51a754fd95285069c360afa48de380e6311c8e2 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Fri, 1 Dec 2023 14:25:53 +0100 Subject: [PATCH 091/111] Mifare Classic nested auth support (#3238) Co-authored-by: Aleksandr Kutuzov --- lib/nfc/protocols/mf_classic/crypto1.c | 9 +- lib/nfc/protocols/mf_classic/crypto1.h | 3 +- .../protocols/mf_classic/mf_classic_poller.h | 40 +++++++++ .../mf_classic/mf_classic_poller_i.c | 89 ++++++++++++++++--- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 4 +- 6 files changed, 128 insertions(+), 19 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/protocols/mf_classic/crypto1.c index df01a348c8..02bc677ba6 100644 --- a/lib/nfc/protocols/mf_classic/crypto1.c +++ b/lib/nfc/protocols/mf_classic/crypto1.c @@ -143,7 +143,8 @@ void crypto1_encrypt_reader_nonce( uint32_t cuid, uint8_t* nt, uint8_t* nr, - BitBuffer* out) { + BitBuffer* out, + bool is_nested) { furi_assert(crypto); furi_assert(nt); furi_assert(nr); @@ -153,7 +154,11 @@ void crypto1_encrypt_reader_nonce( uint32_t nt_num = nfc_util_bytes2num(nt, sizeof(uint32_t)); crypto1_init(crypto, key); - crypto1_word(crypto, nt_num ^ cuid, 0); + if(is_nested) { + nt_num = crypto1_word(crypto, nt_num ^ cuid, 1) ^ nt_num; + } else { + crypto1_word(crypto, nt_num ^ cuid, 0); + } for(size_t i = 0; i < 4; i++) { uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; diff --git a/lib/nfc/protocols/mf_classic/crypto1.h b/lib/nfc/protocols/mf_classic/crypto1.h index f2bdb272b0..7cc16fcffd 100644 --- a/lib/nfc/protocols/mf_classic/crypto1.h +++ b/lib/nfc/protocols/mf_classic/crypto1.h @@ -35,7 +35,8 @@ void crypto1_encrypt_reader_nonce( uint32_t cuid, uint8_t* nt, uint8_t* nr, - BitBuffer* out); + BitBuffer* out, + bool is_nested); uint32_t prng_successor(uint32_t x, uint32_t n); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index f05a6800ad..19e5257017 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -178,6 +178,25 @@ MfClassicError mf_classic_poller_get_nt( MfClassicKeyType key_type, MfClassicNt* nt); +/** + * @brief Collect tag nonce during nested authentication. + * + * Must ONLY be used inside the callback function. + * + * Starts nested authentication procedure and collects tag nonce. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_get_nt_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + /** * @brief Perform authentication. * @@ -200,6 +219,27 @@ MfClassicError mf_classic_poller_auth( MfClassicKeyType key_type, MfClassicAuthContext* data); +/** + * @brief Perform nested authentication. + * + * Must ONLY be used inside the callback function. + * + * Perform nested authentication as specified in Mf Classic protocol. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key key to be used for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_auth_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + /** * @brief Halt the tag. * diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 4b071815ea..16bfb3f728 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -33,11 +33,12 @@ MfClassicError mf_classic_process_error(Iso14443_3aError error) { return ret; } -MfClassicError mf_classic_poller_get_nt( +static MfClassicError mf_classic_poller_get_nt_common( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool is_nested) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -47,14 +48,29 @@ MfClassicError mf_classic_poller_get_nt( uint8_t auth_cmd[2] = {auth_type, block_num}; bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); - error = iso14443_3a_poller_send_standard_frame( - instance->iso14443_3a_poller, - instance->tx_plain_buffer, - instance->rx_plain_buffer, - MF_CLASSIC_FWT_FC); - if(error != Iso14443_3aErrorWrongCrc) { - ret = mf_classic_process_error(error); - break; + if(is_nested) { + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + } else { + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_plain_buffer, + instance->rx_plain_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_classic_process_error(error); + break; + } } if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { ret = MfClassicErrorProtocol; @@ -69,12 +85,29 @@ MfClassicError mf_classic_poller_get_nt( return ret; } -MfClassicError mf_classic_poller_auth( +MfClassicError mf_classic_poller_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, false); +} + +MfClassicError mf_classic_poller_get_nt_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true); +} + +static MfClassicError mf_classic_poller_auth_common( MfClassicPoller* instance, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool is_nested) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -84,7 +117,11 @@ MfClassicError mf_classic_poller_auth( iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); MfClassicNt nt = {}; - ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); + if(is_nested) { + ret = mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt); + } else { + ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); + } if(ret != MfClassicErrorNone) break; if(data) { data->nt = nt; @@ -96,7 +133,13 @@ MfClassicError mf_classic_poller_auth( furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr)); crypto1_encrypt_reader_nonce( - instance->crypto, key_num, cuid, nt.data, nr.data, instance->tx_encrypted_buffer); + instance->crypto, + key_num, + cuid, + nt.data, + nr.data, + instance->tx_encrypted_buffer, + is_nested); error = iso14443_3a_poller_txrx_custom_parity( instance->iso14443_3a_poller, instance->tx_encrypted_buffer, @@ -130,6 +173,24 @@ MfClassicError mf_classic_poller_auth( return ret; } +MfClassicError mf_classic_poller_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false); +} + +MfClassicError mf_classic_poller_auth_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, true); +} + MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e735e2c6dd..cb34f969ad 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,49.0,, +Version,+,49.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e25254dddd..439fc7bf5a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,49.0,, +Version,+,49.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2288,6 +2288,8 @@ Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" From 99d657fcfbd4155a82f7eec3bd1b54851d131e4d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 1 Dec 2023 20:33:14 +0300 Subject: [PATCH 092/111] merge fixes --- applications/main/nfc/plugins/supported_cards/kazan.c | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index 68ef6c16f6..035d20d9f0 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -28,7 +28,6 @@ #include #include #include -#include "md5.h" #define TAG "Kazan" From f7c63c675b4862542878f6ab28a1f634d97812d5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 1 Dec 2023 20:38:01 +0300 Subject: [PATCH 093/111] upd changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c17d2cb1..d16d3a7cff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ **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
-**API was updated to v47.x** +**API was updated to v49.x** ## New changes * 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 @@ -24,6 +24,9 @@ * 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: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* 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 From 159aef022bed497b65e1a955ef9c9dbe036f4612 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:51:21 +0300 Subject: [PATCH 094/111] Fix keeloq decoding order --- lib/subghz/protocols/keeloq.c | 70 ++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 0bda954bad..464f27ac56 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -138,6 +138,7 @@ static bool subghz_protocol_keeloq_gen_data( uint64_t man = 0; uint64_t code_found_reverse; int res = 0; + // No mf name set? -> set to "" if(instance->manufacture_name == 0x0) { instance->manufacture_name = ""; } @@ -169,7 +170,11 @@ static bool subghz_protocol_keeloq_gen_data( hop = 0x1A2B3C4D; } if(counter_up && prog_mode == PROG_MODE_OFF) { + // Counter increment conditions + + // If counter is 0xFFFF we will reset it to 0 if(instance->generic.cnt < 0xFFFF) { + // Increase counter with value set in global settings (mult) if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; } else { @@ -182,13 +187,16 @@ static bool subghz_protocol_keeloq_gen_data( if(prog_mode == PROG_MODE_OFF) { // Protocols that do not use encryption if(strcmp(instance->manufacture_name, "Unknown") == 0) { + // Simple Replay of received code code_found_reverse = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); hop = code_found_reverse & 0x00000000ffffffff; } else if(strcmp(instance->manufacture_name, "AN-Motors") == 0) { + // An-Motors encode hop = (instance->generic.cnt & 0xFF) << 24 | (instance->generic.cnt & 0xFF) << 16 | (btn & 0xF) << 12 | 0x404; } else if(strcmp(instance->manufacture_name, "HCS101") == 0) { + // HCS101 Encode hop = instance->generic.cnt << 16 | (btn & 0xF) << 12 | 0x000; } else { // Protocols that use encryption @@ -235,6 +243,7 @@ static bool subghz_protocol_keeloq_gen_data( decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt; // Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning } + // Old type selector fixage for compatibilitiy with old signal files uint8_t kl_type_en = instance->keystore->kl_type; for M_EACH( @@ -302,6 +311,7 @@ static bool subghz_protocol_keeloq_gen_data( } } if(hop) { + // If we have hop - we will save it to generic data var that will be used later in transmission uint64_t yek = (uint64_t)fix << 32 | hop; instance->generic.data = subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); @@ -379,15 +389,18 @@ static bool subghz_custom_btn_set_original(btn); } + // No mf name set? -> set to "" if(instance->manufacture_name == 0x0) { instance->manufacture_name = ""; } + // Prog mode checks and extra fixage of MF Names ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); if(prog_mode == PROG_MODE_KEELOQ_BFT) { instance->manufacture_name = "BFT"; } else if(prog_mode == PROG_MODE_KEELOQ_APRIMATIC) { instance->manufacture_name = "Aprimatic"; } + // Custom button (programming mode button) for BFT and Aprimatic uint8_t klq_last_custom_btn = 0xA; if((strcmp(instance->manufacture_name, "BFT") == 0) || (strcmp(instance->manufacture_name, "Aprimatic") == 0)) { @@ -945,6 +958,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( } } + // MF not found *manufacture_name = "Unknown"; keystore->mfname = "Unknown"; instance->cnt = 0; @@ -956,6 +970,7 @@ static void subghz_protocol_keeloq_check_remote_controller( SubGhzBlockGeneric* instance, SubGhzKeystore* keystore, const char** manufacture_name) { + // Reverse key, split FIX and HOP parts uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); uint32_t key_fix = key >> 32; uint32_t key_hop = key & 0x00000000ffffffff; @@ -964,27 +979,54 @@ static void subghz_protocol_keeloq_check_remote_controller( // If we are in BFT / Aprimatic programming mode we will set previous remembered counter and skip mf keys check ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); if(prog_mode == PROG_MODE_OFF) { - // Check key AN-Motors - if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && - (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { - *manufacture_name = "AN-Motors"; - keystore->mfname = *manufacture_name; - instance->cnt = key_hop >> 16; - } else if((key_hop & 0xFFF) == (0x000) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f)) { - *manufacture_name = "HCS101"; - keystore->mfname = *manufacture_name; - instance->cnt = key_hop >> 16; + // Case when we have no mf name means that we are checking for the first time and we have to check all conditions + if((strlen(keystore->mfname) < 1) && strlen(*manufacture_name) < 1) { + // Check key AN-Motors + if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && + (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { + *manufacture_name = "AN-Motors"; + keystore->mfname = *manufacture_name; + instance->cnt = key_hop >> 16; + } else if((key_hop & 0xFFF) == (0x000) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f)) { + *manufacture_name = "HCS101"; + keystore->mfname = *manufacture_name; + instance->cnt = key_hop >> 16; + } else { + subghz_protocol_keeloq_check_remote_controller_selector( + instance, key_fix, key_hop, keystore, manufacture_name); + } } else { - subghz_protocol_keeloq_check_remote_controller_selector( - instance, key_fix, key_hop, keystore, manufacture_name); + // If we have mfname and its one of AN-Motors or HCS101 we should preform only check for this system + if((strcmp(keystore->mfname, "AN-Motors") == 0) || + (strcmp(keystore->mfname, "HCS101") == 0)) { + // Check key AN-Motors + if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && + (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { + *manufacture_name = "AN-Motors"; + keystore->mfname = *manufacture_name; + instance->cnt = key_hop >> 16; + } else if( + (key_hop & 0xFFF) == (0x000) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f)) { + *manufacture_name = "HCS101"; + keystore->mfname = *manufacture_name; + instance->cnt = key_hop >> 16; + } + } else { + // Else we have mfname that is not AN-Motors or HCS101 we should check it via default selector + subghz_protocol_keeloq_check_remote_controller_selector( + instance, key_fix, key_hop, keystore, manufacture_name); + } } + // Save original counter as temp counter in case of later usage of prog mode temp_counter = instance->cnt; } else if(prog_mode == PROG_MODE_KEELOQ_BFT) { + // When we are in prog mode we should fix mfname and apply temp counter *manufacture_name = "BFT"; keystore->mfname = *manufacture_name; instance->cnt = temp_counter; } else if(prog_mode == PROG_MODE_KEELOQ_APRIMATIC) { + // When we are in prog mode we should fix mfname and apply temp counter *manufacture_name = "Aprimatic"; keystore->mfname = *manufacture_name; instance->cnt = temp_counter; @@ -993,6 +1035,7 @@ static void subghz_protocol_keeloq_check_remote_controller( furi_crash("Unsupported Prog Mode"); } + // Get serial and button code from FIX part of the key instance->serial = key_fix & 0x0FFFFFFF; instance->btn = key_fix >> 28; @@ -1000,6 +1043,7 @@ static void subghz_protocol_keeloq_check_remote_controller( if(subghz_custom_btn_get_original() == 0) { subghz_custom_btn_set_original(instance->btn); } + // Set max custom buttons subghz_custom_btn_set_max(4); } @@ -1265,7 +1309,7 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output code_found_reverse_hi, code_found_reverse_lo, instance->generic.btn, - instance->manufacture_name); + instance->manufacture_name); } else { furi_string_cat_printf( output, From 6a5d63803aeadc9f2b3cb4f150bb32dee841dc03 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Sat, 2 Dec 2023 07:45:47 +0300 Subject: [PATCH 095/111] [FL-3675] Ntag21x write (#3246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: あく --- .../mf_ultralight/mf_ultralight.c | 13 ++ .../main/nfc/scenes/nfc_scene_config.h | 4 + .../scenes/nfc_scene_mf_ultralight_write.c | 119 +++++++++++++++ .../nfc_scene_mf_ultralight_write_fail.c | 67 +++++++++ .../nfc_scene_mf_ultralight_write_success.c | 43 ++++++ .../nfc_scene_mf_ultralight_wrong_card.c | 58 ++++++++ .../mf_ultralight/mf_ultralight_poller.c | 139 +++++++++++++++++- .../mf_ultralight/mf_ultralight_poller.h | 16 ++ .../mf_ultralight/mf_ultralight_poller_i.h | 7 + 9 files changed, 462 insertions(+), 4 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index c4fd04c7e5..3e27fc539e 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -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; } diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index f415c66a6f..a9887996d6 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -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) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c new file mode 100644 index 0000000000..b3c1beef5a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c @@ -0,0 +1,119 @@ +#include "../nfc_app_i.h" + +#include + +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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c new file mode 100644 index 0000000000..dff5f27815 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c @@ -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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c new file mode 100644 index 0000000000..c1fbc35ee5 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c @@ -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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c new file mode 100644 index 0000000000..a225c474db --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c @@ -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); +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 86ab68c8b1..619cd8c5fb 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -224,11 +224,24 @@ static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance instance->tearing_flag_read = 0; instance->tearing_flag_total = 3; instance->pages_read = 0; - instance->state = MfUltralightPollerStateReadVersion; - + instance->state = MfUltralightPollerStateRequestMode; + instance->current_page = 0; return NfcCommandContinue; } +static NfcCommand mf_ultralight_poller_handler_request_mode(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->mfu_event.type = MfUltralightPollerEventTypeRequestMode; + instance->mfu_event.data->poller_mode = MfUltralightPollerModeRead; + + command = instance->callback(instance->general_event, instance->context); + instance->mode = instance->mfu_event.data->poller_mode; + + instance->state = MfUltralightPollerStateReadVersion; + return command; +} + static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) { instance->error = mf_ultralight_poller_read_version(instance, &instance->data->version); if(instance->error == MfUltralightErrorNone) { @@ -259,6 +272,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo } static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) { + MfUltralightPollerState next_state = MfUltralightPollerStateGetFeatureSet; MfUltralightPageReadCommandData data = {}; instance->error = mf_ultralight_poller_read_page(instance, 41, &data); if(instance->error == MfUltralightErrorNone) { @@ -268,8 +282,13 @@ static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller FURI_LOG_D(TAG, "Original Ultralight detected"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->data->type = MfUltralightTypeUnknown; + if(instance->mode == MfUltralightPollerModeWrite) { + instance->mfu_event.type = MfUltralightPollerEventTypeCardMismatch; + instance->callback(instance->general_event, instance->context); + next_state = MfUltralightPollerStateWriteFail; + } } - instance->state = MfUltralightPollerStateGetFeatureSet; + instance->state = next_state; return NfcCommandContinue; } @@ -508,6 +527,7 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* instance) { FURI_LOG_D(TAG, "Read Failed"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.type = MfUltralightPollerEventTypeReadFailed; instance->mfu_event.data->error = instance->error; NfcCommand command = instance->callback(instance->general_event, instance->context); instance->state = MfUltralightPollerStateIdle; @@ -516,15 +536,121 @@ static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* ins static NfcCommand mf_ultralight_poller_handler_read_success(MfUltralightPoller* instance) { FURI_LOG_D(TAG, "Read success"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->mfu_event.type = MfUltralightPollerEventTypeReadSuccess; NfcCommand command = instance->callback(instance->general_event, instance->context); + + if(instance->mode == MfUltralightPollerModeRead) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->state = MfUltralightPollerStateIdle; + } else { + instance->state = MfUltralightPollerStateRequestWriteData; + } + + return command; +} + +static NfcCommand mf_ultralight_poller_handler_request_write_data(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Check writing capability"); + NfcCommand command = NfcCommandContinue; + MfUltralightPollerState next_state = MfUltralightPollerStateWritePages; + instance->current_page = 4; + + instance->mfu_event.type = MfUltralightPollerEventTypeRequestWriteData; + instance->callback(instance->general_event, instance->context); + + const MfUltralightData* write_data = instance->mfu_event.data->write_data; + const MfUltralightData* tag_data = instance->data; + uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); + + bool check_passed = false; + do { + if(write_data->type != tag_data->type) { + FURI_LOG_D(TAG, "Incorrect tag type"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardMismatch; + break; + } + + if(!instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Unknown password"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + break; + } + + const MfUltralightPage staticlock_page = tag_data->page[2]; + if(staticlock_page.data[2] != 0 || staticlock_page.data[3] != 0) { + FURI_LOG_D(TAG, "Static lock bits are set"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + break; + } + + if(mf_ultralight_support_feature(features, MfUltralightFeatureSupportDynamicLock)) { + uint8_t dynlock_num = mf_ultralight_get_config_page_num(tag_data->type) - 1; + const MfUltralightPage dynlock_page = tag_data->page[dynlock_num]; + if(dynlock_page.data[0] != 0 || dynlock_page.data[1] != 0) { + FURI_LOG_D(TAG, "Dynamic lock bits are set"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + break; + } + } + + check_passed = true; + } while(false); + + if(!check_passed) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + command = instance->callback(instance->general_event, instance->context); + next_state = MfUltralightPollerStateWriteFail; + } + + instance->state = next_state; + return command; +} + +static NfcCommand mf_ultralight_poller_handler_write_pages(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + const MfUltralightData* write_data = instance->mfu_event.data->write_data; + uint8_t end_page = mf_ultralight_get_config_page_num(write_data->type) - 1; + if(instance->current_page == end_page) { + instance->state = MfUltralightPollerStateWriteSuccess; + break; + } + FURI_LOG_D(TAG, "Writing page %d", instance->current_page); + MfUltralightError error = mf_ultralight_poller_write_page( + instance, instance->current_page, &write_data->page[instance->current_page]); + if(error != MfUltralightErrorNone) { + instance->state = MfUltralightPollerStateWriteFail; + instance->error = error; + break; + } + instance->current_page++; + } while(false); + + return command; +} + +static NfcCommand mf_ultralight_poller_handler_write_fail(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Write failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.data->error = instance->error; + instance->mfu_event.type = MfUltralightPollerEventTypeWriteFail; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Write success"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.type = MfUltralightPollerEventTypeWriteSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); return command; } static const MfUltralightPollerReadHandler mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, + [MfUltralightPollerStateRequestMode] = mf_ultralight_poller_handler_request_mode, [MfUltralightPollerStateReadVersion] = mf_ultralight_poller_handler_read_version, [MfUltralightPollerStateDetectMfulC] = mf_ultralight_poller_handler_check_ultralight_c, [MfUltralightPollerStateDetectNtag203] = mf_ultralight_poller_handler_check_ntag_203, @@ -538,6 +664,11 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateReadPages] = mf_ultralight_poller_handler_read_pages, [MfUltralightPollerStateReadFailed] = mf_ultralight_poller_handler_read_fail, [MfUltralightPollerStateReadSuccess] = mf_ultralight_poller_handler_read_success, + [MfUltralightPollerStateRequestWriteData] = + mf_ultralight_poller_handler_request_write_data, + [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, + [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, + [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, }; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 665d90cb70..2343be089b 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -16,13 +16,27 @@ typedef struct MfUltralightPoller MfUltralightPoller; * @brief Enumeration of possible MfUltralight poller event types. */ typedef enum { + MfUltralightPollerEventTypeRequestMode, /**< Poller requests for operating mode. */ MfUltralightPollerEventTypeAuthRequest, /**< Poller requests to fill authentication context. */ MfUltralightPollerEventTypeAuthSuccess, /**< Authentication succeeded. */ MfUltralightPollerEventTypeAuthFailed, /**< Authentication failed. */ MfUltralightPollerEventTypeReadSuccess, /**< Poller read card successfully. */ MfUltralightPollerEventTypeReadFailed, /**< Poller failed to read card. */ + MfUltralightPollerEventTypeRequestWriteData, /**< Poller request card data for write operation. */ + MfUltralightPollerEventTypeCardMismatch, /**< Type of card for writing differs from presented one. */ + MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */ + MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ + MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ } MfUltralightPollerEventType; +/** + * @brief Enumeration of possible MfUltralight poller operating modes. + */ +typedef enum { + MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */ + MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */ +} MfUltralightPollerMode; + /** * @brief MfUltralight poller authentication context. */ @@ -39,6 +53,8 @@ typedef struct { typedef union { MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ MfUltralightError error; /**< Error code indicating reading fail reason. */ + const MfUltralightData* write_data; + MfUltralightPollerMode poller_mode; } MfUltralightPollerEventData; /** diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index c89402b421..7c7354b1c6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -49,6 +49,7 @@ typedef union { typedef enum { MfUltralightPollerStateIdle, + MfUltralightPollerStateRequestMode, MfUltralightPollerStateReadVersion, MfUltralightPollerStateDetectMfulC, MfUltralightPollerStateDetectNtag203, @@ -61,6 +62,10 @@ typedef enum { MfUltralightPollerStateTryDefaultPass, MfUltralightPollerStateReadFailed, MfUltralightPollerStateReadSuccess, + MfUltralightPollerStateRequestWriteData, + MfUltralightPollerStateWritePages, + MfUltralightPollerStateWriteFail, + MfUltralightPollerStateWriteSuccess, MfUltralightPollerStateNum, } MfUltralightPollerState; @@ -68,6 +73,7 @@ typedef enum { struct MfUltralightPoller { Iso14443_3aPoller* iso14443_3a_poller; MfUltralightPollerState state; + MfUltralightPollerMode mode; BitBuffer* tx_buffer; BitBuffer* rx_buffer; MfUltralightData* data; @@ -79,6 +85,7 @@ struct MfUltralightPoller { uint8_t counters_total; uint8_t tearing_flag_read; uint8_t tearing_flag_total; + uint16_t current_page; MfUltralightError error; NfcGenericEvent general_event; From 93732865acac9053cb0c580a2bf3e58962b3a678 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 2 Dec 2023 08:52:04 +0400 Subject: [PATCH 096/111] [FL-3132] HID app: Add new function key icons (#3236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new function key icons * Fix graphical glitches on the buttons Co-authored-by: あく --- applications/system/hid_app/assets/Alt_11x7.png | Bin 2417 -> 0 bytes applications/system/hid_app/assets/Alt_17x10.png | Bin 0 -> 550 bytes applications/system/hid_app/assets/Cmd_15x7.png | Bin 2426 -> 0 bytes applications/system/hid_app/assets/Cmd_17x10.png | Bin 0 -> 556 bytes applications/system/hid_app/assets/Ctrl_15x7.png | Bin 2433 -> 0 bytes .../system/hid_app/assets/Ctrl_17x10.png | Bin 0 -> 552 bytes applications/system/hid_app/assets/Del_12x7.png | Bin 2417 -> 0 bytes applications/system/hid_app/assets/Del_17x10.png | Bin 0 -> 551 bytes applications/system/hid_app/assets/Esc_14x7.png | Bin 2430 -> 0 bytes applications/system/hid_app/assets/Esc_17x10.png | Bin 0 -> 550 bytes applications/system/hid_app/assets/Tab_15x7.png | Bin 2419 -> 0 bytes applications/system/hid_app/assets/Tab_17x10.png | Bin 0 -> 549 bytes applications/system/hid_app/views/hid_keyboard.c | 14 +++++++------- 13 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 applications/system/hid_app/assets/Alt_11x7.png create mode 100644 applications/system/hid_app/assets/Alt_17x10.png delete mode 100644 applications/system/hid_app/assets/Cmd_15x7.png create mode 100644 applications/system/hid_app/assets/Cmd_17x10.png delete mode 100644 applications/system/hid_app/assets/Ctrl_15x7.png create mode 100644 applications/system/hid_app/assets/Ctrl_17x10.png delete mode 100644 applications/system/hid_app/assets/Del_12x7.png create mode 100644 applications/system/hid_app/assets/Del_17x10.png delete mode 100644 applications/system/hid_app/assets/Esc_14x7.png create mode 100644 applications/system/hid_app/assets/Esc_17x10.png delete mode 100644 applications/system/hid_app/assets/Tab_15x7.png create mode 100644 applications/system/hid_app/assets/Tab_17x10.png diff --git a/applications/system/hid_app/assets/Alt_11x7.png b/applications/system/hid_app/assets/Alt_11x7.png deleted file mode 100644 index 3e4bf320ee313c725e228356b6bf58989e804441..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2417 zcmbtVdsGu=79SqVBOtIXmWphdCPf~SC-O=|SV#a}0MIvsdA#66UH}hCLeQNGkr)bq zcTa>1R32bv_F?`7bNu@j92v z`l8yzhW7qnMoXqL9u8oW115)RouiKo*y%c3U!Xp?HC)*;s#de{#`Mk&>7CTA2M#+O z-lDTv({%Ojqbaa?pl7CLb}3=}LU4di?!BDea*vzjVF*~Jz*o@)l&bxPA+j_S9Sc1i1mNL1KbF+DpzfjGLEg>amgp`<$n!g7O z4LeaQ_RUh>s}RCYx$OlJQn!|$bLVPD--*{vpQI+h?4C}~Gx1;gI8Wwje*SIvS*|+b z+_%NP&ts`Imd$ovpY19!#akK43s0ZzjW~YHXt>bR%EUU~jvih;?c16eQEmU7MeJof z^WboQtFW)=rjF{W%KAZYk!{hVKXKI?j~2B3zA-y>Vk2Ys+x$bIIh+=~`}A3o^_cYJ z*cs``x!Xe%gH4XVECMy5dlp;A$GRB4rAw5@o#&gTq+ygRrWvWyvgA(Vcm<`KDWD4q zv*v8bMI($&@1F(>?b~)r@+DpDkIYy&_sDm#)8!F)_5R_i`a9T9_y-Brd#HHp-R|RP$5K=5!=8%Rhwf3P zi-s9`nd5!oHLl~^{uxe6{{e|s2R!i#lyJ{b!;(amr%(OSHT;>bZ99-&r>r+hFo<3l znQikfOl+GiwB3@a85rT-{}EH6s!s;@x5f<7&{#C~6I)Cbu%|n9YFpyu#nXQ$jl#tr z_p5xPdZ`=-Nsd?3^(M)Vps|ggWgCm=`}Vq*yKO=A8F(z%e>fX;+0%TeT(5Ip+U~YLLDMh=lygg!Ga*WQb=;t? z$L*}^jS)fC9c8xTPotG`y8)m#tzp;F{PTV3PxQJ6f!Y&GdP{anlN;hY?Zkk{hav^> zLLuNp$VPyk&Rc*UA?Xk&pu+@o37JD&tj}RUe_0Oza^ea2NRT)P43;7|f=Lkt$a1AI zKnE6s<-+h_y3=Gd7R!?XGF>I{Viwa81RaDTF)Y^_I|6|23EfpRlM{NYvY{54=Dj_T zm{@c;G!l;#{(&tB%U3@_g`@*-n__C99OXE^punoT8aw|K@;dqPft%e zgGFbtsDuR-OO@jyB~^}5UVyyB;X{}hg%voA$U!ZxC=N-+y~t#3pw(lAr%WLfu7;9h zD|rza(v>0wok?TRWitBfJTW{3S|j;dPb@T50ntMs3`s`C5MfSv9S~8t|4ra^PBLZr+gf(V6dJ|a$*L2_Kc=MoB<1eUP3G5y?J`2Kz_?wSZMbQI|zk|ts&BO4VTHGzoJ{Q=g_q+wXVfp^zX8k zXkadhi1cz8a7jU`yfR`w>=5vMLf_q#b0C@ofJXY2^MiFZqHE;VgEL)Go44AN4T`EP zs4c-k#>KST&6=a;Qx$1IIfMUXdufGPq$qdvcazVeHJe9^ckO70@?kEP)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8|eBA@6YN^=FSz=Hm(eG{(jQj8)Dum3s32i>k78GhN>N&o-=07*qoM6N<$g1ch(GXMYp literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Cmd_15x7.png b/applications/system/hid_app/assets/Cmd_15x7.png deleted file mode 100644 index a63a4be747a89745cbb32b5c9fb55d754f600dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2426 zcmbtVdsGu=79So3iM+POA}pt*Ns))-i6kTqu|NWi-5{nsTv^1B3}k?0(##-1d9^^x z3RnxPRtt@fibYsq1q2#h1rb5^6xgQ15d`EVT@a)$LKR^r5rI9}?jJjIX6DYFdw=)d z-}l`+e+~%_vcfsy006KOyw4M1PaEtq-(ZH_yL0Sa0l?H63J3^E2nY&*6JgbdO0fhC z0u`VNl8A!*0l=rAAS_De6lTZ$s-b%8JKtyNN=pl^7rp;dlPK!SzB|4MR?GW5ixO-6 zjNxQQJvW-~>2@K1vAH+b`~mUF9#eDVQSV`n_qTs_C$#h1 z809B}i9cM;dX{mSe=niAH*4qT)zmb*0h9VC$30ZX?tf;h2^;A{b1TVE^-bF)umQ4| zAp%j_D>*9M47XVlcJPYRI9hgr*>!#EdEJRP+kq@~QX}Vf%M@>+mKI%%OG+0NTaMYh z1q=>(kg5*M5#Op3LLRy81rajFt)Fw}>&M=VHBOr%#X;=uPS&~DLw=r9dBZ>dF8n;V zIil?A zGMe%EP+zOCx9GN!=9ADjBX}DqHFDqC^Ek(G z+3E3fveWZF4o(gtoiG)>FGh*vDrQ)J~yofff5qK+n@ z2nw_2od`uEREM|E;tO5dK9uH5yVyIdy>A^OAkxPTUAhz_qDaHcyOKAa?ANjwC;f7; zrsL)g4b!%#qo*U`_R(&|UUr%yB2OC_uF>8>enQ`gbBrgUt#@@Ls*a_m=m*_Z(GQq= zEEmnwO)@5W!|S{!w*4a{(;Te?5}1K+!qAGaVgiJaGBQlnG1aqYG@ zDYF~~^8UVSaz>~6=~BD(P(D;rQ+x7Xhi{8I_1(EWmsvK06(*g}WbKU1xZ$hQKn1r{{Y0d8ZH^f* z_W3dEMq|WI$BuI6iP=cC!lfVRNxiyv>DluGX^%~G13>+$C6gr=^~Rf$JMGwj$Om@| zVueD$1CuuZCPuFVCYWS|oq!PyFvesKcF{kB08@h=02J60@RPzZAU;F}qc9?am3X>0 znTmI#Q|Vq*9~zS^C)4RHDxT&ojg`=8?s&YLP%MEIdSiD0Fg|6xf@X8dWJNaDBS$ge zkFkkGfkJnqyM=#XOJF%!tdB-k%awWqfTLz(q8voUc(puHfw0xS1cL<|ll5u}0dFut z6MPA~g(3I=SOwx4WGb0T;NtLjJVzysWs7)$%kJ2lFF}T)N;ZX(oSaNf_9DZoI0}u$ zVo|7c3Y|{EEJ#R-0u`%C3WWFqy^GhUgNro zyu$8ksfKoVu}k&zvIA707==}#Fr3K!KU&-n>A%3%WTD?P?42qI+Ztkh*KlbZ$~xV$ zJ%^%isWmP3qJIrqtbw^WEYiziz{SOuRSshFAymNg3*GEca5xVK*xH(wEG$tZI^C|J zL$f^%z2nt{Ga--b{~ecAmiV6MR}0tG@eKBfEez4@=ryZ5nJ0OpR!6h}7pY?r@<$Fc huhluV`!L-O&R7cBE}ON;j!OLi67Yk07yb8r@*g!;qu&4k diff --git a/applications/system/hid_app/assets/Cmd_17x10.png b/applications/system/hid_app/assets/Cmd_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..26ca3395c699cd19f2ae3c12d46954a45310eab7 GIT binary patch literal 556 zcmV+{0@MA8P)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8n!+r=$Vtj3glk1^)H`0000501vCmP2ndL{yrdq5DkeKY1oo)gKX&HK%$+;;{_gMI z@4I*Y9vTw7oajgd0ARW3FTybVX^USLmge}qGsnRd0L*NlfPm1%fZzZ)2}V9rNu*#f zs00y68W!vi0KNqU5ixS72z&mwH5F^#`z1?PQc`H6_x?v^Qdmd!tr0y~q3H1}N~#p> zX&$?M5L)duIC~6o=i3YH>c8(V>NiB!yS}@vbUcrGq%!n~S(e*O`;*|q*Bq=yYrHCI zi>ebZwD$ZqTs)a^uNR%@UACv%GkRaIi>asMY3kkU0~J??wOTG#pWePay`8$|%e@|Z zSDXBCu;JpZ@kvr+Z|CFl(}sl6)1kp8xwmt=%6wbM10bMZdF5=(PBX#NCacc487uAk zNyVgr6_=}ksJFkfeQ>tzz*nr?*g#LWQ~7#rcdo@f%Hz#u7U+ZSFL~bI|KLt;4Q%!) z{U(HR{X*8$%yWUa6Sdu0>vvZ$((U^Q)sOdkAp7p@veQHi^@gT602oAi=ILOPyTLYiXPi&Q>vFi;2k@Va3+Nw(kJ_ z10K}!9n+L|%EgdJZfil5+~?}Axii(nZ^fIYPf!yeZf85^c>EUv&xyRjU*Cv4!PiEe z{BFPhvsmhR`$iYP&$kp?V@-_Y*+);dM;$z1IZ()PSmv1TLXWJP@^8wFs&YMM7kduN z+&$3KBw@M#*Jr1Wtz>L{Q@Dp@3#CPGJ$i!ZI3hnh za$J6R=0^WmUxWK^dfz{dOPJ~AqMSks|PHKBxQ*m-q%BPoJLeS+-k{>rv1 zA86QiU2R=$i7k6Om0P&!%BZ~Bph!*aP4qtYUV`H`D%NyMcMxeaG{oNPERVg%-fXS6 z$RK2nc1ND~9$ou$Br*L*I9eP0&@V*BKTeHEl2jZ$vdKI0iK%ldkY1y%wM;ONo*kNQ zURRjdJYDF#I&-jhh=1o7RD)8o%ys8ol4^UAucU8Z7tt^U4trX zRbSpu8^)$|$o;deHUs(4!OE&{{{6+}=Apqj;u)65Th@0k9daA}airF%qr|3azx*yx z{t3&s0T$_>IdrEU2T_f=@2u zM%Lx}sP&F*rOu;|qSZ>5UZ5-O!WP5RXFJj#5_Ek)^&tbn;G$l3MS8Op9}xNAMp3+2 z40zzOB|tD)0T6J>1U~^2CSZ!oJp5wqf&jBQJpd^2Cm@i)aUdyF4r4GXhgBq&H;qAZ zV=-7>3|}Ujrl7G{90rN$EsK}3nC>K!n^+=+ltyEB05Cmdx`1YTh_E1A7?ESx@Td61 zqC?>uv5n%ta;30>CNV~%sTC@t0l-spaZv$c5|Ubxq(r%DKk}Rf7nhA{I+-+Qf+hNq zH;O|^0WboRd}s_BgUlzANF*L2i|2+3gXZ1wH$So*!&F>4JtZZDmf}T&kpw!E!{N{w zEINxt#Vx34suGi^sY;ac9OMO#5JaU2q{1LrNiyO};^1V=k4!cOS~%u;DpX?eLMSCV zpBJ7XT`f`3nKTAnp`gFY6UD;7MUt=dM8i{6AUzC3;bcS#;^vfB0pW%F-vnOeghBDI z32yu{$9thBc*;CHh*-=O!qQ{~sKi7k$aw8WLmI@R>aNOS*Y`9^KedOIqxC|ITUg z2Idp-NH2x~pLi|e*bRI>M2m!i@V8gx>=vxJM6x9084PH0_rF5MbS?FO2pDzR5MjZ* zwk&l7d}deg9ccNd!iI}=F85dO%9S@&l&}v#b~bM?zl!cVm9q)ib$xhxQ?%EK84a&h c-wD*%=rG#yo8xYw#yKPk3=y98-}>2q0D4cRIRF3v diff --git a/applications/system/hid_app/assets/Ctrl_17x10.png b/applications/system/hid_app/assets/Ctrl_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..0eda72160009907c74a5f44e2dd90cd923f1fdfb GIT binary patch literal 552 zcmV+@0@wYCP)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8xs5R007ntcL;*T1E?xm8=0AOSR^Z7w>e1AR?kD%|VBvQy9 zQbH&!4fgi|09IaJXq0?Ys5S5J)n!{Z|CG^LRFrQq@BU?Zd~iqRtAM6VKY)s_wgif`Ns|I)}IS72nUHoGK4GWt8DK(?08e@?SP)W7V!@ zH3e01S6h33A1RznyVs|l=v#NF$|dqhpS_`r?FGu+p9jlsjOcV6tS+^EeQG;p>*t4^ z4{tH}^HBY@TlXiy#=fq{m!}tE$1Vi<8)VMmimkOm=Oe#7-kQ3s5?pEQ|uMbDV& zw#65MgReGJ0O4aI^2t8U!=li*R2+yYS?Uw z3`Et`W}(C>o=zJ2LG7kd?A&?Jjvuz3?K~E1)t{kBsN=RYP6*~I7~(==LRxU4`H1CP zz`&q0rF8!^`K?kB?3~@27cTd>@muyx)yV5HhN%;jSeVn*&OQ_KnYYVC&d_gv3Omcw zg`fN8sLxX|<+637z1PQk3eB-5dcxeJ*~IYU$4m$F**5EJbM0wi6;nP<>ERU)-&%<) zvGjw3y-lK?f))eqHLdO4paQ#s`+wuc81Jul`h6a_9I|h|X>7EE?o+fte$alteo`Jvt~F0fmKP_qo5iTeoeUvO zn4dATiBvF5-}v@vFyEp1Jz1`-gR|4Z{l*cJTK2HIqchon9IW-ssm&Qp@@|}um3=i( z-gbSb*3+uHt-CF*<#3mB4<}U_o>Su&rmeZD{uSFCYrBtvHQnkwjLxr-)PBc-GtN zRT&%mbN>16_*5tQ>!ntU!Cd%wdBxZN{;Z*Scqp29k?H)p`Q0B*IF4Nzt=ZI3WKnTc zeitbHfXS*yg!4~rdXmpTYOOJA&SBg1?n<`hKnbD!iM-Y0Kc1EIfI-KlLG@vmdBBu|Qa`*Q~oPz1fNnh+HT_ z7$Xt^&bVv}5DZ=g2)JZ`pMU`aFw~C_{9=9r14fH_08rvjz*~kyLtu~`!4S0^QGrZ% zDjjrW(wVMw7Q>UOpfZ_kI>>OB#YmY9ClGWLNu;n+Z|npBh9?Y{(acW}mSqz?vc>mq zuiz7l28Tpo5u$fEQba+O=%Z0J3YFdf;A%LysDLmDs8Pf#)f|l%Y0-j%%X&491TLCj zabBbdQ4q*SPzdy((y4S3j|hSw7nQ|uf(3p{?)aM*NseJE4vm(SltfK(r6Oo7jlpKK zX>=xy$)w;G6m_x^lV~VPHTfCHa~uJrmZGoAzP zIOZk6^~q?|AvW>%nwBri_55z zplV28QZLf;ny$`KpXTDCaNxCkJiX~`7Tufmaz1NZSNN$a$P}QuI@^z zi|z1im+H&Q;iHfQL(mWeiRb+vExs7(zra?p(C-;eAPVDKL!$2*9)n9;qg%4)()2C0 zs>Po5@1hoOU>*^V^n4ibh&vkWJn{LkODOOTd1GVN!44t-niAY+7sQnTK`9ZNcc54D zdVd^D3=VgLXD diff --git a/applications/system/hid_app/assets/Del_17x10.png b/applications/system/hid_app/assets/Del_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..13d736983079f920184d157d83054efe809a6981 GIT binary patch literal 551 zcmV+?0@(eDP)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8Askdnas~hZ002ovPDHLkV1h}~`Pcve literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Esc_14x7.png b/applications/system/hid_app/assets/Esc_14x7.png deleted file mode 100644 index 3be4691d8a3d050bf14f5bd29099705d984eead7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2430 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 17ff754f52..9060c1d6a6 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -49,7 +49,7 @@ typedef struct { #define ROW_COUNT 7 #define COLUMN_COUNT 12 -// 0 width items are not drawn, but there value is used +// 0 width items are not drawn, but their value is used const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { { {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, @@ -140,17 +140,17 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, }, { - {.width = 2, .icon = &I_Ctrl_15x7, .value = HID_KEYBOARD_L_CTRL}, + {.width = 2, .icon = &I_Ctrl_17x10, .value = HID_KEYBOARD_L_CTRL}, {.width = 0, .value = HID_KEYBOARD_L_CTRL}, - {.width = 2, .icon = &I_Alt_11x7, .value = HID_KEYBOARD_L_ALT}, + {.width = 2, .icon = &I_Alt_17x10, .value = HID_KEYBOARD_L_ALT}, {.width = 0, .value = HID_KEYBOARD_L_ALT}, - {.width = 2, .icon = &I_Cmd_15x7, .value = HID_KEYBOARD_L_GUI}, + {.width = 2, .icon = &I_Cmd_17x10, .value = HID_KEYBOARD_L_GUI}, {.width = 0, .value = HID_KEYBOARD_L_GUI}, - {.width = 2, .icon = &I_Tab_15x7, .value = HID_KEYBOARD_TAB}, + {.width = 2, .icon = &I_Tab_17x10, .value = HID_KEYBOARD_TAB}, {.width = 0, .value = HID_KEYBOARD_TAB}, - {.width = 2, .icon = &I_Esc_14x7, .value = HID_KEYBOARD_ESCAPE}, + {.width = 2, .icon = &I_Esc_17x10, .value = HID_KEYBOARD_ESCAPE}, {.width = 0, .value = HID_KEYBOARD_ESCAPE}, - {.width = 2, .icon = &I_Del_12x7, .value = HID_KEYBOARD_DELETE_FORWARD}, + {.width = 2, .icon = &I_Del_17x10, .value = HID_KEYBOARD_DELETE_FORWARD}, {.width = 0, .value = HID_KEYBOARD_DELETE_FORWARD}, }, }; From 04cead1fc56d4093bffee8d3a4a7b47fba93061f Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 2 Dec 2023 09:03:10 +0400 Subject: [PATCH 097/111] [FL-3620] Add the "remove pairing" button to BLE hid (#3237) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/system/hid_app/hid.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index a42fc60917..88a68f09d0 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -14,8 +14,22 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexMouse, HidSubmenuIndexMouseClicker, HidSubmenuIndexMouseJiggler, + HidSubmenuIndexRemovePairing, }; +static void bt_hid_remove_pairing(Bt* bt) { + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_forget_bonded_devices(bt); + + furi_hal_bt_start_advertising(); +} + static void hid_submenu_callback(void* context, uint32_t index) { furi_assert(context); Hid* app = context; @@ -45,6 +59,8 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMouseJiggler) { app->view_id = HidViewMouseJiggler; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); + } else if(index == HidSubmenuIndexRemovePairing) { + bt_hid_remove_pairing(app->bt); } } @@ -143,6 +159,14 @@ Hid* hid_alloc(HidTransport transport) { HidSubmenuIndexMouseJiggler, hid_submenu_callback, app); + if(transport == HidTransportBle) { + submenu_add_item( + app->device_type_submenu, + "Remove Pairing", + HidSubmenuIndexRemovePairing, + hid_submenu_callback, + app); + } view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); From c6a14e1a6779ded87b848b06b2cbc51a147f96b3 Mon Sep 17 00:00:00 2001 From: pborsutzki Date: Sat, 2 Dec 2023 08:27:58 +0100 Subject: [PATCH 098/111] Fixed a zero allocation error when reading an iso15693 nfc tag with no additional blocks. (#3229) Co-authored-by: gornekich --- .../iso15693_3/iso15693_3_poller_i.c | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index 917f7dbb8e..ca6f5435e3 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -108,28 +108,30 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_ break; } - // Read blocks: Optional command - simple_array_init(data->block_data, system_info->block_count * system_info->block_size); - ret = iso15693_3_poller_read_blocks( - instance, - simple_array_get_data(data->block_data), - system_info->block_count, - system_info->block_size); - if(ret != Iso15693_3ErrorNone) { - ret = iso15693_3_poller_filter_error(ret); - break; - } - - // Get block security status: Optional command - simple_array_init(data->block_security, system_info->block_count); - - ret = iso15693_3_poller_get_blocks_security( - instance, simple_array_get_data(data->block_security), system_info->block_count); - if(ret != Iso15693_3ErrorNone) { - ret = iso15693_3_poller_filter_error(ret); - break; + if(system_info->block_count > 0) { + // Read blocks: Optional command + simple_array_init( + data->block_data, system_info->block_count * system_info->block_size); + ret = iso15693_3_poller_read_blocks( + instance, + simple_array_get_data(data->block_data), + system_info->block_count, + system_info->block_size); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + // Get block security status: Optional command + simple_array_init(data->block_security, system_info->block_count); + + ret = iso15693_3_poller_get_blocks_security( + instance, simple_array_get_data(data->block_security), system_info->block_count); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } } - } while(false); return ret; From eb6fe0a4dbe2e6d3776483e6a191de2307b0060e Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sat, 2 Dec 2023 11:34:02 +0400 Subject: [PATCH 099/111] SubGhz: fix count bit for detect gate_tx protocol (#3253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/gate_tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 51a424fed9..2ebd6bb03b 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -227,7 +227,7 @@ void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t du if(duration >= ((uint32_t)subghz_protocol_gate_tx_const.te_short * 10 + subghz_protocol_gate_tx_const.te_delta)) { instance->decoder.parser_step = GateTXDecoderStepFoundStartBit; - if(instance->decoder.decode_count_bit >= + if(instance->decoder.decode_count_bit == subghz_protocol_gate_tx_const.min_count_bit_for_found) { instance->generic.data = instance->decoder.decode_data; instance->generic.data_count_bit = instance->decoder.decode_count_bit; From ec99b70b38319065a4d6bb39663ab1bca64b2074 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 2 Dec 2023 23:55:05 +0300 Subject: [PATCH 100/111] upd changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d16d3a7cff..2dd99b1838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,15 @@ * 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) * API: Add new get function for varitemlist (by @Willy-JL) * Misc code cleanup * 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: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* 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 From c477d1321af5a25df57d1cbbf709c9853f768f78 Mon Sep 17 00:00:00 2001 From: Honghao Zeng Date: Sun, 3 Dec 2023 20:00:46 +0900 Subject: [PATCH 101/111] nfc: m1k-based Aime (non-AIC) card support (#3241) Co-authored-by: gornekich --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/aime.c | 164 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/aime.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 9a98b57c8c..07e97c0c9c 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -74,6 +74,15 @@ App( sources=["plugins/supported_cards/two_cities.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="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/aime.c b/applications/main/nfc/plugins/supported_cards/aime.c new file mode 100644 index 0000000000..1db89ffd63 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/aime.c @@ -0,0 +1,164 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#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 + for(int i = 0; i < 24; i++) { + if(aime_accesscode_str[i] == ' ') continue; + if(aime_accesscode_str[i] < '0' || aime_accesscode_str[i] > '9') return false; + } + + // 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; +} From 9bf8f1015d6b4bc03478cbc8ad9e761ab3cc0be4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 3 Dec 2023 18:41:28 +0300 Subject: [PATCH 102/111] fix keeloq null pointer if unknown --- lib/subghz/protocols/keeloq.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 464f27ac56..700f1f6d02 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -979,6 +979,13 @@ static void subghz_protocol_keeloq_check_remote_controller( // If we are in BFT / Aprimatic programming mode we will set previous remembered counter and skip mf keys check ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); if(prog_mode == PROG_MODE_OFF) { + if(keystore->mfname == 0x0) { + keystore->mfname = ""; + } + if(*manufacture_name == 0x0) { + *manufacture_name = ""; + } + // Case when we have no mf name means that we are checking for the first time and we have to check all conditions if((strlen(keystore->mfname) < 1) && strlen(*manufacture_name) < 1) { // Check key AN-Motors From dc25bfb831823e5fe62d66185cf3a2b8354bedd4 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 4 Dec 2023 16:03:32 -0500 Subject: [PATCH 103/111] Add Saflok and MyKey KDFs --- applications/main/nfc/application.fam | 18 ++ .../main/nfc/plugins/supported_cards/mykey.c | 137 ++++++++++++++ .../main/nfc/plugins/supported_cards/saflok.c | 174 ++++++++++++++++++ 3 files changed, 329 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/mykey.c create mode 100644 applications/main/nfc/plugins/supported_cards/saflok.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index cd141e8a15..4a31458d82 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -119,6 +119,24 @@ App( 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"], diff --git a/applications/main/nfc/plugins/supported_cards/mykey.c b/applications/main/nfc/plugins/supported_cards/mykey.c new file mode 100644 index 0000000000..d7a0ea4fda --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/mykey.c @@ -0,0 +1,137 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include + +//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; +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/saflok.c b/applications/main/nfc/plugins/supported_cards/saflok.c new file mode 100644 index 0000000000..55edd2efab --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/saflok.c @@ -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 +#include +#include +#include +#include + +#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; +} From 10444b943e31fc03aa34e40f74e1c89760783541 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 5 Dec 2023 03:34:31 +0300 Subject: [PATCH 104/111] Fix keeloq decode logic --- lib/subghz/protocols/keeloq.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 700f1f6d02..2a92e9db5c 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -987,7 +987,7 @@ static void subghz_protocol_keeloq_check_remote_controller( } // Case when we have no mf name means that we are checking for the first time and we have to check all conditions - if((strlen(keystore->mfname) < 1) && strlen(*manufacture_name) < 1) { + if(strlen(keystore->mfname) < 1) { // Check key AN-Motors if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { @@ -1004,20 +1004,16 @@ static void subghz_protocol_keeloq_check_remote_controller( } } else { // If we have mfname and its one of AN-Motors or HCS101 we should preform only check for this system - if((strcmp(keystore->mfname, "AN-Motors") == 0) || - (strcmp(keystore->mfname, "HCS101") == 0)) { - // Check key AN-Motors - if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && - (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { - *manufacture_name = "AN-Motors"; - keystore->mfname = *manufacture_name; - instance->cnt = key_hop >> 16; - } else if( - (key_hop & 0xFFF) == (0x000) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f)) { - *manufacture_name = "HCS101"; - keystore->mfname = *manufacture_name; - instance->cnt = key_hop >> 16; - } + if(strcmp(keystore->mfname, "AN-Motors") == 0) { + // Force key to AN-Motors + *manufacture_name = "AN-Motors"; + keystore->mfname = *manufacture_name; + instance->cnt = key_hop >> 16; + } else if(strcmp(keystore->mfname, "HCS101") == 0) { + // Force key to HCS101 + *manufacture_name = "HCS101"; + keystore->mfname = *manufacture_name; + instance->cnt = key_hop >> 16; } else { // Else we have mfname that is not AN-Motors or HCS101 we should check it via default selector subghz_protocol_keeloq_check_remote_controller_selector( From c41604137974c70689a8a89f8c86ae76f43123c3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 5 Dec 2023 03:40:12 +0300 Subject: [PATCH 105/111] Fix secplus v1 key display issue --- lib/subghz/protocols/secplus_v1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index a14f487ca6..8d41582bf4 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -563,7 +563,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou furi_string_cat_printf( output, "%s %db\r\n" - "Key:0x%lX%08lX\r\n" + "Key:%lX%08lX\r\n" "id1:%d id0:%d", instance->generic.protocol_name, instance->generic.data_count_bit, @@ -600,7 +600,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou output, "Sn:0x%08lX\r\n" "Cnt:0x%03lX " - "Sw_id:0x%X\r\n", + "SwID:0x%X\r\n", instance->generic.serial, instance->generic.cnt, instance->generic.btn); @@ -619,7 +619,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou output, "Sn:0x%08lX\r\n" "Cnt:0x%03lX " - "Sw_id:0x%X\r\n", + "SwID:0x%X\r\n", instance->generic.serial, instance->generic.cnt, instance->generic.btn); From 82baf1e9232ad72b565cfe33e6b5177a32daa4fe Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 5 Dec 2023 17:40:06 +0400 Subject: [PATCH 106/111] [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 --- applications/main/nfc/nfc_app.c | 19 ++++++++++--------- .../main/nfc/plugins/supported_cards/aime.c | 7 ++++++- .../main/nfc/scenes/nfc_scene_detect.c | 1 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 6 +----- .../nfc_scene_mf_ultralight_unlock_warn.c | 3 ++- .../nfc/scenes/nfc_scene_select_protocol.c | 1 - applications/main/nfc/views/dict_attack.c | 1 - .../iso14443_3b/iso14443_3b_poller_i.c | 15 ++++++++++++--- .../mf_classic/mf_classic_listener.c | 7 +++++++ 9 files changed, 39 insertions(+), 21 deletions(-) diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 141a67e5cb..5ae0ca5f57 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -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); - - if(result) { - result = nfc_load_file(instance, instance->file_path, true); - } - - return result; + 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); + + return success; } void nfc_show_loading_popup(void* context, bool show) { diff --git a/applications/main/nfc/plugins/supported_cards/aime.c b/applications/main/nfc/plugins/supported_cards/aime.c index 1db89ffd63..df1e7e0772 100644 --- a/applications/main/nfc/plugins/supported_cards/aime.c +++ b/applications/main/nfc/plugins/supported_cards/aime.c @@ -120,10 +120,15 @@ static bool aime_parse(const NfcDevice* device, FuriString* parsed_data) { 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') return false; + 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. diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c index 326b1458c0..593c67aabe 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect.c +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -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); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 7f51b71741..721919d2b1 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -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); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index 6be051cedd..e3bbfba59a 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -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; diff --git a/applications/main/nfc/scenes/nfc_scene_select_protocol.c b/applications/main/nfc/scenes/nfc_scene_select_protocol.c index 86b9982fc6..7a5d125218 100644 --- a/applications/main/nfc/scenes/nfc_scene_select_protocol.c +++ b/applications/main/nfc/scenes/nfc_scene_select_protocol.c @@ -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++) { diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index b656c2dc5b..ce86790883 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -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; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c index 1ee5237c64..15fc609dbc 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c @@ -131,9 +131,18 @@ Iso14443_3bError iso14443_3b_poller_activate(Iso14443_3bPoller* instance, Iso144 break; } - if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1 || - bit_buffer_get_byte(instance->rx_buffer, 0) != 0) { - FURI_LOG_D(TAG, "Unexpected ATTRIB response"); + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { + FURI_LOG_W( + TAG, + "Unexpected ATTRIB response length: %zu", + bit_buffer_get_size_bytes(instance->rx_buffer)); + } + + if(bit_buffer_get_byte(instance->rx_buffer, 0) != 0) { + FURI_LOG_D( + TAG, + "Incorrect CID in ATTRIB response: %02X", + bit_buffer_get_byte(instance->rx_buffer, 0)); instance->state = Iso14443_3bPollerStateActivationFailed; ret = Iso14443_3bErrorCommunication; break; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index fb12ba8a95..3423e89e4b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -481,6 +481,13 @@ static const MfClassicListenerCmd mf_classic_listener_cmd_handlers[] = { .command_num = COUNT_OF(mf_classic_listener_halt_handlers), .handler = mf_classic_listener_halt_handlers, }, + { + // This crutch is necessary since some devices (like Pixel) send 15-bit "HALT" command ... + .cmd_start_byte = MF_CLASSIC_CMD_HALT_MSB, + .cmd_len_bits = 15, + .command_num = COUNT_OF(mf_classic_listener_halt_handlers), + .handler = mf_classic_listener_halt_handlers, + }, { .cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_A, .cmd_len_bits = 2 * 8, From e94beff204e807c5ede1e277076e7cda1f055deb Mon Sep 17 00:00:00 2001 From: Methodius Date: Wed, 6 Dec 2023 18:08:20 +0900 Subject: [PATCH 107/111] Kazan parser: basic tariffs parsing added --- .../main/nfc/plugins/supported_cards/kazan.c | 119 ++++++++++++++---- 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index 035d20d9f0..dbc5bc56f3 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -16,7 +16,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#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" @@ -36,7 +38,7 @@ typedef struct { uint64_t b; } MfClassicKeyPair; -static const MfClassicKeyPair kazan_1k_keys[] = { +static const MfClassicKeyPair kazan_1k_keys_standart[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, @@ -55,22 +57,48 @@ static const MfClassicKeyPair kazan_1k_keys[] = { {.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, + SUBSCRIPTION_TYPE_ABONNEMENT_BY_TIME, + SUBSCRIPTION_TYPE_ABONNEMENT_BY_TRIPS, }; -enum SubscriptionType get_subscription_type(uint8_t value) { +enum SubscriptionType get_subscription_type(uint8_t value, FuriString* tariff_name) { switch(value) { - case 0: - case 0x60: case 0x67: + furi_string_printf(tariff_name, "Ground electric transport. 1 month"); + return SUBSCRIPTION_TYPE_ABONNEMENT_BY_TIME; case 0x0F: - return SUBSCRIPTION_TYPE_ABONNEMENT; + 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; } } @@ -79,25 +107,25 @@ static bool kazan_verify(Nfc* nfc) { bool verified = false; do { - const uint8_t ticket_sector_number = 8; - 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); + 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[ticket_sector_number].a, COUNT_OF(key.data), key.data); + 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, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + nfc, verification_block_number, &key, MfClassicKeyTypeA, &auth_context); if(error != MfClassicErrorNone) { - FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + FURI_LOG_D(TAG, "Failed to read block %u: %d", verification_block_number, error); break; } verified = true; } while(false); - return verified; } @@ -122,18 +150,40 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { .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[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + 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); - nfc_util_num2bytes(kazan_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + 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"); + 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); @@ -153,25 +203,30 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { bool parsed = false; do { + const uint8_t verification_sector_number = 10; const uint8_t ticket_sector_number = 8; const uint8_t balance_sector_number = 9; // Verify keys MfClassicKeyPair keys = {}; const MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); + 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)); - keys.b = nfc_util_bytes2num(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); - if((keys.a != 0xE954024EE754) && (keys.b != 0x0CD464CDC100)) break; + 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]; - enum SubscriptionType subscription_type = get_subscription_type(block_start_ptr[0]); + 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]; @@ -222,10 +277,25 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { valid_to.year); } - if(subscription_type == SUBSCRIPTION_TYPE_ABONNEMENT) { + 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\nTrips left: %lu\nCard valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u", + "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, @@ -238,7 +308,8 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { if(subscription_type == SUBSCRIPTION_TYPE_UNKNOWN) { furi_string_cat_printf( parsed_data, - "Type: unknown\nBalance: %lu RUR\nValid from: %02u.%02u.%u\nValid to: %02u.%02u.%u", + "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, From 1b45b8a17d96e9544aff27bb456e8e8591752bca Mon Sep 17 00:00:00 2001 From: Methodius Date: Wed, 6 Dec 2023 18:25:47 +0900 Subject: [PATCH 108/111] Kazan parser: adult social tariff added --- applications/main/nfc/plugins/supported_cards/kazan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index dbc5bc56f3..18e4da1ee5 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -85,6 +85,9 @@ enum SubscriptionType { 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; From 15a29e148392689640eb38dd1392eb1c5e9e5f0a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 7 Dec 2023 08:21:32 +0300 Subject: [PATCH 109/111] format --- .../main/nfc/plugins/supported_cards/social_moscow.c | 2 +- applications/main/nfc/plugins/supported_cards/troika.c | 10 +++++----- .../main/subghz/scenes/subghz_scene_saved_menu.c | 2 +- applications/main/subghz_remote | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index ca52661b2f..ddd3c2db51 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -671,7 +671,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date) * 24 * 60 + card_start_trip_time, + (card_start_trip_date)*24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 3f252390f6..8003352903 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -620,7 +620,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date) * 24 * 60 + card_start_trip_time, + (card_start_trip_date)*24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -697,7 +697,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date) * 24 * 60 + card_start_trip_time, + (card_start_trip_date)*24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -871,7 +871,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date) * 24 * 60 + card_start_trip_time, + (card_start_trip_date)*24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -952,7 +952,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date) * 24 * 60 + card_start_trip_time, + (card_start_trip_date)*24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -1092,7 +1092,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_valid_to_date) * 24 * 60 + card_valid_for_minutes - card_start_trip_neg_minutes, + (card_valid_to_date)*24 * 60 + card_valid_for_minutes - card_start_trip_neg_minutes, &card_start_trip_minutes_s, 2016); //-time furi_string_printf( diff --git a/applications/main/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c index 6897a2d8a4..a65830f4b1 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved_menu.c +++ b/applications/main/subghz/scenes/subghz_scene_saved_menu.c @@ -19,7 +19,7 @@ void subghz_scene_saved_menu_on_enter(void* context) { SubmenuIndexEmulate, subghz_scene_saved_menu_submenu_callback, subghz); - + submenu_add_item( subghz->submenu, "Rename", diff --git a/applications/main/subghz_remote b/applications/main/subghz_remote index fb508ea02e..2ea0fac185 160000 --- a/applications/main/subghz_remote +++ b/applications/main/subghz_remote @@ -1 +1 @@ -Subproject commit fb508ea02ed5515506495daff0409909b7c92b2d +Subproject commit 2ea0fac1857f27fdbcff236ce375c82038e381b7 From 5d28939c287552b1f4aa44b375ab3f75667b6853 Mon Sep 17 00:00:00 2001 From: Methodius Date: Thu, 7 Dec 2023 21:59:27 +0900 Subject: [PATCH 110/111] social_moscow parser verification collisions fix --- .../main/nfc/plugins/supported_cards/social_moscow.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index ca52661b2f..834ade02e8 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -671,7 +671,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { FuriHalRtcDateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( - (card_start_trip_date) * 24 * 60 + card_start_trip_time, + (card_start_trip_date)*24 * 60 + card_start_trip_time, &card_start_trip_minutes_s, 1992); furi_string_printf( @@ -1565,8 +1565,11 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); - const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); - if(key != cfg.keys[cfg.data_sector].a) break; + const uint64_t key_a = + nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key_b = + nfc_util_bytes2num(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); + if((key_a != cfg.keys[cfg.data_sector].a) || (key_b != cfg.keys[cfg.data_sector].b)) break; uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24); uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8); From 0b356c2d6d6d59ae25f968a3bd81eb73ce311f36 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 11 Dec 2023 01:36:54 +0300 Subject: [PATCH 111/111] update changelog --- CHANGELOG.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd99b1838..6e4f190922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,14 @@ ## Warning!!! Please read this before installing!!! -**This release has some unresolved issues:** +**This release has some unresolved issues, if any of those affects your daily usage, stay at 065 release or wait for next releases:**
+**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 (OFW) -- Mifare Classic dict attack fast skip causes glitches/incorrect reading (OFW) +- 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) -- NFC V(Slix), Mifare Classic Emulation issues (unconfirmed) (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 @@ -15,16 +17,23 @@ ## New changes * 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 * 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 @@ -89,8 +98,7 @@ |TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`| #### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis: -Pathfinder [Count Zero cDc], -callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... +ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... and all other great people who supported our project and me (xMasterX), thanks to you all!

P0X%tZA`1z%!;olWF5xr*3uMB=637O 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 z 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∋0Y1%8s`BZ zM4>zpyDD=^%af-#7|Y}>xa`)83Q|G#}sEUH04o5?+8y9vGg~q{W}@Qn<@cD zbZ3Gn(TAfL(PzwO#{d5S42%s`RL<&iX~=C96r!@~%y};IqA8{+b&A}PPxMB@vWq^( zo~ESoLO?KRJ*4UjYX~$^(OPj+ngUAa=P=u6+zF}>&J*SYletEix*tBfW{OwcKc|Cp z)>+Ne-_XhjsiGE{c@~MKOqKquZFWFwmG|87?Sh#s`j*w6|Kr9m7BLjI6P4&$$J4B7 zyt7JV=niIM^yu{R%&V_HSpZUGVd&3DYPJ}h9h!O~?~>@stabSE)W3Aunx^&`lELE% z*lW+EsbzYu3t0kNRjbpALKFWI&ZE-P6P1T9*X<`d)n%v+-boLIS%?DkB2jpI_aKw} zV1DRs15!HEMbIU!jSf^3>wfZ>D>UoOIGfZVZ~V^APKKs!I(MS>GuXPM{uP%>dDca< z8mhFciaSLYSa^qlHc!^}b78cr1;Ysu2t_n z$|N4uXQ*ClZVT7B*L7m>Qr0600#)`Dg+LME-=`{QXGx!#kb~L|B^Ik!uFg3C#;be$DiKI_C4Iu@U|H$8LO7xLK$TP;LxpT;~0!5+O4SkgGEm z)HO7(Nvhm0=mL{v_tO9@74>uD8@pbwB6!%TI<6v}O05EE_;tP;(|6jP_J-4?Cx7z@ zmX4^E8+=({bW`&QqDKvD$%knwjD546RDXO!t{4cuFle#(126+zln9n4=XRL3BhEir zClYL(#^?*cauQ!Vtjr%zL*?qqEgXxGF5F`HZL8$_w3I?6os&vJs|;<@WkbLdtcESV zPk8ebb*oWh>tvL%A8G7r#R#JDyQ_#Tmvao~JQEf1W}! z>0MO;kK*00{Upko<>p-o$=Ca1G5)i^)w5;n1o5v`h19MMBk&$D(8Y!2|KW=<`t}I; z*SzsDd?M9>tk$&3?k@pbDSE>h1-dbmA%Rtj-|y*RvODO7v3#(3Xen`Zl<)%hUCI%~sXxv18WP4(+d&a9hwJ zVzVgjZJkhdx&DWJDN8995LLffN_*d{zsC7J`_L#p60kq#5{KvmLPl)2ct+xWH;Ei@6SARAX(IYhcsuSuy%zaz6v8{FhkOael@#i9?eTqJO8zh_>tIO4B%wm z5|dfH^lxot&z)aEJb`8CCw>`ml#ytx<>&_9ar&%5F6Rtq0J&)oK0s8Hg+Z`7GKByh*92kTY20FL-7rBlGd$3RYa%yxd44I9A~ zcRThwuHMD;uH9tku5llLcNA~~Mj6y}^XrI6Jc%81wUF0{R)`ghA<4LTSqlGMY>a5_ zA4vI4ecKx@^kgvnCnJk-_hj^i$HsFbtc!mCAe(1oZ!H5XrpyxDbs=3vwc51eze^`{ ziQD3=nOXnq@NY-E*tvD=&wfq-K(|JioCQ>*G>oS$)>(~OII0>U#=(F^w{bv`0B5#jjj6iq54Q*A!YpQ()hu{u6*W4mzJjc&3$`WkFOumKRg$=U$x3< zC?m67W&8~hKLxdU7y%6Mpeo`#hNKl)2+aO_l2?%G0ILu*(yLsz#ebl!X! z;70j8gX28xRfQg%+}@&pvpWpDI&Bk;l?7w+OIA^!;Ztif%iIsY`hAfv)XhlBLu4U^ z*LS+koE}yOEn6DIE`AXj-P=Bi-%ed*`(GWcdtceP2Hr~hU&P0C#htLMOm4?B*Rvo? z-MJD^Kjzcasm!)ZM{Tz|%HvC4zrTwAi!cA@=iIOe{rH7JA(n81B)%m7&{o8hl`xik@m9`6iauj4gWNN=ab)^W2XB$R&R_3ci z-mUMleX@4>@q{?W1h2i)b9QyMH+9@SFG|~zYY&O$$jW@z;7odAjYg>7+Gk%>0USjC zHby-Ap|GvwdyGvT?NIXjezF)a+?>f4wE^Mu^M=2owC&nS=XVmf?D|f&?)_!hXlzD@3BL7Y9pCodG%7sAYl8ewE}D=o!Ygh{pHqA zY3y%pox)?whheU3G`e%U;c&vcwC_dF2vWml#N#2VxGsZw@2mZLaO<+Cj4uT&b=S~p z)W$@?VRo?G?@UY26gbKcL#UCje)g*a9}Pt zHpJ3rfiKbr zFN{LQY|r&>wUAOve@}rDt1PW|mDt^lgUs;u+>7^VkY;_^1Yacn(DOy7XXR8```>uN z+xk}92k!AsbK76KaE|MW;Lc&y1~ zR4$n5Y2)Z=X6oi-QOl-3a8qw&8Kf&wEW0&8(`e>n+-{j#7Fm~mQ%_79vvGo_ zuI_Qa8mn=xc36>8|E}>1E+lhM-k}v{z`BpL3_gF9prXz{c?N%Dj(7thwDM(`nKR~H zH#JUXunX6rVyI9T@T@&mssLC(r@!8B9-1#o3?x^u#&j*`Y)hzOcG9{7$0%L%j4eB5 z!p81PJ2pppOluf1XLVsd{6%0r3Xe-g z^AUr5#uKs!7X1qRQr6HP)#%EdKm5Ts(i(^4Rrt4PBchnjiL;BA24XXS`f6{TLaxAH z7Fe#?`BJ%O8v$R}H}IP9qZPiv>>K01v%CQYZIP!>ubMCK->gU;DL`lP*Fr$m)UtG+ z+AAgrX2fe5J@$XIZWj_2V3B)(k1wu=uou#=jEV5@BVoT|_lSwp3Y*=l=EIPw?sX#Bu@|a6;M^)<089yh*`-t ze018Q_bCP6DgCYWJ4nqtJ=Nft%xJ8B<6x=jTFtL;OS$8|q`T!St3sc8uKPD3)>o4z zu9(y1tN2=Aj<@XEI5hcfx9(`PH0@eA8?cwO`-@g`cw~F9O9=Q2e!2gBO)I;tVXR^T zzVhL+ouWrfW9Hi6YZJ6pUY90z?K10yk+2GYnr`|M{#Ju6zJ}I|(R5F;hR0Fx`TsZP zIGiD7Zll=7|H#&nVJBVXfn{(Xmkj-GP5ch%^M7c7su;3!P1-SMQKX+#Wz>AxsVdRW z>Xolsl1-34_kG z{H-T{Dc3Oq;XKq+rWszcvT^eNFAL4E(QHO+NzcB)M>slJuI*VTbKc+Ey?Nm7WsAa# z<1CrR>1Fsx?qX|-7c=LYyeoEMoeD+QuBFIUjI9UgEm_G|bCZ!ufD>lewCD zlGpXRi8S>;;u*}P$d}r@byP?S)Sue_y|qR;PaGzgs_(tDDHmJ~@Qg3DanwHa|EV9ZW+ggqL;!uzi@q8cm_MyjUW{O zFiz1j1dKR*M-Te((otiDxE$`dSAPT;fAXx1&A$u7@E0Ya~O>VXkPk#!gk*ovd6WgUB_eo zqv*bHd(0#$x}&fMd%4)QS_XLdtSMpDJ=D9TnjWdAQ3X$Xx}CbM50j--d2wES@#pq^ z#dt2B>?X|mD*l6s^ZvX+#)Vi(M)H!!s7W8au(Yf6#n}5r`Md@RvG&_@% zKS){Fw+#Mk3J~n4u)gShBZf=mH2W2mTM3AvD>C;lbnJ;M?I_hnoVj_v40^qoheURJ zHe_)ebEQgCQnt#Mp*HDM=?$L?de79jB61&L`V950EfM1*Dc?}F}?7D4w;zE0q#EYr^GiEFT@QVk&2oSm8E#T;wi=wE`Pz#abe@M@=XgD~nbkOl|oh`JQTdN?4tK+zOfJ+94>!ZK~g;ELGSI=(%=QqC9|Msch3$X*zh z*9HfMC|F$R-D$})a$bmX1-07l5#ei01*oZ6sHIUOzwhWs3@bo$9yov~pj<1gL#^lyPWMQo;dJ$L^7=8VSCIRl zs#t?Vg$4Sk`ObqnE;Z3jTLo^y?2Vbd)8kJ7b0p5fV9gj1g6u=L*b2E_biFiH1E-_M z(AfVqPspQLrhfQcQ)F6Y5D2N=N}32qtW~5p==v`&;AUm(Eu3i;49%<_DI=z`jdPeY ztvN?RNkA`M*txhY^0JJnL;(|&o>o(s<5=;{DI_5A+ez?V5Ywy;7MfcSQ&8>g z#W0zOr4+W9#%^CM4WFcI%bS<)(Ljudf&vzAZw6tV?QaQmI0lr+SeC&I<@( z9%*Yd(S^Y|0pg&n5IqI3JPIY=vw^c{F=Au6QgJBK6j-3B=5*v14cwxCX(H)^tNyNx z7r+PtfqBb(m3)mk zvWb|5-H3`YmH{^J8e^Ix5Iml?5xgv)ImQadlWKdAci4476h#i z8odN@Zw`>lfvn7T3)1eK8PK+-h{v;pk)AWC^;7P}R3f?<1#_=n2~o_B)zv~&Q5I8L z1WySbR6UnF&SDl3A~xpAc)CWNy{^sEQc4u4=DR_A2y%w z8bAA->A;XUu6%f~VS_9X9n&f;71{tGjka<=O<5#PJE;uo;>7pV6w0M!s%RYQiTJ_} z8Nfkfpn2HHKTp@d6?-*Z9X<3BYy?&#G>fyt6vQk_dITk1JklD-o2_JLDHO#K@0&=- zDm+svkp&-y89ERZQ`Mz9a<=X8WxdPLtp_2BF&0dVC6Y?2z6XAOuy_8=5lV@i_m-uI zxH~AOj3^;aYevXbWTFvw^gKcA=7rZI8rE*W04?W0kIxi5Xe^)0%nkPAY}`ji(Z5lgeNgni#sJLf{PvS^=gJ90wLiC-x>moQ_jsTY-dXK_v3>d6HDe$wt`v%E)1z!S}0)e)$V)O#n+I@+G)KF)d80bLS;mWZpd6KkU_O|LnaV{Q2Laa@}cIkE9C zeR$aiojfidMkkcTYo@o!k15&eA&^#%fiUI#hoO-r zF$YWu=dP^Pye;eQ57iT^o+`!Y#ex&owJHsyCKf`k*B*lhPvPRNGkNkNJ~f0SPT~dD z)cu(^#tH~MxY`}zuRMqhX?bgSsTD(q zHLP$j`;KFSfCt98jH5?=At1yAJZq#!9^e$kv^c45otTSwi+atPISQQ32_qINz*K+; zX}xlKZdOvch~kEaHSjEN>F_B#rcA{`OoJ();K5mosHbL+Nn0L2!j8f>rb13i`Vd~_ z=n%#bGalGwEnFmmNo`U>{7?NUVKAf+Ai0Q2NB7o<)GKkc&ZNC56z04?f}e+_KYSsL zh9PLv7lGV%R7eOHJftzVUuED(x%vJ^a{&PzFc)eGHIjju6feTmzmHRY=_{D0p zc+yd@B3gVgMT!lW4E2|!x!2;a3q3SV^7*)YUFjLs0pj|48Ti*{|Jb?q?KY&&L~#MI zn#_vCgp2P=n!=cYFsA($iTiGjXf;=AtOO6(Z2IpAXW&&cueQ=?-shSo$&mbo{FCCwU8zuu1lt6@A`~qG zo+5BBxw+8W+s*wVk0ADHs`Sb+EuJw`P>F@Xn{bV2UhtL7q8=~RYrT8(M>@2T{m76c z(~}+sz(TBfaVz#!P6j?XIfl=3xuVIXJGekbK}8Y>Lkz|oDpJU379F$PF&DzKYgQqV zAc0w9#pU1y1SS(NE_h+tOSbLhe`3np`#~LA$rja&G`T=>t9O=(oDhT#-jS~9pGpG{)r#|2{|HPKaaNw z)(dK_gd7RHbcVnR6nQ2?+G))O?l)4kiCOx~TmApOLnWqK&KTPTy$CzK#bvMNIm01o zo%n9*Zhv+-NWtI?*{!w_` zmr$(5<*}jn=__r9kdL`mgADYxLCrm<)}jB#m+CSCMy2?9#U22hqCp5GXxKGG#GHAr zB5UxeOFRRI5+#gpuz^RJwe2fwqvKQru8C-*dpSp7v z#Yd+uKYPU%*B}WKtr&EZQs_iPVPMQ)mScYJxXbd1r_$!LtpY};ngyriok21t zfVn$#8T*!Af~kK-T%V_|1k$mN+`%)axLR3MlFG6Jg9ACLYhLbCe*QSt|KpY zGY5%E7=m%mM-}-pa7UE&BSgD@=fSm-am8EQV6BfSjdrJ5j|LPO1t1POr5 z)mZ3r_)u?z)>=6Iacm43QynBKf7+o#W}#X0o%ivUhTEsMZBS zVi7?C+2{n)dMkrx)2Nm<-Xw4T!% zM=GUEV5bNVsw5Rpg{9m&CG(C_7@O65f5C$+{r8*^=^Avv)7y2f6Fn;`Ni{HWOPs^b z`cV9_f7;e*82V1Ko>-6oa-kVP1-2qB71U!PmhPPl9XxGhk2l42=&u+=*r-$ymJC44 zMV#4cs1W0J=48zNmvj=Pvtm1FB<*~QwWsM8aT=u}ETsUsV{MJfGPT{1lbu>w={sr&t5;^`jMPg7-|Gfp!IZG*w2E>uSA%|~u_Jv>(|F(H3*l?iTw8!ZfKnJ@ z5qC&ZixhWS?Fgn(TxSkSb9YHQKQl8gD2d9{V5!eda}ZFV(u-SU^CSVrkR-Dy;xV1M20RbMoORBHO_JH^o;?`ZPT`iX5hSuBT=o|NJMp&)80&HA7cb&c4MEL=~u{wP2Fx1(jd? z^74;D$VTu~MQcO^>5q4pfObWMdIv#2fAu zC$1XKp~UBy^~S(~A*W$yRcwg3F!CaPL-Y5MC;RrxMjh5u$Tr z!BUzDy&Egu69ao^{;Vw`vZhqeiS8pn@Ef<-IwLd!7IE?G5j7lYt5z zSfsO~mZb=VR05_E-S1H+Zz24OYbe)~THcvjkkfN8*QQG6tKs)70h^BQ*8H9+=nMf4gBaI9(8VMTED%!y zIFGr0LdP6j`E+XfvMD_iPoLs;pXiQ<{%Gv4#GE@xfjm~M?pbzEBFx(A&Ui|XMG=d& z1H~W@CFW~-YE^cK0J`%DP=#b3RsD`i4l=KL3Ix*9J7ju^CSWAfAjz5H-o5TEP3Zz> zXZuitH_bzU^bimO00aOtLjy1XP;X>!lq>-9In@o@{y^)XIV49+iMTD1#HW`60B;LY zy|;kaBSquyEcy3@zv);25xyYV9*ud(zG@Oz`Yq*ptkppsSr}1$Dby%mziHTIPPMsnQtg$XP zJqs}j1|a}o0001H28>1w0Cu5-#!DHy8XCKlf$OoeLfgLJ2HKI>R9#!Jwk?5`q_~N$ zyBfpD*>IOb7MoP5ttR_Pn!(TSH*5NT(564sUmf|>EHN+0%Z|uHU%nyefBAV1z4U+Q z{OYVVKrk>(ylGOyDLV#rMsBr!OXx?Q{PT;@AF}dZv@p64`it|^mq6$~vz14V{{9^K zvz&P9kKf?_1cf~%{NEs%hO~;d;aK(-@p9*P-wklW7X;N zi*fL8aWL_6Fx(h?jN?;c;(243xc|8E=O<@9F6@{C?Qxbv=Oj^5xksK2oo9@$qF|~Z z<|^e&HRYoweJFV5BZe{>ccx;_X-?Ls^mT!%A=={MVy{GK(?=r$gpd2J_hz41|B{xC$;-tz9klA0Uc;a<`6m! zNPbFsNzT^Pdxik_?0%M<)#>Y==}oP*ImN;zXY;AT+?nfL2X{iK)Mih7wJw?O?+-_U zIwU;M`hDEpMY1}KTygTz92h*wGpMBW)O_t~y$c-v`v+E3(_<2{9(Q5GiCGQG3e%lZOntf^C1Di{3;y=GbTaifMI zs?i6$sh3Z}P~zmMbL1nTQzfxTohO4@OKTgty2;S0@FWq-?8Q76?U@HL-;ZZx8z?7q zb|5v@1r%e!Dt2-=G^7N9RgYR&cF8l%jpO8c;~tJS2W0ysC!N8%^W(F#Mp?nNLJJc~ zYwJ>5OmlZQ-0WPAeD_sQ>9$4Uy6$A|bJK%cS!Up?^@BZ@l(Zh{dc&OK+F)c~U*pnw z-x#@AnAlj|HB6g4C$fU4*gENTBJ*aTxDBnWiY(xprD&l&`3hLfcTTu~w5XdN+t}Hi zK&r^uVQmTFr5~wY`)VV6tVVz#?4RcoZ6?I zkGb;9ywQrxJkSbEJB02XFtf+tY1*`nOKw2hT?SFy&My;RO-bRR_JQ3T_aEapN=B<> zo(2gJt&vJo)I3#ER{vQErorFC#lXaZ*DMO$tO+ei9SGl6YFOQo>`<#U>$8Q4qSwm< zx$A08n9eG0)(yN!7oc`ZOfUCEP=rE_#o&?sK@&?}PH(z5vK|ppO~_#U+6r4c2XGJ> z?8A;8YzE1(^4H&>y^RQqC$Y5sFpb?VwX8l6g*5E}5tsccYsMJ56nsQVnWNrJ$5@fBR3IHY-d^VNKL$ zl@$n3YY@g&-0!q!WcX_hC6B?dL~VCOKe!{ZHiZd#!Cj_r2|0dNJ8ie$gE@Cc10If4 zN0E%K8@|?$(&MoAIoUl*ZqA3bKH9c_VjXg5y#%G)gJ>@&X^&txv5a`rhFfyKPdCu8Hxhdl<+*7aLW7cbLca$3*1?>}g@Ni3wNym+3 zQ>9jYzfo7(xIPW9HCn2P>A=Mgo{KVCk`Yy}9WG3BnrM^4ZacN}m%_iEuC$#!im$lu zuQu}i!Bk^Z4UIPCsL0cE+?*FCJ`?wNb$~FgKF&60H$6D!CAz z>7tv!``MJosHNk)cnJZtO+n|L_6wDqng)9$=T*y=uY-LoY-pyz#_r{X@Mdb$q8OJ@ zR*z3T2&BYGdMNY*IhnHVoaAA1U|e~bum3wAGc|w}@CnGy8mm+hO=vOZEKhra4a83S z>3gZzIX}COYza`<%o6+qd#54~Ce(Gf_O8<88F+;|=wEfw>Acv%p`VXKmyE1iZi2Jz z4eG!<&h5_RLD;nm*FR{#9J z9o($Dnpd*;*F0Hn`UGW{f#MPA(C^w^Gj(^fI*A+neeX4(q%n&<=dikcsy;8z|LDmS z>>asEIbeq%lA$$y_E3^q-E9a=Zf6y{@eXC1o7lYjKw2P9}+Yeb^N6T;2j-#wAB2WyfVMQeEC zIna@Ze#y%7=h*8$jaK2D2*De5$7#!rm!#U5lip)(VAi;9|7Nc1T8+2)D(U5T>*{1())8IIeD%W1mYKr~_p0kl&Ttu`zj+D{^~k!s4=)ka~QXwAiq^#1fL7u>^*`%VI1K{z2_SD*m16l7{HLQq@qE2zS}(e z>qa)%)vNHlBwlB0_q6a-zk95oY}q0DNT~jX$)aZd`0&h_HqCLe%kURdSXHf2TUBo? zldUK#pY0y*W;nR+XC=DXEN32sL-k^XSXaoY7-mm)*73!%{ENs_DE9ppCUZ%__Be~Z zhkRg+Hqx`@8U!3>?vM$Z?^(p@Ma<02%PE}3+^wBmXkG%pcEbyLzgU~cQcl3aACzLS z%EiGM8tQh9?=AXQx%(%-L&IThFF01M%wh(5c!hvxE%hCCj-yaq7y$0%f2_lLxb_A(NT=vVz2ZHvAl;uQ!H!$d z`Iw^o+}pgq{8gTzcZn`e*>wQ7(!x7WR8gJYFDNi^>8}5_Z z#aq1CA1qwRGsXMf$$xZw6hhP6jY@qF49{zMhq9jbGx&96q83g~%xDM~OuIW_Z z^FWWJ(94#VNfO?ix+=3hDH^4+ia>NyD(oru?zTHpTCIqRQ`3ucTt#o%$Gh7rlkGZ> z(oWs>ceL5Fsq@2&P0u*JbuH^CDd2*!z*_cpR%;gcDQ2gv-FpYOxS|VCM^ELv&ee+e z1m=s!?pCI!&>A()+HRJa{UXq@b~`NLnG}rQQ;6!v|Frk+a-*@(D5KZikF5n5k8f6m z3v<*L@z(%qK=#$Dv_ET^Jdxv+4Xr4?yAH){v_q}dIn%i>uLH!|a6G!~QjvkS*JGd7 zJnhSi7GkB{mS9EE3V0I@&1GTUtSjRa(Ib5cf6iR!8=nYhYAL&HZ?Y_;&#pk!W23m8QJwVR2eV4zbb=6F|n9-`@91ufp z`?lMz4f6ajYnRqeyJqbetXJBMH$-bue@NnU^aXo^o0*xJmn(7Ma}TGW&E1cPm`v_# z{qF;Ud74+7MZq7syNq{=gH21S?%(D!0H|eX+57t$#h2hX3fN9n-3C`|*Qv%Yqc-_#}_PXN2bUz5C3O!n}@vMw&+%ldmFOa^e zhahpi>G!!qO8{aCr#=m!|w6j-Lv}qj{<)(8o1-|wDX$Vz9UKJ z_9@+)%!SUNcAPZfQ$$ODGoHeu_wBZ6vuGPWbz9p2x6+cX7jS=P0!knCZZEpt5w_EP zrl(rS@eR9Qb^38&zco%8f_v1I;|r&!nx|__YTZr~z$tYRI5`lW+3~h0Je!;6j0%rP zxH>D>b+kJ7f&!lEeZ5WFE?r`_n>qk4!|sVxp+1rW<$7WG7yX#1;H8gjxBgK#U;dN) z8wF*|qvhv2!$76;pk=?)z6KK~zoXi0yZ42kKO{FV({3;Odim!3Ir){kG*c5JcQ>K@ zy{*p9Ouw(yrQNA#x7W)h-|50#<%|HSyvCbQU;i}^<7ctea;)i@McdW>ulvqdQj3=9 z<(E93!|ho))j^Zir0{4$2ANX*x#y19{#`kBudko*=E*L>?3>)Ye#yM2n_JZsKi|BX zg;e-Y`DTuGUA4e0wdnt~_q=9PA1)SGhiA)_=6QYmEZ@?z)G~Py|MdEN*7U)SYTb_c z6w7|T>++j@cZUruY*ksiJaxSh`ktB@8Bm`xa-qI9WS>7D`@(K+;)j3NJ^|9!UB=MUpuxmrcDwS@$Nu-L#aCw`QsIbo|_7WuM4t2B*_TF5qkjYmfVUdfxoW-KOKhP0dYkG#UI5?Hmbh9^tp#gbBJ7JjeO@L(KW*GN6pEv!=|TZj4W$1jTyadvpX#dZ`J9_ zX({}5wY%lS3#X^*`L%B*!nUPXPB2$@C8cnh^xoJ=LE*#n6vT@bBl5Z~X02_Ky|r3q zQ_kS~4aX>^aqr57P3Y(J4!L(pw=LWWf$*swT}XEM=4A6;^HXH*{x+3w_LNd9W#d!h z+@0k1h`N{}Pi7b7qsb<6m&RuP3cS1BVC`i-iaqyU__|1wc1E}}AeJ?eyg^CP;Pu~@ z>2$U?;|7lTG}d1Dgfc_6W|G%RE3TxdCT=Vbx2|BrcHVu_&3d^@PfN{zjaF3K+D*@7=)c7J0B>4oZEz*rp=e$AEfBc*s1Xl$ z;wDnx76Qna*KI>@|8FS6l7t1Kn0{q5d?Jf6qDIo#TaX+)t88KBB5n^iQ8F zmSu;ZKr+Brr-Ho{|5lZS9ZH#(WFm5hFILHW z_Ot%qah2_6#FHbK?i|&_DpA8KUHbR?-^+u&^QErmlN@bd^t`!?%}ej(=0JUA`y%<9 zrXksi@Q4+WXIgFFJGK2AK-C4Zg-eE-%$on(#&Q7Z%HKm|m2Y+iS*qvg?Ye=6TzKP| zY}>zr?%+MVmt0GGE`Y2>7Gbi@VV=t#-R|`-0B_W~9{MS@!(KS&FjICy-%}+G3$Ml| zKh4Zo@irXv3W>eP*5*Iv+nWI`bA>nN84PDFeHQ2H-vV-sVsV+k-_tnv1G8|XU?z|J z&76vV@n+)ZcH|bHSI&FO%=GCpa@RHcfL9jR6bse!|KENhK?`bLe&WsKn2V5Kv)%;$ z)OI@DBUyH5)hCO=eZ@iY!d5t!`^2L>^EZXz<@sT#p2NrTsQ7mDi3bQOZhai_Y}jBK z+o(-W8Dw+|FuKlBLk?6PMlC)*_CXHH%peR##YuC~ujGRlCP@VZFq*m+P597qj4 zuoS?-hEk5LUJ6o>Yo_=p3ulnT*h0dm`>_Kv(NG_z+YnD;fsH&RMV>}cNDh!I=A;uuC!KD;@uYsg8sA-}g5?5$3eQ_x;s06X> zCD3^>vN0yma)aiHprm?M%gTFmMXjW3$^(+t>Al6J35APL4X>KACxnMLU~syW>_Igk zZ#gz}?Lag_ZF9^*E=J^qhns4ILE@30)C1oJ491W&0*IUtBvMkoYgCiD@?}*W23l!S zz-w|2g=A|Uw9=4LJ1&#YE|q(YeW>!GFfRoIP!ToEaaDvo5EKBZ5SoD=dK(wR&BwyS zpaTi5(B|bzVo(n-7>w~1oN*y*_Agfc^kn8>VDjPU@QOG?A`eEwCDt^KIEpDo))OsK zpMsz&eE5*3s>eYjf&!!i5gI5k_A2FPp`{p1Z$3+N4L#~I&@JE@Q?({}iA>exIa5jS z%m^BIHP(_v`Ed)bqA|UbFcDk@GemD79S_hx{bx6HHRFs+LLNb6EmRw1EmbvFT@R&E zKzad+@OYn6+)WvgaljY(SQQEp7N85z)jLuer);u#oWv5P!eR=jrx%SZuGIt2kxmRU zcL$fd7xxgY!Qe`ocW?;!8!IdwHs=j!p5%zTKJVke!RI>Cr)X2p);pp*B<%qWZ%DXq>582-UgnjCy43XqIc z)}yLNYP_@kte0oy&0tUn)Kn^kL{ux0f?3 zz^DeW^rlV{!CXub)nHNFeY&E_K%Hw^)WjmeJH1l&6iL%g02Ni{h)iM*Z%GLyK4T)p zRPV?df|U0`Kx8Jhy6|v3G91kLf0Vm;81S41Q$SGiI3ZAg3ouDJeQQXbUHk906G~ws z3^8Xn?5r7@)HXdgA1tPn6w7~)DV31lU|%ifCR1>vxDuLmWPmRQH4= z8H79~>=+EZ*P`A4dI&z;NhT3U5u8c{3#pxnV?&K4UFaou*ZUHVmcm(s%$CVc_QQm4k?|OIeX9++pgJl>*lTGFDM8ywt(v{ z!rn@ur(+eC6fr*0p*L#O_cRy+twEFP&ug7!sy$U{rva8J1MWI%Xj0ZiXy~t z3tGU6mZSs3ZPsjAa_ zF-?w^K{HS#8bYX}oD~JEf!qdcYcGjea=;j)PgX-Aw?*j{QIWNDO4=%%TD3>9V<9e$ zTBpKE>dDZSH1Bipr<7p*)wcYVUxA7G#R(qpl!ZERs0oE4Irt(qxDmn5wJ1|mD zS}v&1^4?N!3dPZBaxl)lWiRA)y>j=D$|PaMVDcnMm4$kUm|W{7jHqxRsIgmYN~MTG zrcKa7bwe#c^&Bu7#zZWF2D1en5{?v^1vqKVG`)bM`VtsgQnBWsN^XHC#bCgQ*AczI zk_ha}Jh3W=K&dLYc5VSD#UF3HwmIx309+~am=Q&gGJ3kYSPLHVbjoNkDulP@z*VpY=sU0Mx8j62{K$4S_GmxMgxQhn{H^s?yQ6H<^3u4Jw^8gs;J<}yc(1I zx62#N!C>a=a3=Q~`+vAzu)X{5V0ZaMpAy8>)>4wKXO%9A4#di`#r<<523m|GNa-mb zWNp+1B9hE^kPUoyZM4AoZa**7nK<~f`AR@I7J*DdJpJ9SBahsZVnp38GQpiYApuL>^1`Y{ER{%86y$VS+f=c?#$UR}PfArB$OKu#1S5 zm~EQy&tt|7aK(T`iDUtBE~uXJK*>Rx4P{wJsP`rywAhtDWTVLHaQVmbjr zRTgDxQbsWy;8HC5cpFo#*GaM-1W+do9CVtpV5(~s74sPHIYq-#{BwPwo8}^jmWf0( zT<1uEtl0#JNg<0qF@MFN&ZNM|l*lN6eCBCds(MEvf>417ovqO9$DUmq-*ys6#kEkF zUsO1B0u^{u)#NeN>lkOe6s_VDX|j1$@x&705^{Dgf0x`(}nmIhfBS-3CSC zG@+JL(L%jQqL@@9ga^z~=q$#T*~j-og*DzKEJflOKfnCZoLj9Xpu6@>N`nRP=>gRz zQpy>)j$f*ql^&DSI_YHAmCD0kmg$asrB`$8AvuzVeuEvV{-1sDBHwB$2c`(15kfJy zOu{UAES{f=E<8F}jN`A?<43D{L=bt)=~*J3dH)AjI+j#d91fC8}t4t59_z z-dCeZvFWc}BdAtjq#D&hNug6l7KSJ$^*N?6Q59JTbH~S)(7I{= zalFfn-^L>?<1C)cW_{)`KTp4^w-D3ZNg3&KDM;3f4{?j2Q>gc!rMXZ~{n^uE_VXn@ z^N0-lbMp;0ayck7yJ;J=T5Cpj;&8EzSZ7UGG-Hqep9T%JJ^^c86K1j<|0Zqk(6++!>cFnlfVRn zjOZo1ToK}Dg`p$(R1LZ*fezE0z5hVw?V|XyhYY(Corg--eDEL}st5rzrm@wls9fd# zR(n3|{}hNEu1jK?2t2hj@}23bdPXM@_Io_Hf;WwuU4knnicul?-n0|?bOLIKO_s1Z zhefu-)Pl#_wH5SW9u#U*c@SYvR7j|D>~xyg)JBrAfUvsW?~_R25Q8YiOg}0@6$T#* zEhbxa9nZCF{M&-=lB@{diZl+W*!2tP8!%boq+}STTZFv!1+IJ^7^U16o zI~5Ti%Av>&OBoYSq0tB5C^+=GFO~_cv6f0a94T6;kfm5mAwVG^9iJ7SK3SN!xI12# z;%b?{;+%FU=0wLBlq@6;OBMl5#f5fJPNhH0)9)*GBFJ>`=|Nyj6Y1F^uwvR<0i0W^4W?wKCUQp(puQ>jt$m(uHbBe> z@G+ScfrCY=6KgEivgE^ORd!{dnbNv+rtCkNH+L$T?~s_VjvL`l8Vt$pw5|Ex^Vr<-4Grq z5-1{|gPdoq1;f#te1+;Dr?RV#R+Fq;7YqGjLP2(K0MmS_DhI2ok|WVII->uTyXa$C zRmdPN>QC=g?5ZxpyG>>YQP92W zCnfRhELei@w!CcL-?OD7*zcSD&r;3y#=$MC*9^TlkBhPF3@5PxYGf(J%eB{UdW;@}1Rs5zve!D3s?$_!`y+|2EBCTKn5 z;_j=Z4T5ykVL6C&=C`06=SXD-vY|E1BcI=K_kP{}nIR8Dd1IDv#nT2RQi^b+)KH5e zi6^SqQ2!WzTlc*UUl2E~+y-Xefe`%(Ac(F{vVBaZT#l64Rp|L+{|#RkPn3`kMS3~P zWa+aJb1a*(mN2-_4v%{}bOJs8$~S>6eNeR|QkDaqc5E^&kP?emPfZLe zv>oCj1{s;%u-5cUZY9o5@p`30V&7<1|w=GoMMxiD!9K2dtPtJy%pY)2p`=E zQJ5l-n#Hju2_`LxA-|b-KcA(+XgbAECKBm;+LZu|fm=xORRDzO#>Y zWgUNzIXhNQFm`d?cOk0GWBvVKb zRsfXB6ayQxnUnb0eti@q0&?ds0UnZDNBO&I)Ikw}YQ<7g)Lau=)AlR}m9iN$jl`7l zz4(AxJJ}Kf2}kz89=F3e>vi+DdrI*t!D5spXY*u;rO#(iOL7{jqUckMe-do&?UT4i zEl^ByE=r0UVx0z`L62%4xTY@@^qj=El3X}OVXlc*OI7uIXZIZW+pZGF1v)ST5y-t@ zlp*<%YtrI97b!`p8vf5w)Wva6Ob5hq0LU~DYCK6s7|$WI@L;gSg2DS`kz*0VQIYIg z0Y>zfi)Lg^4qm)wS3GG)O1v_P2+crYOc)_dMLu{9O75QAG}*W>?=RTIcjc`rxiRWV zvVoLTU6S{qjmwJ=5CZ^o05dcLHvmv>RsE<9;PnKP2HW;P%OK1nM`mkHYm&T)A^^ZG z{^h)8O5?P~G2Dv%_bC4Z5TX$yA^-q@0#sz!tv948A|Jcc0d>LyzCf+~I`$_F_&~6H zx>5#YqF<{ZnA;Vr64?I#jFz(7O%{r(ZLo1pM-{l(rl&~;VuGE2ILv@xJ3Gr}X_Xzb zM51zcVVG%hKf>h)*WV_ug^r>IfWC84QQ>Z{M{{s;b{qjc&0esS%_)Mf`njy2gbDf0j+ zG)Q`rVyX3=6Cdm0SYG|R^T=Fp?bI{%${#wP+drt!gMa0hBbkq1_K(W8-J)jRwp~3UvnR?FOhklPzu5cDgNfz2Ng-b!6F|W-Wx@Kqt z)4o*b%~8G_Pp_S)p`ZRa_)o2A)_E?=_SAa`Qc>GEvmpfly#H^%{#a<8A)4-%42&R^y1!f;GfdyN)&?14=b;+4)BwIoZpxhq9?puDRCCt^H{y z#s~n-HfxO9AiD!rUB{6fhtsZ(=Z2(}xA*?jq7D>f8wkWgSy?eAv~Z0w?zNFFKlerZ zy0{nR>05YjJKBY7`!-we%r48)OHKE4^tAIcy}Ft@Qlp)@PX1LVj%+@M{N>V_?-vLC zx}k%yj5lkA{Q7Q9!W8!csj0bzpnGN4F>|%Ktu>jc%8l~obn3D(ys=*1FK68=|LA)< z_6kE-{brV&45r(*y`zXSjKxj744KWH^0JkG=39Q_`6RQo+?|{SwwZLHWJ1WPwtU9j zTkiF06&&23o{lhfFaOl5pU`JDDUsUfBn950SqILlCKgz0o~V7zrjMA%7+PHY^89G& zK4uaf*X91R`HVuj+vUW#HMG62%u3-T1iGoPH>K>1lXLvi=KnL;n1AL|Ail$^h5@7M zeo3>wVa7>ktyz%Z?3C2sXSd(%^BHDcwQVuv?zR@m6Kl^JZ=1dyeiq>^r+V*VpET;! zm*=Z9jM8EAwI#F3DoTTt>q^~nl{sXeLd!nhcE-yv4J}=QH9NPT%rLXFHAdTF2^34u z>sFFL6{n~F9)ro|N8X4ZG&AK_-?EOEAyvB7;BZv zeZ8rAfNzm!la+kIpFof4w$*A+jkKTKoz8*I0)Jixb#(n@=}2te_My6cYc{er#!3nn zIkpDghYZ%&LFR7VJ^ejy>OFRb(SM$)@?XMtkm%zs`RtWwjVz)Et|&%9#k9?!ByfCr zoXq!*ty4j9CdoP8u37fV+3MbM>js*3zx9jmHwt<6Rl^+5@42UA2k07mdXVd&Z2zcz ztZWnIeW_V==k}NNe_W|c%@WJ>-nvr#n13i3_PVo`y!BNktKHmN_VimsUE@c+0&tgf z{zZa^KOv^D(wpzuP#?y(?GL~7Ci*+V5!@3)MSWVGnYqVvfj>@5aGRUKIzQ?5rlhwL z&QL=zXlQd1+DX}9n@x8->2{~Wx==`6V_jowouBQYcWz=wIF9bh%u4Q!77B_oPOrZW z%}TwOw86l>>oL%CTkTpiBP#U+b~buY3chcj;c8lmZ=Gu)cVG@v-imF-G|J>Q;KqGN z-A!o@e*T{B#`5p{C;Y7m58T?l=bE=I8;4yz5`YiZ7Ft=!?BZ)TTD;%gyRPf&Q?cQ} z*(S{Gmvne448s(0)?NAAE+tuzyg19)?bEJ79dDAX!j_Ev_UG6Ax806}?Z|fDexpVm zkN6869g{zNBL`*J_6wfy)8F5^NBj5NRkC|iMe=m^_~yrdawGVM(&{V`2;LrGSC`A# z%e^J%rTeVMtli0WmfgQoCrQ!;@MhFc0i%{v8_}s&n<98kGN}W$U$VcCOCqIW!9}IA z)fVKEUUi_@t$FHUHpcZ0e!Rqf_mrI2J2+qF_HEk(F{LVVOwna<5arj`!P&dT*4XBr zPiMOy9_y2eXIBMgRlCu26U-Wxk5oqUh&{u`J772SX8qo?PxmA1&rB&#FJs4ssd3Wj z+rMOg{4yPZ_Wy%xv>=MtwnN&_Gvsl4{=HwVAG1+}{njalx&~5y|2{C%N(WxASt_Q8i(0lOq((oK9;xD0BKUUUrerkWL zjbmMM04~(1Z1ormY3X>Sr+O$}*%#J3g_wRl;1MYMYu2GfH^Kr--}YE=kiB+rg~-Z! zbq;QDyir6IwHkoGvIubUQ+9TZ4M>viD{Tk9H(K^{e4As($K4&TgQ9jt?smN5-YdW; z?Gjt;+Eu`H`FN?ES@MIrRJn_iO5h{#H2V%0Rbe_UNBB%{(s~WJLcik^H>+l;OrH?8 zr>flbtnG5EXC?X?#NA}`1&8CFF@-j^{nhzj_~n$}oyfDw^*z_wd)7O1!Lj7K2)AM1 z*-ER{O@iVY9hGH`;-0}Md}Y4ZSV^_a6tck8Otf3;F3tIrxG!6Um+<6W7am(ctUPbi z@FGS5uWj%$g1cPITn%mcqTn^%Jh*~MCY>8N@2%W&bNjuisd|80E4J`ejN{{}f6s4D zFm)G}8!x)=g`-#x1FMKOW>y66HVkBv_v{`1%x~S*SwQV@?`D%QrPdZFZ4X{bMJsLA zSZ@ApIoTy}9NvX)9X=i|jbF;wR_$TuagX){(7z5X5CkSu{yv&&?_ld#I`7#V9%aZC z=b5tL`+~W9(xi%n&eq{8d|gK=?za@syQEUcQ1;X!n}}H4cjr3{LBahhsd~_L>MO8g zYi5}R-wNLtn1Ee=HcUw0WivqMr*XAyNsIv6t4_05{DE3iz_yr@60_1O@GPALsDWXu?%Ab_1P;)+c+d_-x`Ll!6w9E;+c+ zx?nH91BG`3s~#>*3i9C+G=_p>gD?x7*P~6M`@ApUI&)B@+wg3&S!hNUfCD4KG+kie zt!A|}l-R9gmz4Wt!j&-h=-#d#ou&B)g{BYlcpv>LBnJ8mwr1({m}B>o&6(ry=PynQLou%s$O|f zggV5pc6K;(umQ?i=y;wULUq34+RtTk84Or+a{H$E21})4Y|vc#1}wMQGMuAyay&c! zf&S`L_Vk$$T@q>iaJX5l(6fN@vnE3cS5mMr&u+ts<4|3C^f`>$IVpivub>-e8=E@W zORzj!5AnD*or!vDiymY2O8a$KQf1j$oc;UI+ZaiPZ3SDW#i4#LnW|{xKpGbAxUV&} zcdlvl`DdnYD}SD~)n|ssupdLn+FS($NsfH z*b`Q#9O2zlz9PjdxRF#rv<~w|ak0OG&$0f7^^IA2-MIoU!vMe&K6bK)WPfkL`h58Sq4s$nv7^6|uV`O6UWu^Um*|JpKwIJ}Fg#_*U` z{-#Ruy2F8o^XCUG%*&sgd>tfOh71ZAtyV0It^L2{cQx{e??jn>Q;n6Ow97jWmmkjV zmlCBLfmzxFWJB>Ua zJa7rGopROxL$jDncO6;9H?rCqHro+M!lD%Ufo;LrG+j>KoB>$|X8-oCgTKb6FLXHbJ(zKx-g zGT@&16J_p#hu`#eKHgoWF+HYa%R4|NK-@!t=73~nu=T!4Gk3tB+iX+~P-{}{LBoEy z(Wc`i`E{$nDKkg;6PDDxO-UgI8e2>^b)HosH@6JUQ*Vyu-7}q3f_*#d2Wp|FZhzHi8-qH0X4MPE7`YZ=z1^D;w2f?hp zxw?kg&R|1hGOcEh)?)6u{UyncW2b*9?@%`=qmwiJ1BC`<6!1}1zFpW9Olq1zh9NK< ztGN=Z!z;&sYuOY_ZJ=)MQ(-_bdzZZYjZ$fXh9$I5p7n^%WiJlhyRy-qKVI*4X;#aw zyOP%6Y$<1H+0CtL1gbXB;FGE(%($aPiI%mDPt-+<;2Ku_p;)ZSeJ>64)1LoyM7)SW z@!ZO2%y>-y>>)V}#v9+FF7D_lEh_7>(Vp!gT^TwPG;dhh6tD3CQ{DlMxU^Qxy2YdK zba%HttolKuf8Ljc{9+hxrPB5gk54-%V_s_|j^ zlpXO+jJfgl<4`<#^%E}C{^eyWHC?e|wd1b?-#f*{MHin|wW|YNgL7YB?LxHbCciaa zX@BcF+LKNDv~5YljM#zZz_-F~+J)L~8m~4$u%y|NjPPM+m@#*Pxowko&||nZ>?d7l zHq5C6kYW>G(+Pb%|HZpa&XzCLMY#=h1I|}=#w$v3HV{etO^1mkJ7(0sQ`|l78#gGy zTpRlu7dl^TK}y8@lN#?NXuN%=#5WDQb!VV$@NKjkYxldeP6%Wf7&czG84DcZ?Ux(5 z;py{U8>j|cbnRr<)^>KAG7I7E@E5mDQwrhbdsvS&)^!tH)b`>oX)B0n5A1Uv#=XR7 zTowv;IGE1f>o$I|b^|W-aMWBCk|(tNjQ<{P@z4rvu4P-B<6RwcYXe_{LvfuAl$MUf zcs#sqj;jA4)Zx|MYVXpiu3Nap+znUo%!(f0PuXL_DeEbPv)8)~-F^=|vjZphSA#fo zE4*0~Q|$ZwNam`VACoiaFA^ZhIH*g5~$jXrIHD;Em zrS%hesaqqp9twRDo1cvpGhH9rJp`!R48^63%keU7cA3w}{mJA_hu20;8f^mJHqxjH zn;lKcyl$IzANk?u(r(x}9o|r2k^eUd-;Fqp{`6orSe{!TOB-X$5{FLR9M>THuvfQT zT$xid&x&bl6_BxdsoPSqen;L*cB1X~)9}zPusfFqTZJQ!BKMzmK#$2=6kD)a&elJM z;VP_%q_-R@#Yx?6fLce~Uuh}7YvGQ6ldQ(IKUdG2JoA?CUvFX&Xg6_4wsoZg^oD4x zxWSa}{B4N!utRI-tn9-&<@%mH>0UTu=!v@NH$^&sth{*n+6}z!{AcTFZ|toF_5(Zq z)Bf=zm`*Z&Ql+pCKj zSV~G~R7NsPE*gk~G#LcRlmsmTI4EiATct4wY6inu27&LBhh9##*k8ur$11>{o~HS ztQ4k+yNERI7bmGOdvVGXkuG%SPA>DJlBFR+9|TKE!C=?0&QRilE0YCwxCT78SQIq$ zxR7BAE>=P&a}0AuL{c3UyUU1H;JM%n4tE_o;ffn0ge$R`LW*L|G(u$l()lEIJ@$^bhjxZA!|<*%v%;?}0*#UjW>NQNmGaFo-ZGPJ9*w_)UlMF$e3=mzmud?hr+o8l;#P=ftbn`XeYQLsmW}C;Bv|6sA2lwWrD!}EkMr#RJbX0 z-n|R~Nh4fw$CU6vUNSbZZk3*$!iQ8^L@NXhjSO8MI2K2Rgh3sF`Kp7>I@+TnM)PeW6s@U+w7GzJ= zu4CO6=FBD2c7B;ps`F!HV`c_%89jr+fE2eBD&7{-l+{WUq*Fxay<~Yif1a8?%CXYN zNWxxOf>=RC^uZtf{G)RKcC?X)jDLQA9eT%@^Zs%90?-M5?Jdz+s22jwUV=n_XQX)qkC&EazCXBmy-#i5o6jWgHc@YmIs_)K+E+UV@T(}oV`snzGUt%u*h*~`s#wKpRi@2? z0AE>R6n|BvWjdRmHJl|n^wK>vHX3yRt~H(mDNI!X4-wpPPKx0G>KD~37wVxa^Otq^ z8o43Ek;kAQVG@1TY9fJJrLi65A4`vi){)&E`3>wboM{4csN%TB0kq}LPf6fwo?$q~ z9xX;ebagF`x}I9Ty_a0gDLl5F~k1%PDkQmBVA| zFZ$`tZ&fYhFi`~l4s+)>lYefobB*_O^VS5&wdAX{0vAb&nuNUcQp~(&5}&A_f&8@F zK=<%49*}rUroE0^H?g^`tPJFe@|cG*x{sPs>E?v*BvfEY3^HR|dKn_x!FVw7hlO#I zS`NSCo5~DTX%Lz?aOT058j?(*mYCpy2{MOiqA!D1m)v<){X6eJV*i&HyevZD!eCP> zunG|FjAJ3!N;(}ddaQ6DXL&PYV}FibcaTB+X_}lYCjneW3|mqtkyu`XGEs+) zw}eo4s(fft_uh)E5(3;&6SjlmAw577r(mn;^3BD`_n9;6=H$#I6?+*5kZcddU~GZ# z_nbr{&OH5#%?Vk15~QO+!6o|<4wN*QQt;(Yh6p>LD-r#9jI1}&_3hK@4q_k>feVQO+vL7T1p6wUL5h)U z`BdaA{6L=gQYhwt(rTr{_+1oT>Af~-Bn1FmmTw3yDDv8PdE6~FH5Zhr@qM8ihgc1^A3JK5&H=LaMiLG)q;(KGnP6-BsTrIR9BEaAz0uW$4=&13l zcB7PhZ*uF9m>7~CVRdzoMi3{(2_$kxfeUA2htiTqB1KE5v0s#Y3a471QeYruNEpbZ z@_Jyb>zR>X*2#A{Tq6G3J%)~6<%jSw{Vq9LK*o$I!d$HuV`ixo&ImsZfb~mf7t48x zbSp5_xcn9q$#_Z%A7OJ=Wg@aCj16z>&CRp)i)XZ-mnC8Tl|%!|IY+FC(UO-0IdFt{ z;usKuxoRwKmGhICL?64Or-@iP4oDov0Pm8p;98JVBB=#u(V(JUUj6Wx8A9s*bq#GQ z)C`9A?2pd@yUp;25uMo`tZ|Eo1Rn*H;!WeN)fs88a3Ys$0gC>)Ms1lZmyf^R(exjf zm-q0W7(Sd`xcSV>W$So*_Q>At_v>UR88o&aok>3T8*YiXYOMxi?yTKwV{y)m&wXiA zfA6-Z+Jd>TTALS+Z5D64| zBF0@3h=>sGg+wMZ+`*ZazBUbCPG@-D=X zV`Au}Dq7;mZXJRl&9RwS0xSbp$)B#z+IMstN~(OMrYk!~kD0Lb=5v44XVy3WavMbc zmFc#{1Fgid#6ldBfv8~BOjEE9Jc|UZThD4Nd6jH^U5?a4yz}6=Iui;$*pQ$#xuaQ) z7u8@G6ivV-lXq?OM5C0IK%al(grylk1=;`5+$a**VKYmg-4yF-NObyUF+(@hx{{vlXg^W#3|PE4I?79U$p zQDh1u1`oJ^a>&Psy|7{r-!v!o0w(9%;YXw~iPr2Kk!0=?YKPCIA?F6VRqTwT6GEQhrT0#a%Ks#VX$-N#1FCXXqB zLXcv>I$)VxL`I7bq}((lb8Y{!AIh6UnT_YndF6$>imx%4EJ0e&#^6J$hyn!)Jc&qx zl%vBO&fEik;DR0HS7-xpSUa@lS>}+8peGjI2TZ9aU6!I8T#-o{#}1a6y>%#j0)Li_a%Nu>O<} zjbCp+ad4sw!sB@|QZ}Y?2Z+AHPD1KP;xppR$ItWce2blm;l(-m>il$gW0HCHgjU9@ zBy3AsF>rZg?!3%-*XsQ-WOE-D+99y+e)T*oa;49iWa#*=B)$@m)z&gH8CW!mDwP`? zg%VIvB%h+76QPLIq&2DaS;n}eT$H1^3>1BQO2lu}D#`l=Bgqf%euBxh8Afy$a%n7nt@oc?Ip?vc}(WsZ?TypE_#+>?dm_zi}zBH;zMw#4bV(; zddRi7nl5M)k8ovvZa-0xiN^{2JCeQvk<+8lXd!!Yrv0>c+TD_R*-T6=op>OHK&~ZG z?KE=@lpr_iKlhEErlkcEEmhZxDH2#~3zin06^arU8#O4*J^Jh{StfGyLJRuEcp>&W z6orR~#e(Gr&1j?##kW7)>9hodi^uN3(qm!?)f9BB<=Mtma^@{Y^tA8plZlu#D#s&H zs;O2(v84AB0m>|>H-DDF!x|mw2+u_*d1;0uau5^^xK)`NrxBL5lUK$T(eyk~4?!1m z7a%c8W>Sy>ML+&!O1n<&S%g z)3b}y28yn>GBPA!deDhxIR;2V-6?d_aR*3_(~EDZ@-OHyC!iXaC{atMxL_T; zRwf#OmBK0jLTs3YG!9Ha_2l)P4B(Z4qTTe6QVUrU=H4zj>L>9*`miUj#+N^!pv9o1 zA|#Szf&0v(g9g?fU?65}giZilrX;An4?Q`7R6HOBtjL;H8KgNe&F0x^BV4da|DD)58rvd$ zrX(Dy9Z-2*z}B3SCKE}NBGBNWB{>F}esl^ybJ6P1Ugg`7SQri#B_}1U$IqAdSr${S zOF7+_w5RcjH3KFsBOoLvdds~SycadOIF`eO?p`{X z=|oUtz=MCp2mY|u6PWZ&@NJFTF+EW*^d1!w{=?jgrLX8qehb4Z^l15EiS2e zlWNo|Vv(Z^4MwFfrS)nT*j>IS%n>rEjm9xR)8^iV5*=d6`{pzEi4S_`6=FKkhYts&Z?nEYD|iKE1Xk|3db zBwo`*t2a7bO~J{C=!1@V#z>)R&Gj`TDXhwvWxAFWzIGxexdVo)+@p+uwMw6o4+EGV&~!aUq7usTIJ{ zWK?}924&cS42MUIp%#)a!6rr>dUAB1KkCQ(|4*@`bQU^#=E)gQM$M3viYb*?P`2BX z=cKb}oZ+tML>_VvJjSY0r6j@{lSf>`LF6eQznvLA?DCO5K}1o|nWBL}ijD-)ZM|XD z5HK04!wa;i^kL(iLNo+N$@)=BSah_h)t)f6=R==^sx0v4wHX&F~kf+*8POpg!k+Bc!!pJ11_xEq3QUP!pp zBbi{6#b$V~VaiJ#1bUu3Qgs`8;S$D@x`vo(MWzV_hl?&*#EL-l5D)?Y1OPKc1UCS1 zUu3S7h5^V|UtPPqbpNa_awDZS2G`1$9_{v_T1in`a9jSYhUTyUw!@8)o$hwFed^ZF$;yA4{gtmy_uKtn ztKWaEt82abYESG(-LC-tSNC7j`q?X;)eiT{zZ&XqPyNS!?A~XO?x)^+hVDzJ8bbXI z22FE+b?nc6{kLVmMPN?}l}U&MU{1n{w9R!;urX08Jp9YI5BPH7INf|nR-q-%b8nBG ze_Aqr9Nj6YZU3O%5B>OepS#B5J74;n`IxgApYrZx@$dK-|B#e)WH<~gp}`=jLap9Q zQ>(<->@W;)f7{Ai*LFh17s9KB%S7 ztyWH+IdoZ>-<(5NjiI#lvNGZ_okP+<)Y`NrB2-u+!NPvlOe%KdYABB~kDHFsQt>!K z8RY<=Yjl}_!2?+1RtsFd=8^Ch_&qa@geBA}GKXWt~zI z7&TK&Le_)2AtHtAys_K>2((2_L3snO8Bj#1ewGP6$2cbu{FA<%@V(VaFhDI+HFnRf z5SKop)7+aK- zA}Bn04KXO!%t5)e0qC!^E^O8tPFp^xFxYiLr)o0~G4%5DH+-z8<=D;&Q3$DS+s|tK z!LqpvvwxA+_bE4OqL~9;m_+6@x0TP%1Z3XK&c=2=$@#6zxMBN6Ge3}I zIY{c-vjoSde>MVrXB#fPVu!2OvZ`4lw@qYdTRSm6Fa;_4)zQkDgV@UUJ=&k3cJmHC zs5sk2vi7-ZrBiie`N|5W2)98;5lLup-~Ca?b@2NGa?PnNwMGf4@l^*1l5`S{2P(UW zRcLK_kL|64G3>WiDcxBkG#~4QiVo3iCU)-YM9{QdyppFA0OLEMyl1Tg0#n*tg(}e? zwr!K!tS=YQ^Kv~jo=mBKt8gG2_FI%#5kv%+QcXH&Mf4{^wn^wu`y}9qdUN|2tJk*6 z)071#^>(WsW3ZE>MMAM<=Jwmgn>{~-Kxi;(0BJ&@}E0`y96-7&PPF(O z1$QGzeu9tyakFFX-F089M`!dE>m++#->EvjE+iA| zxfs5sxSf(UXiqN|!@s%U#XTM8l0XtvJac@5s}x1sy0*nP8JB+>OR?aDO}Rbk;%q@l z0XL%3GVk?4v@IQ?U-xXmF?pU4W2 zme7ioRBtm{S~I@~c=Biggt4T_ z@TQSlmavHk;xXRx}Bpdbz0hO^Epd>e1t>n3ZXs$GTX7VhHK<*v!t zaqi1y`u2as7meh{bJBXfa@~WxmyJ8B-P)nt49~NDyy1vy+ip%5?9_$oa6=#x57&RG7q}Ji*?tzT+;&s9YnfC#iWG%2CA@ zYQU@CrR+1wa~*5tt;C|>MyeMx12JQ1-OYD38n?VI99!VsB^R|8123zRfE`_R=*yo1 zJf-J-nfmQKHR9oKU9cAH_eOz(sur^CvKPS1DbXtROQ1T5p#u>r{%$rK(zo@3zVP?8 zRF#`%F9?~Pk(HC7lb0u3wi*Fv7zzlz4RKlWMthd{ORG3-#D69Q6<%y|6eF=Uxw9Mj z^#s9oqiz!egIyxI^Y@fN>soA!IubA2cPMv`Tdx}Nyx{R=p*cmV2lTgcs4WqHrIZ9; zitVQ}%8R7+9*h?Yo5Q_}k1$dlJm2V?V`InuKS$cW-vMrh zk>Uh-?Wwq_BUYJnF0ssKjAhj<>=o|jLInqU&|m1w?X@K;gOM45rU?WQF$9B z7nqhiddhE!TM7V|vBVN@*+%Epzpuj7`*^zGT9kI_Yp(Y2R|(m(+1%nITj}wI_b*%~ z-r?i+ZtD$*GiGDdX&l^UDiz`JpiVrm6Aq1|Im};GoXwx`6Cv})v$m4Dg&S_axc()l zoet=Ik7L-#^t>!y#+RFCmAR%NZsAJ&S1r1tCYYamu8$^Ln0K8`T=KAvei6CXsNo3p z(o)SC-#iTEa=m}T^tQzuSiFF$dfmV+0_h8B_O&lw&%7>i{X1&h#oMfa9JTBlS5xeU zE04#;NGSU%N5BMo=PU`!6Pr69awe|Y!rA6UV5(;w3!K)Hth!|`;fM0R*PZP+*Pk+N z_)>GJN{w}ZO-&_zV!tdYP?1y{eNM8u_ieSkH`#(asNEba-ktR$R>ZX&e#3I4&Mu}e z=e6)0qWAT-z;*8Sf8Bd(v%GH~QV7#G@o}DpBx*gJh1akb?I=p9fojXS10xcz;51yq z?-)kb;P2k{?zOJ_r=l;1PPBJsQUfT3p3XPa*!}TRW*|1DZA|z7*<7d%`A6#SyXLLl zU9F=o?O*SE>d&jMb?-tVSe-^WqV3*ZO!(KLy4?+03*k=Jh|eAipTGlp+QZuw!Y4|? z!IqibiPYrJIRn_(ES!~|eUg8v`)i!Uo{} zQa|U{Z+mRUS;KX?KN#P0DcDJNV3WggYY(ODeqcjN%=PdN#&bFn-I$(U|8qILL2m8- zhfOGpezP#p>@N4j;baxJtt(q;ED)}f44D*Mh7 z)V)YV9sK(ASi9jL<6&l1!|%ol7^Njtv*hpaG%@+E1alK%oH)kqZ~T@n*R(~6ObI_f zS#_z~6j(Q#8BI*XGv5MCapb@+aAz-hCWGz}NaIzcuFX#|KB#GAVq-aSXR&$AugNyz`}22SA%>4(|tBF4eq;@DB5=~pOf0^?8~da z-<;fi3KtC=rG_R_*%iO90q@JO&(u;8bvTpJ2*LHC6YJMI=;fq)U^$0-mpkpD|Iqt$ z-t5f}Ujjx68C<5g?zmp4B`?6LID4-k@7#6llShtH-JRAp+bpt7=%nsnN)DEhZUVgg z`@t@#(sB9Avi0#SoX{w|`0!u|cg0E_R=OCeqN;HraZzI1&)GNU$kcS}adWKkq5H1L zyKM3^Kk7X6l6)Ud!^nyKIN2PyRhA9GbXWe17QT+qtxz6?CXvMRHjeI%Msy}0TM8vr z3MW-{cuCY0!g*dN&(C&Q<8Z7$xFhl99mt9BwRcOnN-~FQWzP=|7cTgM;VZ3Tr=@N1 z*yYXOf>Ah6G50mPtEjdvH&4SFFEgKkA!fn{1-Gxv5XBX3Wora1?%SH=H}+9QgXho6 z{}}%b_S~y!KkbSeREGaBEURco9p@7y+HGvk1Br*0=Dav{B~8~?=( zybJ2i-DE%N0q6ST-%(bvSTB&PVIiD~UqaKtZ?X5>!QXDf0yzWtHbu{Vg+_eqX09I5 zD}T!oo3uzAUhz&Ml<_Ii!A?wv*k%DX67gN?T+uf)>z(st!ebT#bQ0hqw&LpnTMc4o zo=cnU;I>i451PhepZLtpkH;Gc>Z>dg2@bb~`wG3L*3`!s{4@cUAPg}+fvk?4QdLLB_|0zxmbsI<3ArKX%YygyZQa`+-Z-p zz`{KSjI~2iunQLqi2hlqk^Jp8Zbgk8TFdI0ict@7(i*7$#iPT z09n4$b3Cu1wg$I|1R)?C5TKXDKUbB%^*7$}+({;|Zz>6J9@ponlT^&*h z*_8a<)NSyt1FQnG#Ap}_4$O9LZDm~!n3aNyZ=?GJ_;xd@_pSzu**v&g2^(EdB$_{a=X|q_DU9@_{lDf3`xsv zF1dMHkuN0K`5f9m-fyMdewxUFN~R%{b$Xjz;3>sqxvnRY`kVRLa(x{hMtWh1k$}PB z)3%2pQG%OZdXfZD|LHq+gZ>4lURWc{+zr-#Sx^R4aHB%{(Eg)auRS1Zw12 zH}f1Dg>`UXv4N=6yiIZs+EQom zethzToqHhUPFba9;qV?W=ROLcsb3Coxh)s}Z6tNY`SX^uZ||9ktVM$UK)JoA-t3gY zvtI4%A9Cc3{RfWL^Z9b_%gpx5^*ZZf85iPz9}mT#Q~}~>ci;7(W?W4gSD!!6zrKT& zxLs51tcj%RdXmxrE39nc$rdm3I2RX`4oUEITleMA{p@xQH&#MFvw_sSHgLD|;kSs~ zVpL$b;4^?NWW-H+(x_n63(0<+^k=qt=f8INFaOYr!o=q8**5*sa!+#*5Y0N=R&Ve} zQyiV3 zQh|YCNZg%$fBskXZ%%*Mh?e={;WV5OTUS#|66Ed>keye64{ueXGwoIK#=S+ z@8xOvUzo3dtq17W|M#zP64Zv>JsisrBDjcNgub0}Bp8e10>Sl>uD|j$J^Bm{t&byH zA1}uv0j&S`fpEZe()QY-H|MaB9&6eZx0UHa@GwA(b$MsZ-gS(=`ed6IqluB!_IuC& zskIct{K#NI;n^?Wdor^!a-SJ@;;Zi^5YX|~&G&IpEv_#rzYR>@=o>|^400x%4b$7S zl({1@yp4j&w=P>jK|gnb4f5v0T#r>E^tM0cbfCY_f#oHv z`7oWY zIr%NWK)L_xdC;Q4%sdmzK%NjfxivazwO|RU=hE}oc&tY(nIgI+SF5LuL^+B#;A;e| zuVxG9k8S2!>sbe(#-$tLMOk7xsmU~&8%aIC@n5mskA;c}ctm3r+Vjm61Vq@R3^sv5 zd<4GNk~Q`D|GSTcvxZGn(V1%4cv80k8RpWGh;<}!_`1d#owveZW{20A8&=WNf6XDoB80L?hY%u*CfjN!t-MgV9)m%oD^ zlQ|0pRrCV+mAfcZpTP)4Q%Md1FbEiI5P=Yj{;2NFvFC0O6eOP-$n6JfP2TikQ9({3F00p- zlflYtlhg_Z7)>+u&>Sd@r*KK;m?n{68flWu2k-~r|0mJFQIKO`W@6UC0YlBUma35| z5VtYaz!!0lncjMcBG?9q5!_5`;+~u=(POk~XQe8V0QJ@W=QYLE-j|NrVUU(7pv$ij#g zL$+p!fa^!4s70Ow&ukhbWQa_=LvJcAdK^mS7>zCagW^w;R@7v2F(Yf=hd&3}ACk^g zLO6CwrDsST3cg{ART@Dp<7k@w@tG`6n>5xUN*<)8*7D%eFhWfhwdFX+cZq$A!4!WK zB#7o|tMcDszs=Q1CY*E-HiQ3Y{WQ@Nt;2wn5)<#mor9)YbAu3kIK~|1IQHNGXSgKc z4929MXleyz6brpd5?~J3-IxzsO{ICphDayadw~W8kDw4BQA>>W%BQhdBFOk`)%VZw z!wA;TL@C&FscWVHY+U?Th)s%qW~#j+A$)PZ2oDaV-xx5>DO*Z*QPnd#ZBsxrDu@ z0A$N)0BJ~rBfqX1?!{+pq*Fn)VxbgB*w~rTK+X;$dfOx1-{h|g#HGllx!l+12zn7$ z{-6*UB3q9Y;p0u_>9M!!6A*D31|^o%^YT(5HE!{+0qhd@F%sJA2 zzPN=-&voKCKmSc*=4=y;>|=lTLyhd+s%hdUmsbW*nZXV*6PQ65D=s~QI4e?bnw69H z9)iT}v4WV9oT)hwNZKD{L&&H`qxM(4IMx`6k`m_xN4ZEZCV}XTx&eQYefgCip2mH4 z^e5+K8m-|It;bC5sK^)r4<*4dFlz`H^50oRk;?FpLE;mPGHa%Jn@)QXSsvU&_BmtOPti)tUgibJIiu26MqO(5Y ztO6kh6TqFT;up`g5(5rGshS=+daZu?5gz_w5a+!iX(0v@YMtZ*H6$&@Nezf;+M@o$ zXNhP*W4{yLjlrja#xzflc+g%OY*{Yu&k`W*a;(;I?kEJK9?GOnW4LD~ffe*Pa8`(V zxz9nCvn+;VPG?Vic7|di3guD?B!t!^7}Wx_4$s)AGzZG&D_?~%XgYeF?u2KF&6G1E zEs*hj=5@04vsgKJCkKljbFrs#D(1#eiYY_nmJ_fs1k&Zwm)yjiwghNen7HQ>ap_MC4(Fx7K6DN2xMf^7@*5+PKwN$uWOyK0fX(6P}7+% zD9yxma2T~baypTt`2UHTjmO5$$LZ_4h*>a;pcezqd`_uEt^^h^nOJ8Tk>?#OeN={* zMGh|7FfoN&kcbZAC=P7+)5Dh9yzojUJlq&cv?7L0`ymBf<`5DhFyd}TyzSI}T?ohj z#7`YdAC0MJV$;U7OA>?y-U=Heg>a8#++ad! zhq0^v%-)Uy=O9rWB@tx(gi3UR^0}CGnp5fli%&&zW~u8%M3us?s|-pRJDy|R5JY-B zl?ChZiJVi4j^@l|<{M%E{#OTMwJn=ehzSTX)B*>Sr~z0$235q@6dt2V=4JWaT!3K0 zbQcID!vm@KLrD#6j4$~yvx+l^nVNvZemk{dK_O`#yBY|pBRMkW(PC)krcZXwmRt@a z2IsBUW8V1k-m;5%3@a4Zux_oA6K3E*x*1X;4L4pRF=~)KS`y^8oCY7?AV~E}>j|dR zFq(yVYBU9F?{N|`vc@<0zlHmWC{v&1Mx`;HR}U_RgC!7;i;x9l;Hv2(;akVG4?&82 zN+1rPTO!2AVv0Gs9&)M@Jgs=8uM37q(uE|7Nh!8@Y5`$JI9zJE<$xsD-O^ue&kN}d zoJQ@&36OirS80?$FqEeTLaj(kuOt;6(zz8-V4!k2vjd2XsEGtRC&3a)KbC$kKp>=| zQCSXW8ssQOTlm4Zo6@m6BBl`vzD9?biveZk1i;8=9I%;ZSBk)8GAzZ)zWChGl;mod43|lG*5$~q z5lqw#82YdKU%xlg%jp>L-9iM$+@_%zD^dr4d$JWcP5_=a-GBMVsK7-Ci6jk!X<@9# zxPb}ih>iuwphPIh0%NjH`nh}R@BQbezD#YDp3XhlpX>Cl0~RhVV52ge zRe>ebsm=}h#Nt5*Aqil}f;s3AsdLB+8Om8{G0Q)_b#o_zmIzkBU<1a7shlXJLqba@ zTJEK%>wn*?OQ&m|+Y+v*CIey&4ist#41v^hxOP>3x68#;oaNbkA1Jdk{9by|)`w&vVQ(0QQNdI=r!=7tJ40Y=rI{ke2-D9zu0Z;XvV>>^)!uj%r>-3timx+eQ56lH zGoHc!k}^I%1gG6t(Qkp&F~Mh+t&swOs1A07D^)61l;0dfQ=ZcwAV-ssrG`;6Kns$= zCrWWJFlz142%3IL^GUVT;z0`Mp3I~)21rBDkibjSph7J2T+HvK(-EvV+87vuRY`0x z*%~atztoH6$!gQvoEgkP79S$qoMgmlum_RM0HCt*7=-o7=gF)*5KvE6*h-vCa-p1Ol(D0_MCn?~X-V9bn~Ql9d&Gwt8vUUBUVz6LumaU6(K9K59J zGeb#dT$gA2IDs@H>7mN25SS96br8%cOEOR8!E_UprRyP6ZMYICHHKL=S2&^x)S7CL zgOOq%VE&Z-(uN(E@Jn3uGK@hLLcj&*VdQpBG*;Qu4KJhJ4INZ|CRd=0knJ}Io>nv7 z&B$P9d%i~L60E5ZT$OT3=$`AXViay5AYXH|A-)1S8z(E)4ImToEzT+h-vE+|N+Bg^ z<&Wq2E-gKADsk9_A((16sV&!Ij<;CokeXgQw`pJru|Z}$14PZ+IXVbHMI+Q5g$l)S zZyQZ|HOi*II3o_DI7|>~!FeTjQVYd#@0?5xRcWX|T+T2g$Z-^h7cnR??54}_jlm>J z2-sa}m)Oin35rq`n3<>^cA+TleQ_yg9D}3;YZdWPYlB#@Fp>a=S!o1%qp|dXrsGyg zGAplOPD&9TCg5Gda$_)71>h|3vt zSxd8J=C;RH2?&rqH6~L?DTsSOs4}J&U=$+;Q}RdhNOkE!q;|1Xnw^A0Lgo@Mz6*7jsam#G6Z~d|nI`45(i(nHs^AJ+bQz4^+#t2g23XxmI6_ua%|3fks z;Yfla_7DTH+9o-9HPGHiQi%gB3Eozx?~#wpThz!vh@dj52C+7A=W+oWO5+|0JigNh#x&q*O~mmph^i!X49h)yQ*BlExaM*@FZMAQ0UEbWx5o zU!7?^74x~UBO%WR6XL`;UbqGbnfVwW_ zVk$OsYlz4qpj<@^rzcK);wlUD-qWbTGs)XN{5k{ww+Kb%=Gl6>9CHME5cgs&q0&Mq zLQ!q`ABP;MgixML+OL=DjdbMre^+FIrFyXwT}e3{GY}_%6hpkAQ4k?gqF_Y&Y5(sm zBleyZ&uMSK2%H-S4Ue!vkv9|DDAOj>v-T&VaZPR#J$yq`@SePx|{Io2P%jQwdA|iA)kegL315Mh=1sq3%uCajatnO$JY~ zc1VBAd%dGf{zQsOxL6@4h53iih!P?#bYv;`96@RSoZPee%{@xk-1E}xo*?9!l%e7v z@CV$$5Tyv`fV@t#Q|~`apAZlO00aOsLQ8UO$VC=7`pwb@E7;1iyNrxEXd^q>PDgdku) zE_DG6_yB{L*_e|7KT-An$HZ3KeV!m_-Bt};0~1{BRCI`q>^m(|TdbX*9*0=4PCgB{ zu~?@I3xl}K42c0Wi~>Ud7y$s0nE<1(0=EY4ShfP&l)GZz`nwH;(?NXZ+HF&i2s7kWWJ} z27qhC6hH~X-lMpm1^ndCuP^F;Ey$nN>HI$@AN8A)4?6Pc>%MiDuPzB2Z2pjBm!0|i zi*D@bM`2?8S-*SY-uDE0V#x~?4TpKKPh{by8X zTC=^+{$Li&stxiO&(!2Pd)924Ce7wTBJAvKHy?cbUGZG3UF>^a>g%EX)cb4MUBs&IA?6Uz;-rykvjXeZHC`OqqX;p&lfJ%*hHkfBS9s3p z^NBOiQcY*8ixkRIk|@X&hIiXN1ZNR)NaHaxCY4z=_cAZhRYqlQ`dIs#pq1Awqd3~L z=*eu=T&%r-o82_AFdk=xi_x9s&;RXO5B5lldEVLn$h8+foHx#)Kc&UyNvhkQ{zF#% zEwx{o;6NfO;%w8MY^11gpoA?AYzS^kZBn>08=F1$W4UXMn12=UUw2LQ0xkORf8dh} zf!bl`u zC(!Z_Z(KWRa^w5R%hT*~WR2GSHI@#)w)8(WmTs^nf~mn~QLWAcZ>IZ(`;~-mk8b&E_BeeC#0l69s@Mm}EUh6w6WN!kt)XKD@a+tVmu)T5`1e@$lq*=W=|jebL$vy|2A;hWCb7B5fmr-X^s~ zLYy~K+e0KDE^gvI@@|`7CyD=FR+HK?&S%ru9q9lbfZNZ9JuNPQvagC-s(B%iGn*X8 z|KG1=?)>Lk`#%e-YZgj^u?TCCC2)v#wz5k!Zbd%15Q8T7BC1x*E&kQ>gLyaO>%fS- zgE>O-?BF%6#(RlNT7U`2F@*2d&M3q4 zcIp&6{wA8*+uxQo&gz~A87wR_=CT~HssL8pM6T!2Zz1A#?c32v`W8nYy47M^C(yM0 zc1qJ;nGAz74YJ7%Q~tkKKw-O0_*?L}f+uGcvV_V4I}4n*B}o6y9asHU`ZA)UqVGYdI8Aphazq zMc%q(oPDQTS=dvhzAK$y%*TFg4nVG)rZ%kZxAJ8s5YD~`@_R3fj^R0Bc8t}-yJhI= z^`$f3s^OP*JjJ2j8XYtjoQJKOrP;I3!0NBxLndv+n&k$ct8O!;wp z6+=4R1FnAJ9C^>49rXB)kL3TnQ#n^)uY-56*V!f}y8TUA-DT5*XIJ-fc$2yvTX$BQ z{S%rPv8LUo(Gx{|Z8m{mUkV5Auf^xTn_-KsyNn}lo(}pYenPsc8p~_QH!#-#$`LA$ zeGrZA5RVw{;yda7{*7nzRCmWS8nKQ$ij8UEL)PwJ2ITK`Z}fi0{$zL%{gl>qAH7dNU_DRVw29_eqgKNujdr2rW0G)U0oFR&UnW@>!97&b3mJ1_k&6&xdPt!-4`k(Zz zQ#1JbZ&_Kui{`WzUjD#-9YTN(p2>?V&shl71^N z&ZwJ*vrY2)5h`r@W3$Wz-1Z`7t;^-Fuq-I;UHG<(n|fR54?Xnk9QMCoa}eLuccl+Q z>~0KV*88SIe4AAIt1f5G3aVxcRWCFqs&6E0y6N4ZCZ*!y26@G~8_3MF+_huz z=FZww2q9Y79{GkU?Br~!5mA86X6D?5Zfnc3a()d_o9}Y;MsT^RQWw_pk1QsU*`?x- zv9I&Viq6a1?4ZeFLSL_%=^BmpL!jMXQufQ2Fz#4*30UfJysvvgCH)hRLqPp!mEJvM zb=G~BvO*EGt_Jq{b*s>A4F9y_x!-}x{D!g?ZL~Cbeu{YI?TW?;*Nd#6B`+T`OUS>P$^6G)b)!hVtnC@)uu-7 zDX6+ht8cdKF9=Lrf0;hUB1|2;te?t7`#QA@TNyfzo{H~2cmIKTU)CO>zxy1v`Oe=* zX0SbbUj~9O35{vbs^B3b^kRl5u-d})oma=P*y+jn2+7t=f`)HD0^YT3kKk8ZT4q1E z`-WY&+QFPVA9S0JIhkde@XKlWYpXo($OdMnKkNEA!*00A?{eQjvJNLLWwcZqZ^nZOh zZCRPDe!qDtzN)Vn_3BqYsSG7f(JR;YX;F%AVt?^lUqkxq<}5Hn)h_~8sec7UyEV6_ z;glO$Z*5rIEa_v1ndg-E+W+;x^Yik|ewP;Ix$s(#voEmd%r0~Rjax*>zekCACeW}Rd3fpxiE24OPDRPpoju~&JH+e{^A zKBXo{vL2I9Fn8`=Qe^7v=zb`syw83)m6-~xcz#Py%scyY=$-s``qQBiwR1=TY&gGw zsK6{7b6f*!v(tQOq@ok~PIXv$F$=Y$Vj|ZLH?ror@70 zx2Uq%q5>ArL}w$3yJ5`gdK15ZgAf*RL+#=$r(;o=DIZ?A7rsc32Yh}V(&74y2|Eic zcH2c16WCTYcl|ph@$xsdF1ZCD7BTOx3bCntC6CMx@&q}By3JfgBVq>N?7Xbpzor`! z*{E9;Vlnc5a-7YbkBO6Z9dXTZVX=diqWq*nRtWQPlZkdDrH1lg@la?bj}Z9yX5l#` z#s9&*N=wGq4#>Gaevf!$J?hB2v=6Y~mXi2g0IF6MbUJuWl{evnKC+(K-DV}~)GakN zxW&D-f&*`0m9J3jVfi{P+rj*@o-?q-now=3)g>#T^A+5-Z$5b`&oy`9&O=?^+F-Z+ z3Kl#j+V+c|2S>v!%BVSD(_{bH4p+=UWMn<7vQcIIYo$~5z}E(-a4?4M;U#pollnP+kr5%NSWY3lwv|otj>?W0?1F-}G`l zcmYj9NEG27PbSfQ+b=&86P%ricD{t}G?%E#i<)+>Xk)=9xV}>ig>1%yHt(*~Hn#$m zc+YBIxO-uj1AglX={&4wZf!cbuz0vRmE4%znrBX)%(k_66-WZS-K#j4BDpiV?R!s7 z3-MHx7}od5#m>e8uOs>f08}?8DU#&+P|_7!1mA^3EV%n~6-az8BFFQ@@(~Q1aKBCt zY-_JhwMxc+u;Pd@P3rLRa5P*74n6ExnWPZu&~!p#F!f2+}{ujmXEq9oDfP0 zxhB`znX4ovU^U+f_wxO@bevq1nvp4qlc~+lg?A3?`osi;dE49Yszv&@n>?RY9ou@& z9c4PUB#p><{@mDHzQ0ii9mF`ojaO>jUrUpP|9F$g+HPP;NA|{d{|mcTw~XtHL}h{6 zUW`6iJ2k1EtqAX_l^6x>`MA^8<_wuiwpyC`E~YL4mK*jY1hnz%Vi@P-x9WC4+z!7~ z#)@Wl9P-0Lw=MX+ql#O_aZ#9_k2~GTy<-cf`EqSi=$CDtWdfl6%fjjqgo86XbT;2n7gDhU% z(p{#@Pq=o-ajzx#wC4Vv*9Gx5x;brn>rlwgJVfjLI!>r=0$wb0*TidG^&sI6P1OzY zrMu?%2LdBel|=Zu(Q7Zcxw7AYLAYn3+#pnut2PzjRc?JRb^E&`_vVM#k&}G@Dz>RF zQu`WADX2cRN}TTEjiyZ{)e|hsk;%rM$hfrl>6F`Nn`%b@rm<|{4>g;%`W<(77X6ZE z#09P0!d?w5=j*g0&l@c0?_9`v3D+G5knVRzeGNtn*jK~H404T-U&~d8Txyh^154ER zof_WN&A5QO*dpeVo-n^XxChzVNPXHfxsA@J51R!`QUF_C8*8M-VMLua(0kiQYJ?kh zBj)|Tbv`rm;XX2SgzkIP$*pfopG=rc8D8hcB;APqzfSiTX6?<7&zr*3aoc3ZBKxO( zP0$RhFQG3ui`T1b9F~9mzDa^V^-k(^F^1k-EU;UXCZF$&tv!7A>cn1{)cn{i%J>F} zr47p`1es+5JPDm-)tb>|CaGPHXKo$~H*PF`|-~=~NRnUfR5{dCG-}G^J zGQ;Oh0u9v3_i19Ty`eCU-o~bCP54LqG6jZGahCw?P_N}|mlPR5fr!sTcQXII!Ab%z*>Bxys1k2EJ%=}-8;sXGWco{fpOuRUtE9lu)=GYZ&wjv+*!2Bt*a6M zyvj35$=Bt|qu-tGjnAF!t2td-qMJY6QF<2=;wY24&xO+r95b5=Sp1EncuM18Rb;!% zrCsK7qFp{IOvRCy`8SoNCi^a6L_)K}7x*E1cCqR2X0!^;O*bQR7oFThL88!E3U#9z zP6-U{3%mhs9|<4z>Rudjw<~or&C_@Ng?g#s2sILw4CAEzv)-GZRolf9cebjd1v|{q zxvQ|-n(em!X&)3v`F3?B7ErLkuIJjea1S@1Kk!tx1EYMqiRsLL%zDPM zAS5-)n_G8>;=cvlMG-lR*~>Uj_59~1CK$H}|jZMR859;nu~_y_$Ya)TBWQzEi752&dC(xO@H`>(eCpRA1GkF{XKfcR}Tkn zgh2OlDSt1!gp>9cxP=1RPB>XOS%9DK0aA^qa>c7)aJZfqaD^`zg`zy5nXPkhmr)o# zOFaeaNxxLR6>>fC&X>ZXKiG0!KZswXcZRz9PI$QdSWcWiXEULct0nyK*itu9#MPpM zv>{$K_}1q+O)BtPTMgf8Lp89H1RDONt8m855~Y^+q$s}6)3(^Y=mq-YXSl&rP@RC1 z!(h6&zXR`JFVGnV_SSdg#r*QSLKtrFJ*n=FR}(zF%N=nnc4#t^MkuO1@y@Uu`fS_} zoQvIyzBq$MD2@tp<-KWyKrDYai`q>mg6yABQR+Lfa}2$DOx&2%t^Ic;@_}La0;;G3 z=-|c(?2ir_EpHZ-t%L4;XM9{POwLdG?+2yec22#l+--R*A~O@9sowU3R#Rz-*iYhd zzw##lfd0+`&(^0~>$OVE5Qt!SHbyZfJ$y!~@x8e=(!RQhRx<$7jI6|y5C-o8HN%z~ zPkVd7(J@4|lE*=yK?%bXL)6?yEQ1mAaznC!m-gII{R7GbT9h_Jhz%SYXuL$18l!^q zpikeuy8tgtpVmn*| zK@>y`;}E*zNb}-vEi`#|4DBo$BQ*14Pd1VfcsZqmhM>vk{-G`95TMGaBW&covqQb; z1WQ3kE1p1bB-Q`|`r!QEsTTtui?__lAQ~H!ph8m%ttPM4ck=I{LXf~|M)>n7vh3^5 zYf=?4hiodN@8sWPgGeQv2V>$*06*RUk|nmR<32FacO>#OZet3l=z^=1UJWXFh4+<3 zI+Vj-GUz)h`LPqC2p9%4gPIZW?&TFo;T=#8X$%n*{~g3mPCqxxk@3WX%%RqHd~x_U zUO9j%se)z3C>pScmfS@#vYdJ9>|Y*H$EJY=$-J)=h=hrN4kpkpB9rBdk4c?|N<)z# zf+R8E4y=_}=_LZgz+Gvk2!XKF#*4vBLKwnb>RMqqrBLoIEpX7unFfTl(>Tdg)T^~J z2}-~p8rW3CX~`?*#T0yNf&|Mo2uWkp5a>1JKm!?^XshZ*6NyE9N#+5oN_*s#T2Con zQ507t5OTir9sY5@Z>)@!ihG#)^TE02U05vRAG`|29<^0s>QsmCWw~yv?^I|iXK9rI zJXnn~plY}sm9EOwq%`AHg_QTp_g@z%_>4u%;Pzn9fiSov#TuDBAqEv3xG^%&50Aqo z@R}C*00wctpsY{aK!XLW3MtozpwGxT8@M_AT!No8V>IaD1H!=`_H+Yns0%UcS2Te; zNc==^Kbsqg_mKi?x&d=+ zsKUK?YAkak@|qgrx#nM~f8^Yd5n&P2FYPbD#aLFoVqyj++Rwc8pK^1iaD3yEN2IWt z6Cj5WtFw^lAZsnL9!jT&51l#1|L({J;O=mdriTjm9)OfEeoIiJtH@mf^*1W^V|)Kl=1*rLG5YBNyYJ|GR4`T3Z=7>xNk zB9j_O!P`@@1QJ9<<(yyuN2e8sSZf1dcudh7B&Pwrgi4ku=%t%|n|}_bG87gjj-4;$ z=O5IKz|Mh_32N6^LqQM;AQ*Ikwlo;>iL%2{8sHPdMges46=^8PW1*V762+b?oHM9e zTjE*XPx?R!&e9k(DX9)cY4NVn5D?dS{hbg`F<%ld0^|Cl!Hompp%R#hq^A;r==L7# z2*_K59n4{o2NUe~n2H?KC?Kb~@=MzSjJTMZ+z^8fx177UsXE4xjF>^OMrg@pn&Kb} zTPj?k&-@Gr5@#%9WkXP~w-^ivh)X~PA^j*(*ms6;DW(@RlzE6|(u%CyGJ*`H94E>- zg(Ubyp!ymx!*J!;pS0C_=dR4D!kz?)1mxXgj5lODv{qG@Q1u`)rn8jj4|t}ZQ(S_y z3WKnelpwJ@vIp{-QU3(i?t$m`BBOXygVF1&H54o@f_dWQoPMaE{O9yJg(UcfA_#gS zanTCkAhAb~3D7AMUY^K}1mfN4j6eYy$zfUov5#^z0z_cL9o^#Lxl2z>gV(cYK|K=S zbR7tSMX_E$Fe60+N^$358*sekj4}NLRj;*~NUUA}X`b!p6p-Kv9RN{Nq^8fYL=JE` z!+@y;)|4*p2uJap_t(x~WVtmQO`wzJ8H7MN(ZVzQoP-kd79@7-LA1nr=ClxA__1Y! z>Vc>CB%^pZP|r-tNq8Yg1Bv^1;;0D~2%fFy6iY%#TD>GvB>?LfXhJ<4gUTVE;kP}p z2s!bYSZw^?_>iFrff?ulX6Y3?l8Ggb6P(5e&a1eFLy(rPt@0@-639teDFy;D2G%|Q z!Glj**pUnP%Zm=msgk0bwx(QYAk1J($0y9JC{#IX1hBL*OJ2Ue_{upw%T4_0rKK8E zh}h&n$ZM#yj6<2MoZI_>qfuvt_yx%pQ48qTUFVsuC&@LuNIk(57RA5n)??&;G&#IiHMTa-Z(IYjY^`0K@}rw2tb0W^^_LtMKH`Rik@OI zGa4#Jion64Lv-N6xR^OZ^y4`wmISpQN|UBTnT%t(LRd)$!4O(wX`VDuOweP%)`!*0 z@Of;u*v7c04-!hY=UFe@%mF|vCvBXKKS%(9YWjnTwowo&A=QiX^9PO$azRCM$(Dqz zJx7BQEH%&pBnBF<|JUr)4kf@@9dXE@wGiPrsIC;0VleZUju>eO!*kOEf*3sLg0-c} z_Pxb`=QW0%YTdZyY&MOnl+Rnq0mGz<8@!?iWwAneYTb}FCt2v$P>3f_^^AHg1fqB- z1*dfybhcen)~4IYv%oPC%3%<#wp)V5WsqVvMhRZ5iF@SgeQ(XxBlFCIcNLpzuSVQk zFE0QO667e+ASRyZpb@IwqSX3f>$I(SPg>kSwh_eD)M&xASVOonQb0LrXPFzd)hak< zO75B1D?K9SRUjCNHwBG~IE#|*l>yL-gBrLJ46=~Nz0>e`M^fuFqJHhUk-GheDlEKRi}BNLFm7P8V@uXq`Pl@s@~d3R}Ovlum42$t)8-IY(_;**V%J`1qH(7L!(ly z9$Kr_07m9f_to=2DW@Bg9O5Xv*7TQtdOF{XH}Q{-_?BWy3Xs4g z5rPV6F^UsC@*~ogP=&Z2o!P2>(mFGKy;Ej;qOr)0u?P?o>$Z}H19Sl5q>f5NtL4Pa zJ~cdN|50(68h}<9NHC9O#t^`yFFQ}GbqoeGt3|?jFa7r`iTX)~4pb!>AwkAMPHil?*2ML|7%Q>Cz;O**B_?xA?|M++l&d*mASflH z6t=-iSH!{dvg#Kya;^V$Ad^`pRtO!_8lS~HFRQ4M8;E8ku(Mqg4du#U6*XULZ@BXw;spYp_$nlhKP6^ zm_(%al{l8nO|99=!~FF0*xQk~w1_3EPg+uxbxN=UVAUw__Kdt-9Gt_izBE<_;G zrU++%C>)Z;yJC#+35f~1jm>^&gOil5p~Yf311Y0u$Z0U)aqO+yefjooy~rFvxI)l0 z#tys*NIR@%0P*2KRvO@q1Tmj@dI0~K>o{z9ie((yT7#3gNJ${xYZ%u-g4bhChSG5Z zuvrIxr%+mZ6Hq%>e^W;cTAZv8#$P2-QmIS?Ze;3P&B3cchIt49@3OSEM~slr^= zGZ>;WiM>#lH=Bc{5UOM4lE5RymGat=x{4a5zc~VeB!xf*4`#!}Avh_Ax5r`6^12i=O60&69BBxEPLgn!m%B({=UB#-$^|uH6D9SkD3BsJfHa6{p(oPw=-;Z~mv-sl z=h#YvBT>|a7d-BA3vnK#JRW==rDHIjwq|!Ypjj8(>YRi!2B<6vuN3Ly8&wCrHP2k> z#l7n-CX~JeN2no0bCs1k5JP|jXDMgp$da@ojlN}XJIOHetWbz57$mw777{7JgfgZ? zJThu^JZ44ma2o54lLITnf{euVtGLc=nnGkAudL{xMnH*@jIKQrOERO;SaKo3VDYIu z3~_$qSePP6X8@ctYkAJCRCU@t^;jUDg`zqhv~$q3$V9P3Ntrid@Z{W1!Gtk|cnMmF zBP2uzzz%DbK~h8gzP@^CwjsifFA{=4cw^-Eif26B5VWHiY48RhEJVoYlZt%(tWNK) zH^ddJspJyFfi)U}v|8dm_nvYJZy
czZU#+M6|=Kmo7*u~~Q;W{?-hxuZBKBz=Qx{q`J<{@*@pEti_ zo*nva{*HOR`NKN9N5Nf3-rp0c=h<84Zr`nantK}gvTlz9nok^VXle7;)-SvEh@j`h zF^2Xw@4$_+dyn^8ZX5$>PxEf!XhJvkSfJ&^F@p9ge>KC8Um-96$-4vIcp!`dGVoZi zbW2ZYNpJwQx!$-;b#6Q>Z$=iAQC*ALV)^%gKpFrdA^@`>0B{C4 zv)^Ca3tl)Eu5hk$J?;_JC)vWf!YYuea8L^Nfj>ma_T&#;;IZ&Sva;l~G20p&BWh?h z=o9ObTlN~7NQ2x^TbZxoLAVD1!~g(fW)X`F0ZJMgR(h4aT%4B6(k)l_+-GwXe<*=8 z16>G#f&p4fOM4G{FYnm|*K`1X{DAnsPt5nT@4EUgJ$f-VQZ;tmpU3UkvrqlM zfB4!@Kh?15#7sQFWIL{CisqDrTdL4j5dQO=JNVGY&%ViT?2F$&9e>3i=KZmKRMMY4 z_7{vlUu^jN`D1?;l_D0DR5gt$e1v1f*TRZXc}aBt->XlbnVGG7q3xq|YjpIvI^NfI zmY%MzX18v7;c#i~ub(D)@n4=C#18-eXJh3-97f=pS9;sVrh^266%{_q4#@88f^gCz zmK?}3&}K?!QY!>Eu>T{VHbXo7Z^IV&H9$gDytHRW=VYqHZb0QJ=}DOik!$!@1eIS6 zoBoQ?XYUmcBS8Y*I~&BN zeT`$hAAm@svNw<_o&{P=$xO;_hE7I&s3la|4cIV&LlBmr`oh7L%_SV&OTLMcfihDj zliMyh@i!#%+YSyGKm10yim0H=U_SqmOpwgEXH`O;BnB+f9lU@ zYkKuSwt?vi~l6O`d!GU6Lww*djdH%Q%F)Zlj zD_SevPM*oj5UL+(A*dRlSN5af3dN&VsVsxO)l*pvx6_<)J3TU(c1{Il(ZC1 zS$bS-M4%)Bi;kzk))uWlkvbsrz!lE}m8PU7Wk|10A(8l@`Y7g)IVK`-S|U|9ZJERV zWJ4#`uLiOT^qG>Fl%X-%#W0=%I=U*9+!GWGACPa{4G!marjnoROadS1I3*n^GvG6~ zqJ-+Dh+CQGe@?H1GUCSe4lpcdwePeXl>@z|#XT3Guto&a~fOjD2K$$6? zNxM>$dfJcBRS*)j-k_t0C2w=t+_r#8S^D3&s+4;O%1lXI%I9DP;@MnZS3IOe8hQZX zeM!i8n#!(zPjkF8|IWJlbM2F+VA;)o!00 z$SzQ3N@!A%C+-}hVLBT#PA#=3wF&f@l9`mRZw7{u|3`qa z%`rs1V~}V~&o(-?ZQHhO+qSJewr%gRJ$r21wr%Uo{k-+n`PWselTIV)Rp}&`V?9y9 zAQ%@^wPHD$>GkrJWD$^Q$pKlXmGasX7MxV@Huuw-pTa4D-Eeu`nu?m0{gx!juU74l z$?1`$q_!J`u2nj=68C@QhWA(;uvl+oOCwVK%3-RM?y~_cwrz~~+}g~JMK)aOE*nFh zy>GswZ6#}ke9BD9PB|k`CtyHDZ_$X)=~5N}Gk71_j6fwLsM*rIT%`1?C+bOv$e3-1 zzGRM^LM{!jk&L4n9HZ(DAl^YF?g7)vOi4_2IFrMHrKgNL-FHlwE2_5#Pr^dO5J3&L z)-nH^hZEV#IChU72?rur@#JBa@IR`)Q5HUh7CZa2L$Y6c>!`-U7VFuS_!(+cl$G$j z*z8El43~f&s_bAEjBPn8HJ?@7;Mz@h^hnt`FpKW1u4?7~WFQ)FL(mCB<^as5jJaIFHHS&x=ioX;b3mU+$SxDLLDV!+AN3jXIk6Z}EO4WR$@L z!WqRUYwybpg&viHnUsxEiH%bPI8Dk)Ff?)XZk~pG(8lgaLV>07AxmTk^>=-lC{yk7 z2RYd`^H68JYsaAKqgu7eqX8tN1 z`e>GXTgDINvnGoaO=?Y)5y<(`L+}dhO=LbT|GkRsO6V~-dpnm$UIWpRk(3?3)5{rA zbc~hE!NYFyP2}Xay13!S9$+P(T8unV@RPz)G6S?A!wLdCnf!ug(iP%>4{Dn<8=MB? z3;Nc}S4d_lnsgsgz%>F>Tq7MqA1^qnrH)*h%#(EFY}C)wRu>bld|3H=J`1b~Rv|Mc zJFbrAax~`!8VFPS*AwEMWRrsm>1$4(8iI>_da>rK%>yW>goMo06GkC?*YBM|ZcyE* z{({107LIv?41u#Tw1z+K=oRLFR^m!z-Z0=hHF)9?Q>baO>=>FCe2uO3hiLpMfCez; zKQ3}qfqi)Z`UTAzNeSeWl&4I7i_fs>=}F$-PlR4gd>OE(0L!}Cj5%j76hcUl+26F7_BC1BE=EJJm5~DYU@x%prq{{tsGW{l7Qnf zL^5S5Cb7^GfJTuq%GoEqOe zFN1zhbUU|iCdL2R@QB6a&k4X?jHUz-!CMV01(2Yk-W9-7u#vWPr7`2zcI8OY zf(O;T%7_+ZOA*ZbTQU5lp~~Mqn)hl!F8fb|SIg#?3yW}zMNa0GW_U0O9?;Q>ZB;NQ z^3RfGiYB9Gg=T@6e3Yo^%b@IrhCsDpg{KuYX<7@-N%tt3H<^t51*LMlr~yo3g*nAg zP(eQv3{a>yOlmD)|C@IrV^n@s&nI;=j&eMthml}3P%`K}mMuZ~uUz?(N+3W{cj<)Q zm?)9xJIq8)t?4IRbmHijDF3_rGANYjjp68b%a8W7k`-$AM7%a1SMvopVM zi}T)dyN|w_7ON+g)oR-ZPAHd4c{h&Q>%@IA8XeBJVy92t?Z6zK>4ejF2=$HWFd%MG z(FiEE=|o%swF8P++kI&3gE)0EY>82?4xaVz+Ur!&ys4Ax)lg{S@u*+^izsw#OQJ>; zmrka4j^uJ!6j@`^lHQArrNHm@gV0g@ceEb>7e2B66U~#^Oo_kc!`X2vL_lf#JT51R z;*?5xHd%#Mke1jqMPZK0MNV2bMPY45m!Nkx-&qxvKcKXFL?O?IzOVhpH1`)YnP$Rs zPg7YSqXFPinoXZ)z9@{6(w2`iI3BL=F!M}?<2+Qq!G5JLGt{R%Ce{ZA1Lp0}yr`oC zGGxal{UmL_viQ%@ma=;U`}%3`{83`up5;KL#HvFq+B{yWarxUI z>D?2SrKhF=pPG+!Ev|&Cc4z6oTJaBUZ8(`KOR*2_f_mlZa#84syO}h`#u5v=%Ando zx5BPAUa=ckciyDk0*^CnK5H@v;VvDcj#;+WBnNuNH5jd<3hZlavJHW*O_KqOK53AD zX=ygoZ>{r?>A2`t)AUz;py+@-*-n&~w{OF4<9#PTe}1SFA7;Sw0`WM~Lo@=7cnJ*8 zSbUt@sWDgr(kCLZ9rJGFhp2SlW}BF9jv)gfOi(S}&P94X%a)1GvlFRGLbx<@b9zqK zdKCL;dxNCQ1;>q2PdmYIym%a>M4DX265+3^?fo|$Ph@z?S@MlJUG@tx1Iq*Cig%fF z21`Jg71#@ko#5LiB?*p(4e}AuP9O-CvQfooa@)Hu=e!p?_`i%tVGtoO%0uF3xuJ&? zi1m1?vn)&SlGC5@TkM?5-sE(gsT2%U6nBIVbJw7H{h1!haq%QA+fleADYLOvDIEGOOmnak~QXBoB>1$7}j?!PFp&3-r zB6dBPf+$c+>IJR2b6GNQc{<9wN{z(f!ey)w_XTPQRC9-se|l*3=oq~`;vT%q-4g^9 z&3k18yOr!vAfIr3%%=SN2t47Il?;i+4WjBj9CYlRi8a}8y6!d% zIatpTlT0F&1Of;45~QP}R9zFNbQZ5~$j()fSgsfY0-b~LbyO02sp9fAjdOXT&fmTo zJKFqds2kzF8Ke0ldMN{A8Oucm8uPbLUf%ZucK8!|IorMS=Bmghg%lLKfj+ZB(tMdH zlCV<#RoryqpIGroIe)}BJ)eB$O3+lpoj>NizHHa5ZnOoh+;9Esiz0-93Kn@V+b^V} zd_~5)7&t~36sZhVYRV5I2qmsr{V1VtXP9m*Wj~mxSh*p*tG=mS^1oHU=$?QPEu)Ao zU&J|;Jng0AO^<4m92w<5hETV*ten<)yW?goJXld3QYPa!2m3E*buLytaF*#g*~z)}}0HGlKA6pZY8vkjV=z zFc}Bv0Gt#jk#|9&14BP*mxPX_hK?Q!N*>!NY6k!vb^HO!pdeCI$&wJ2YM+vPu525k z3KLHU1gbSS#DYzAK?JqW>o~RiTm{z^!_otS!FUI;ts6q(US-glJsYt7NLWM$N+(L1;vP&5Ff#61&(Ya}S|a<=ft@>l1(*B3V@ zZ|WZYsk;3QXJ=VBd7p}8b!p2kuDoH6xBXV%G)~X5W*;2iC>?*T(Zo!rxJu8g28pb#d}M+sb$F8Lb6ZE)Zu!az=j$BTwTOtrEG;I8)C@ReH)VC~!^df5uMU)o(>Fh09c?N#Q2!m?lxFEI`78lcMN4UoFP~o)8kvq?nC~AZdfGb|74AFhFeo}w zCCZrxF1((s2Dh@v+uZxpL?x3H8jDjLH6KEZD$byv`!<^|I2MSQsy+;`r@1J#wDckZO`S!s76j!8t)NAv*uis8X~l86?;B9f zC}jX7{-+c+GjA6QzXz2YFXx->aId|7bx z1@4h8B_AqBg&A;!Gz#Y6)kv>5W946=tgeM#FFr{C*8lUafddE&1GE2n*G#@tSb7(w z>CxW)bFS4(IB9PtVaJhZXng`&eWb6nJYk7XBl-P6By$23W^zyi0oeF=Lh^?b9+G!# z1i0-1&v>*H;ws@E!ZP3XCWY@y?nT&-KL4bVI&S}&*PoVUl^Sm`d1r&Oo@aC@-n3b{ z#xPbvY-@=d84PmX$2G^@p?F35&&y_JHfb>l(1vJ>8o+@7oLSsDv8*e76>nUN4;n87 zhQ_F()w0pew=SWKSwa54YXL*`3Ul(n=|_OwXu z`!+EX^Ky?Tc3 zUB+#@V~pPusLZ}nzI?v8249V3-`c$|z4R$vZODZP%mw;91!2Y6%n)fRO3MiZKyuhw`?pzK7cu+9QIed|p@h93o{EYn zL^u=Xzusq%b%_@Fv0Q>y^)Q$b-)eo@N;p;~_|CC=kfTKTqZgUDbLHK zW#_u-$Qepceb@}hW{@XZ=iyLGQr27FDJw^^(i4rEn+t@SPP~u^ayO+X|Lm?e@)9$o z7Cfo-YiqSX%X+38WOu0*A@j|LQwj){y2aY~O?l&S>f1~76~Br`bTPHJpDEh%#M@M^ zo6BXD;^N2aRDX*;Y1@BJny9tgUyoh_ws41+`u=;sZRqb|tuGn^Uq0JxV^O?*wbcr| zW7har>CIb@T}z||tjmB~rn)xJN>qKFn#}q*z;?O+1MEP!d|oPN<{gC}gfK(*>Q+^% z%k>@4E=ChYNBxhQt>UeGGzje%>DmKrnCBU1DVbw@h4>N0xn5}YA{U66vdbTlC5dqZ zd?CJ14GYPao#}QXy*X1Cv{{nv@!r|R^ARVb{PN`E*l7ocT)B^byO6XnpBCx59@kjl z7HlhZ*|zz14e>XJz>sO~GCOl}j1SU{+UD7MMUH=i%a;1|JvaQ85oP?U^EAt(DeJG@(u<)KK-GEJ`~p>%G(EUse#qWbVo2co5tk}lES_>0MZ zqTc$*(OR?z&2tn=2KYw<8Fryqse!!WHRy=~?tSjvX^ytzsS8)Ci1Nl@OP2K`-R20V*w&za6HK`-A^^RlMYga||;l(X;|{HYgvAt(fEI#2zJn3vb(Rz>fj%bPbs z!;_Eb5Ps1e|)phaW&rxR9tK8wn3;A>Ba36Dth6G9|uF zU32O|I^J_ISeH6`MTx#=Lf!@?48E|{P}dE!PoaErcA>thVyr2%L52C?ZBK=P0uL&v ziw`S^>Fr|jh8s-a0L9U4q~8I4H;2C{Y^a{Rqbcajm#l+93^-l_EK~1I8zxB=yTwF& z0KK$QT-5ho@qkIgm@IpWxGJh={!HD(WVZ|om_6Ifx@Q1#ae-;qb6h}Iru1GV+gPCb zXLl_;AzCzQdKL1#i4yq~pm(5vcIt^b33EaFe?l^%17^aSDFS2u>B}8EJSod>>jVF@ zyFCmbss(R*E417^uPu_^o({0dr>fLHLWqb_;!?2M1qRyE8*>sBg|9i8zmZ=ec&o_t zbaKZkE##CZLs^Ug&;ROk`QN^rpDv%OUHF_QTRnaCv{(L$?2e6L^j#DeyZO8Kds2%41&7nMz#IW{2ky4rM;wqRl)hH24s z(L^+j1fYgxKM-0VV!t6xh@q4&cM{lCNv-=EC@KbS(Im-46isJmIe(u5grT~zseJOE zcV&AV1$~~yei@E!WY|kZhxR6H_TKZJj!)2>GF z$Z*&08nzM+gcVL4rmxmMZjEQw7O)_p8`>hySz$N&Hy>v9`{c?q+Rp1x+tw|XQg`co z3xoxZ?kwE+a_QB5*Aliqr{%c=O62U?`nDmasIgZR!PgTX6VP^*3eR0K9yfH&J~oUZ zSzTs(T?cM4xCF5`2cLwfK|-D2sSI5Owh!oA?p zh8Vk?MmuE!`puOO|C*jF$t8c*ab{Ep(LC$-TTLGUNw&QPvwdv8=_}2x9!@!(_|{f@ z3)gSSr;>Ra{w-v(tRdT2pXRcG61U1cS6lKsK7KnTl3OAfNTTT-ZhKo$=1%f8N}Yco-Rl=g@}<$5>H~ z(C`Ul^(L~qAL`qamo? z`lO?%=)7RLf_q-ItF9>&zH1?WLG+k(`>71x;dIp8koYq8K%9FBoPzWnBvpjgfwOiT#Nkoi z6n`xn)WsJ40fgS99`1c4MkughH&0|scmh;+STPj6p>xKR;r!_5qwjZ;u5G9KL{O+b z`kr=Lz!$^tg;6YfwO2_jnC%rHbe*vPyDT!AtQc zdSUtZC0@*IIexBM_PBCS@^{h#hxDp4VBU4ZG%&tFp?9I+<_Qi8-Jw_fr}bv9rwFz$ zZi$Z2itI}Xt_=@6BA(k5&SPB@?zXIzsj}Wo{xfYxq(4r^t*>=UWGKfw zOZ$RtE>n$pWA@rDGg&-oFDc+XUA|9vD>-^M-q1eld3kBVh<`k#`M-~b4P*3PeYk@^ zX7H2Wy}y_{>$?BVgdtwAMLI{+Fb2MY9u%=Y*d(x>YuOheS>dgz%6kMj6I@%}`eV#aQ^RqUhM zX?tRgJ$!U=u2kaO-WVD)irUiVLwAjjlx;@9v=`FGtK#0_m5e+yYmrMmur~>DpJCq} zbbGwLjbBv-feum65A%pxcf%^$PCh>QmPgZnu$>J8(uF9^Ij~7-QFOPpRiW#yb-bdM z%v_cg$WiDyqD@;&(dO`}PN+0U>XTtKedS=Xo#_PK$N9R5KwcSF+bXMnt$SjBjH;aM zBDE_8Cf8jJ&_F$kK(0+HY41YJr{iKwIeT?v&oBJg8i?W8Jwmq~6-lYK%o!taBV$UH zs-}A7-DsME+X)4sg?Weyq4J&WL!#RrIt$FbRY@sMG~m0!KsCR4B8%KO7USI>*xXn! zLJFVQlGdqkUzUGeMnxpv<+#juH&U>cj|kmnaqUWl$xDXm9!CaqypM|^?bu?E1Hdc} zGSz8k)PlfH_8Q$H=%8K(eiCh?hbc(@5lR(1gKI7l;Xk(|s}1;UlgO$57z4?2AK9Se zZ=;vx6$+?!A(LnQkvn5pl>?cPS5GJ*)BNK zsP@X-T|QRo-a`#=?x=a=nu`CRoIDvFx-L2`Aw z{tQd|TvNyM?k9q|#BE~!<|(^L+ixCwOJ(v>7w8k*+-ED`eE#nJyrJl5>$6Iy!gu!= z`XYbdN4}_D=I3ik_5j~a5wRrx7yUQL5VF;K2w zR&Z-y$99wVfbVLjTxUabCc^fY{V>q600 z!|Fj>o+{;)B}s9{4quc%fDC660tiXr1C)$lFe7R+NWj`Ee6J12n@*Q&4^I^Fx)w>O zq&$f7KeB+5;AC?YF|7*q6^VZZ4qws6ghS4zswzNS0rjTaakN-0afsS|eQI%t*-R8G ztOnIK@Rmrg2J$#SHA&2A%-EmNQR!NkoDi)DF5eFED~YH$^}1>FR)_UU>3=Eu)+C0t0xcnE(%A$3m)A=Y z7bKn88-9UOK`_Hro%Q|8IY6Rm6Vio9-AQqC0{=`))hB(A!=MD=ED}|yD#F+S_S0c` z;kZR#%H{dT*kS50vm`X{%H-UIiEg7n0GnWDG!~Q82Z=WZ+h9;E11!TWBEgWvw&$RJ z$^E0+<|Z_Z4e9VSjKawZ2$dQ7(E4oNQ3+rW(?0M>4aQaDEJ{+8K3u%- z%MjCl8NLEN5+X4$Lb6T?r~N<;GO{We4(^;xxxZK<8t(=2xR`S&3&kdRwK7tI!}?9B zhLKgxjbeFcVV)jId}0+i){f4R^$b!nn9t4>d*l3E^Xq;*P3igfmtL*DZi)fZi^y6? za9FYACWbLVCuVu=E9|w+6V|8B3)3h>t|&syDe%#3SPW&Ah!H>!A5;DZ)-bj55Ca)N zG&xYvgea6uhop#{j{H>$|5kIy16iSiXK2lX3x)U|twUhCptsmgnKZwZ{3(Sa07zmuBsvMuXr4f zz`pEb_*J!n5R3%4{WKF5d~$kej(boZuP5{IR?uHC4a!3ey3zj5R*qtblqraOWal&g zkD(i)XcK}lP84qv=6VG<<02vLVk_;l)w~ieLS=+?)ldv6YGa0?-cc!DCU8iQk@cMY z_~ZInN>nDdeUPT?r3MJTMoBQY*%_XA zK-PT#T##?#yf*p8M25C`L*5}kqq;scLP!Y;fhNuf7o5*;SP|M7bY2zYvs#^x_rNQI z)M=|ZWAbSs{)9{>HIQ5k;x0kp+lV$xb4tK@|IKhkjC|Z`%L^1eq78x5gVN3DoJ6k7 z-RRCg%s$sf)>_KBR0)bw62tz2PLf0(nI(`Y!3*D6h?$E4Mroy@_G5K<$fO`b*0z1S z7Tz$(;f$(>_G%3~RmVQJ{6)%glO4Ps8ulExLeXFr*5xdH0?5YLnqRJu<~z9w3-l%! z^a(;HSKe4SOj=N(q0&t@S+v2wC67D1SuA~1`E+O$kq{yo7<3FvOz)RR;$@7HMi8;d zA4ey^c3GaNN{5E`d+|s(Pj!K1yjWe-;5&y}8I}_C-5cfk{^&Qp&CSo~E$P<^S6cxw ztok#*eZ>JJD$Bx0XUrqAj6!(V9v`3VJ^o}{@~xt$)CqOYdh&`B4uJ?{Ec!z^E)()P zx1DsehgD>w1anao4YxzI~1&77wPNKl*gF3NjyKwq-3WCcSQ zp@;dN*Zw)(G$yw9CloYmBnqRbq7B>awlRcm%vG#e{#hDbTxvL-lqtXf&XEcHMRJxo zP&mZM#WDfDB4F{XRNwqZr`-+&1TafYGoPe3Ny^YjX&D%l%iHQc)b@QQjp64iyu7sI z^&?z9onxTs4Sk1q&M=PVgd_qfC|O`-OH_Vy@b&E}?&B{Cji5(F1*SK|qoOx4Rp$j@ z0?Nj9O3UR+qq`GBk1($gO+qmkM7SHM1idqXcT`X=g8ERffG+7?`(X%Fi&F&z9E66) z+`3#xnSg|4{iG!};|-GtsX@Gf?G3ZeZ6=(!+FEZ)%K7cgr#e8MDnwOq(Dj>ypf#?{ zb*G7t1Q8KiD*W1uEOmNbhZ_`eQs8(|&8#>}IRdj^FJZk)v#Od^_baV^uX6 zSJsPMX>Wa|@nE*KwCBIa5w7(3Y#u`=g%L)7@_qc2x8D~ew8VMQgS|ey`Q~I1B0w}J z3^Cbt2`m3hIhIi&?17+Y6iEn$(FhDpLu1MdEuwk=XigYn344+*0ex~l6T%q7z;lI& z2zbp=V~h_k6{CIN|2QnIOGN%Z4q>|aNXW1a@xu)0tG{unz%+-9(abGWjC23vlx{0U z!s0LVidW3MCDUo+g*wh5bhKRc4y!B*7Ma5>Da$-Z#t9lPN1gS%CBj7=iZTYYR&Z!+3Ri zT3+ifBqh5}4TcbAc*Go{q(YkL7DNX3gjI=O;{xSE%ew3F<)swNe{sr!`r5Zol{$b@ zT4NtraK!F=EmH)SO~syN1d-ga_q~9~WCGeqG@u|NdZ_?vLJgXkBKvRtjP=*Y>e7m@ zjT@l5uh3+ITnsyEWO zNE$z;Wf!2nfFG2Jv?JBIfe>bdO!e=Zcd`#qsYQk2KMNH6w-mj9Q7&y@u$A}BEEpYZWC@S%Lb0J9FYqE zu)axzGGyK*U)#Y`;ZBq>MQ5RAVMUOtA9mm%!@Kq9r@>h`to@4*2%zFbB#uV>CE4eM z+pvb~VmVc`3@fy)z{mn7U4ZLp1zwGq38sKGNh+=l^EZ@g!5+;rNc1H?72`Ee}Xt&vS)l@M#=cR|p0IQu~oES0JfzeHY{A>Bug`cXyLaJY&*KJrK zu$&M(%4r&!PEe7i3s2ijM7UTzq!ue5rD3@=PB>j3Jk{+vJ?OG zYCEQFx~uaFnmg!xf91#B6nn#Z<$JyH`{Q{f?oY3wh16Q^^-L5k zK{RV?a-tI!9yh^C2HFS>@TSOE`YUk{ET*L7D&4wz-q-W$>eh~X`y=Q|&(}h;{L5K0 zxUZgf*NMfsiw|d@-bZm7?gtj`TUv44=lYIb zSN~^WE$>>}*7=8TPuJd#_ziq*-o)-s{maE_?nlK|{oQVh0S(Y!rs<_3)s#vyxFP1v zB&aHFS4!r05F9^Tu+E2Af3r^F!r=25%Lt6yKE{1KY53|-mC?sFxYZyIIqLiUF=`(4(hHgZWyNJ&^X{A z><*svD!qfIl$UxwFx^oQ?y`)VH!)(t?u)$@V*Yg13J}gK0g-TV=+DTz`3QX`fAyxIJ!E93AlH|`y+{9k7@jjz=8N3G^P*|n$oLve8f zzSpVI#O8S_-6(Qj1|n>WtW2qspXwjZfr*?|()jAkb&J&DG5|DXxtUuAVPR*EH%go|;B6zdaSI>7p{dHq zj=L%cxNIYSn9AUGAvLIaD;QKb;5z>D?+;Z8GC?-vi(2izvpPh-9ob7AB6Oa7#Jm6v z`X}IAERx)Npc4Ri)orr82l$+J;fh|ZCOH9v9HWOx>Llja7xsvOIr z(d+cPlyTfB`A>C|9qLo0mV_EyKI7I9AkFkzQ7w=`>;Ltb;&CGZSiRX46fAB`$4#~C zK&v4$ifBH`7S;Ga9(&^Z-lu*0F(+%pdL6B)4gtV2^Rzr%cp0AU%U_%j+(Eu6Z%LrZ z%0Z!SZ@AA+j(yq=7BW@U$C&jKXlhzbjTIiVxJ-W<@{g>JWp0?W`Z1E9X+sLSI7fIP zY=(i_M>0JXp8os3XVqj?n!Lr7P@3>5??3FvX;ju4LBCvJpF1F2TlXm#I9@UK=c8ow zm8iORU>=>cBP)7_9=7RucD3YNJZ(YR{CsKsd+yJ*bTFERpy`syUjIq#8D>Y1E(cXF zuH7Ah_=|1N+-eD3i`iH0mh=x!Wno?9ILTPxN%LguK2diME=}St8%fH3S}UP> z4Vkq03ZJ;Jzczp4-o!5`3D z>+@O}NMvR;eI71S7dT!vrVHWUF}}koUhLEk4I6Rq!58#`L5VIdJyxoyjQGD3W2@dT z9+xLT;jOISZ?KX)l^!i)vbOp+m|wa1xo&)1WGryE+<1%vhBrc@R6VnR2SMkDozSJx zOB~ZMN`u#w$waVkDKTb)@3aGfbWu?0-Vs2d9TeG2bOLCe|NlqrBXZvfdL)M|a}xvDl(pH{Yyg^^I>h8NfmGeEyh3A~@-swTM$drPRM2My&XtL&$4ebby#G85 z_f-HhkP;(>85+L)(N`xCC9P8$_ZbV^EU(5!iHs9HaQk>j4&W@Gm8Gsy_d+r&`3}uv zZ5sQP+%GkI=%7P3X!Pc2l8zLeKJ0~f`0Zu)NSI&!)EsauOapeD?<`95la`gF_Xp|# zyl6(Z@yR`g%Pfhs$Bm)1UPR$VtH$JCZc$)}2qFy9AV7v(}+lvn0u8{_n((mi5RH zpVprmhU&)OKd5%j<3@SRo1=iIo6VoOh?op=$(J_q`adZ&?qb)zPBc}G>+TCbQ?omh zQ+bw5vn;&4dP?PTnqtQ7U`ikqN`$syMq}FZf9!c@JYkd0N+iz$NYBOe!vrFB#!F~g z5F6;YGJHr+1W9+ObNK&XkB=7+yaX2%JW`bddz$y*ws2U^j-E4qyS>~o?Sn2b%!IMD zK%0Ns58lMMRxyJyQ4~LMEhvv-9fFTm6vCUw9;_R$Up|{HMn|RC$w=%n*pl1M+pg_k z1y|NUax}&(ku%A9w58$+y#d%ZJ?;z=Ij+|4z|91&^p}37NqB$YuQ_`R2uG>$c|Fw7 z>z!JyixhvFqvq^cPxNt#3pjE2TQh%W(f)Gs`GsL8OEe_wiJt8Tfu^5Rv(o{Q$Xa|k(SbVk7-+n+?Y$d4b1W|npUgR#Z~L~ zyQ99gIK=SuyMpr0oZ&X)2?!gPFeYoxOq{LD_gk*(CMCS`T%0U<=VZLzf(kD+N#Fft zgLY#rFQnDeGwX}6)$FWvg&DC4e$NaWT2%LqON3YEP}3}FGimvm<>tkLV%>uN2r`*# z5)`@B&4tgo$=h*NvV&V)PP1lZd1n3 zh*_UO>n`S)ALYQh-2iIvwHdz`dtm*_s{3VaogQs#4B>6FZqVPbcW^f-F#pdE*8YTQ zT<=5qkMGZjoWq}#y6i>`CWArkpL9HzYlH9bAlU0zy%AqWKlgXe=#5bEv)v^?m>lP` zz{F&0?srndmu*)+acnF0&%^g|#9O>gkiTHYz#DChlQcwo@E4A{LL*-UpRvMBAmfrr zDs-w(xItmUHb5;K>nCCh%F8oTjWWXt-eVVUqdnIY9|CJ8g1IDR36}UJj)>q1& ze<<%xl>*&fj!Wq7(VBNipl`MlnSKxgdo!GT4px8Yo3D*XSGhHh&t1Of3FL+BOy7u& zQI$(6^I#-TbI*M!eBUoi@=QYk?*KPmx+Ch~bO6Dz*O! z_xOWprwHNa;D9mjdM^JDv1(k6mp6|tX|`h@&a?wE_xcH4sD0yFCnr!e(K9s z#`XNd<7tnF`fW9y#=nQ*^&BkyPJ(~z(xW#mkmXf3kLUWdY8wb0f?QsoZExg;F2HC1 zhNFNFggGX)|Dpav%?*t!nUb=q(RpY&pc}a@j3IECIih`|mxN0e;ba8u*D_wK!Vld#i^m2C3==C#m-W-;G|#rm?J`eZ>IsPEUdR$$co_Kwu_jx;7SOb{P(!>yNlbJm=V= zTH?CA{O}!)MVaV>z#O_A#_bHLN+fQgaNb31|@kZH+w(gi ze9Zkkq4$`)_VyC+vw!)l9S>{Y)4-C^bG5>ciH&pmgw9tP zy}AjUmLnH>IZ2?5EobZi5sq7_#dF$yyM{lRNf}UD@O_Td<6~u`^E7FB{{^JjQ3ncr z)<=Fvap@o9KSlE!2Aqs;5c45-Z;V4CgsTmwUqqqktB>zgxa-vTyS3IFDGils3BP+S zFGYnT!}}x^jxsK7JwSnHLwhDDp-LlZlOrsJY$B#^03&TmN`N7LNHe8zEML8~WrnWB zz#0^85{{=nf~s*>og%r~e_p=L#2Vgl)tG{wcipE%(6FT*$EsIII+zAFXU>XCoKOxC zEy8RvR)cyw@}RkRc^?bx@39&`#AA34g;fXN-UI(Ar($3v#PB(kL+gD4N1(KiS}=ffPhp;st|6l;6B{x1jN5 zWsWO18$td$9A%47ve=kIu`HB}GC{yu>}qL4)760?GXO9tHoic?Co?nxT#zOKY^C{P z&feGFRUdc$rbs&H)g>w&N3os|>}j{PxzZM^!$@W3S`6UB+__&Veyk~6+$Lu+5o5ZsZB$N9kBe5&Tcy_?@{p-o9_AoK9KNZDEaM~N_%wGkk>|gF~(bSRBADJE| z9xXl4vfU28U`z>CNe=ORg~SN2I(4@8r?@Y=4VIBS)~n^Pbs%#8pk-+M8}|P+wNJCQgU?V*W$zbxk4YV ztu61U4e*?Fxm>@8g-7$po3X9jC!u9Q|vtu(mX92aZ)UY*Xa>tVy8LLfsw1!Iyg=^-cJi~2J{OrvTP zg4vw^wZ)kn3Y}-mICBXtyRV~#M)@ojvu6;P^tqG-M>#1Lv!@XXYV{)1j>bw=>i2 zier?jmX-spC@pA`vLaV2geqmlSy7CKOqjuKsDzw>wyFy~i^F*plKU1!93lksxQq!+ zR7g3J$cyG{WS!)j2}Z(SjL5(rkgS226wpOFl`0YSEREz5aUdk&@h};k;hq#ui!qa& z+(_k{FE_^sgb6dG=Vq@3Nem@};?;SL8mJOdlZKKEZVj7ti6@5uf}JiA>2sCqSPN?R zOu9e3U6oR^ia-WkWkLVJ)N2?8%Y^7HL|ALz}>tY)Qzb!&~ zf#K6QyQGmg-hAPCX)9fA`lxE9WC6epAum)Wjfid#RKO&b9FQbqdyyf(FOL5*?jIs< zrl_VRIB4S7YoZXvp~^}&T~J4V_9Q_dqq+w$>pNafiXswjiGnKn(&^6g!Hu)};hZ=S zE$=1)o6E@HdM4H!LThZX8MW+^2~fXQ$T^H3dloKS9#T<$r?`kO`(iD-uGz+=*X~mUcb*+$m#Snn;l`1f9B^= zJI7&`+iP`B9iG+;9kAuN5CWlzft9T6`0iNAYCC00EL!V$srBfp4bk=Lng5H~_xlSa z(o;`hkf<^T7uAyCzAzAD04wL;Y0bQKc>C~}jtE;hGopmfs`g9RJ%p>1*cMY)cY>8h z^5khZMFc8-^O(Cw4Hc%T@QF6}-(ow-Ud}`Ru?KV*0U^oz(abZFE{Ymrr0EN3I_P!O zpT9>%a!Nuf2?a3%+z1In0hVRu_rF)F`9Kf&3?Jw9kFH@feasT>GABrz9VD{nppslR zWk}y*-L{f7mK=+xYhjMWUurZ&igGlo=%#+89h*+MLx{ zMh|^S+wtmO$2OSQtfAB*84?_qpj8gc_s$COU-{~c;)40|nHvy6h8jhn;%p;c(G0L7 zA`!f%co}@2r0n{=(yBQfk#H0YpjhcDN5V&=Najp1s*AYh zWXAd0NeG7`_H%;uvTT;$^&=n>(tS*`bZHHCQ3D(c46H#|i~7=0jba+ZI+wp(hvIaz zS1Nx_N|KzktF-L&+ZKda<*zT3%W8Lcce)GzHW_A}2PNGTJp1tLw#jI)5=~U0bAgjt z_~cwC4VA-b%(`&Nwtb2Qf+aXASa9W#Y5KBGK_>t&v*gGj)Ac~63Y!G5%9<~LOw=+K zB4`>kFHo`w-e3GXjYLMN%Rh6?^nLT{3>yWp&YUiVWY>p(6-m|LbRm+Red7x27`3_x zod$-?L@xy=_p7^Rd=H8HdR>{NM6%(!f5p2^hjVUQ5`rP>6EBX{1~PTyQUO@cX)3~8vKBj=5_VS6deE9|HufS$tsQ$Qgl!su2el?fy0Ok+?Upfl7KUSBHxE#IuvEOaK*!=N^aps-D#gG zvx<$7KF&NIM%u<_LPJ93WE7FLg}O9~y~k2ZR74zHrexJgc80*7Q5^~?X_~_`&jT<{ zF@4U_=tE;@02&jppxEQf`DS78w7y+fC4;`0;gS2=f`!e|SllHLuRK>z@^TvYB*BW9x^!A?r}A!UT;8i?PR* z#iki@80)Ow+TAS(S4>>mgl)DVFYPHV|7)L^#|wJmg^4jpK{{IEg9+{(fhs@Fh?OYz z(CGgBjn?`a2;Y^2jCf$w2AQyoXcWKh2M)lC6*j%_^V)Y>8qhqlTZox40u`j6R1Zdy z&>NeMSHi0lTkskfRa6M54V+MwTTBG8g+33yz{49k6A&u_bXAM%G4fDpZ|Yu_97dPJ;fE#rbY zrV5(}nw`JiH#o@6OtT=eFAJn=8kX#HCh%R8(AirgTkHr@r|+~VbcbY|qssF3pc5M( zY#QFym1&@caqnc3t}fOuDTN&i@BW}=N`JSl4Jp`>zbpKNNWL<+$~P$=TlF3y|AnwX z-pDMSeSAJk+PbOp@zmRMVXpf6yTQXT1`|IGvJeU%N)3To7@tI0e`BlWCPG>Lh?_wXMn_m z?GUIWa5y*`E+{CBtEQu_!!BSu<{5Ir-ZBiqR}W|#o4C~<)&EMroKfKV^T{-l1+@Cx zs9U4;>Tmi;na*PEL!$53r~P7?-s|s!;SVmc4#rH)}N$*HwwJmJ&!z1SIl!gK^0-JH?sS;kyhKxQRRrg^3) zhopqM+G}?9DULLwlf?hay$#Ir9rgy-yP)^(egK|sWgpGk)F@y|A)#?eeM-r}2=aw( zRzxMQK*g>|f?T+>-X&u(R!lrz{w(6>TD=AUQ>Dufki+GIm$%~}8xS`%fPTakB|}w4 zpM=q6rgC|IC<2F_E>y*q&Jf^3Qm$%U_w*Vd51Kaw`@lpkyYBDn~7BO-gtamtcqD!71yqH)MEmii0{a>cz{BV%;$$ zm&KN$db@ww(oisIfM$JJehMlKPbZ_mi{|OWt5}_CGP=w?TQ`0b|Fa%iymA&7<5o@>f8ha6p~JioQVJ8dW0NfLzQ;L0b4h&QrRVvMZ#4I8BtK z8|(66xC6$Ixa^?iMCU3bYy1b3HUj!`1L-$=ZhPCNhhwHU-2+ZG zIm$3=40TyRk7E!ft165Y8*}jzeV^V1yObBgJ<<6DsMXIc^-5{7!?-!kfFc zQ8JqIlF&msxcOXS|L+RlU-92*kiuY<%;e!Kry3jNi|SC2i0dj!Sv*sIwFPwL0UYaa zJy0^D(5bjuq;zwa2cC4n#L-q0#}Y*M+c;N;wzJIN^g znpX6IL(Uo^!!TMXks^8~DkX*swwu*85Wccu4_URk;%&jzz~tvQrN$UuH^pih9u*Oii@VVZ3GP2&`E2> zcUz(gR2z=X2ia8BK$e@ub89$EmBoIhVAH5kiDzg0FB_QFm<68scA#?Xa0s za#>^S`6LJ0HRyQRM>SyfV$xB|jDbgUY$~^)u#8S>cv(d5r(pp&>07k|dhRWaY=r!I z71EXG#E+C3WUI52&Hs51q@O3wmt1#*-exjh)FcbYE$CHeIXyK1J>=LY43S!5p*|%4 z3B1&fd7jDWWv>>;Z>en3rL!eXie*1*1)L#F}~PcQOvD^2 z>emq8pBJ@1Z{wNiKOcOX6$J6F<~e3-2T8YOvcIfn6?L2FUR|@^S^ zK!w5}@%H|KcvYR$VK1=d4^Lk63IdfzK+_{zm#R=P@frr2v6z`#I!$xrC!K5v{)j$L z&9#fumjK<`r_1V%S;K*&{K}TFFPU)SiG904texY0i9_RE-;wZma3N8fK~zbexNBsB zA8Ub8<^sj()$w+2urQpNCfI@{kk$fey#Vm1E70mDpbN&jsq$7tLC1JU`v^M9pvx%dRZtv*ogb9)=L+?l3!`6|hQEPKI8EiSP$=J&Dn0H6 zMrz`@b>(U}Pf81FJyTzkIu;+ioYW+<*E~sAZYPw8aM7;3$Pzb&DMg88gcyd`C_gNw zV7&L2*2ge>daVCBzP;h6O?=b#J8*adic)y8;#YrQ2z^d43w}OA%^@#IKkIhPqlk!l z;LQ2Ij<-)Xrm=(tgaBB{fLRJ{F|{~rZ-Cy%0_rpq4Mv&HMVp+d0;=ES#Za;;oNMC(+R`gy} z3MS*Sypa9eXj5q5RuX}`qa#Ox)tqd9(dv{cQi38C^2!h38ru7aT)M+5*^^ZIP=jwr z^YK47hl)D9<@k~KryNqeY^0Xn0s9kdJ?C7ntcHR43A-GU~T-|0>8U0o;>} za9Wx(3leHO2(Y|VhK2s+FKK0!LQOpU9c%`wTekKv%)K!q*PVv9;Wm1F$|^zTlsHyD z?a|&`2oO{2ZW+e|=yg7ShN;Y(frAIAIs;VW_Wxq;<57pcEGlqUh9W_P{Y^+BRQ_`X z?f%&l_xn#y=Lx2|2NR`TMCA+1z*ncrWb(Pn8Mxl;IgeJVj&XH~@EsSMF65C1`7m=i zmihqm>w4OgQtZALTv^N)z;Hq7_hRu|8?}yu`b?j~5T;h$A<^Sn;#(re^rW|#+l?1R z%;`nb%7~^cW0Q|(s?3a2jv*#;Awg)SIkohLWNoU?&UeX>;dieEMLac;-w%!n9ngb2+-)yRi2@}0lNV95~* zJ4NyRT~)(-;>U=h?4M@sxA=M?%%_30@6kwUYJFb&-X?h7?{q)-cn;HH(xQS98H*(A z#l7Sc{v^m$^%Sut3uQ4O zmu1`!2HGECM1`qvj%F?9$_->6-I1N*O{6MsqWP{j>8>latUH6BO(TkRiG@TkYdzhw zqIka>PObZZIv!s9QC0kchb-tk;z!LTHU8$q=lB4gAP^m zrf=<)vX&M+Z?@-^G8%f~7=Z~9S{?7L;+QxDi7Stu=f8!rKRlPH+#Co8 zvi}53lZ^J+1g+uR@lAC3_m!#T{F4&@`p7yIa=F|b4hV=eya>%$xOpCxyDYa0+BM1e z;GTPeeTVX%wJcMaxYA+OhONBj^K*!NKdX*N_!hfCxZN?q(yRdG(ujFUtGRttThA$y zA}G&v3#bAn9c3nar>8^g=i6w3R7qU>Y?$j{Nn9Rq|6bgj^;2thFAT+P{^HJrhK_z- z#H)?C%8X!)1Y6N4t-uLfVn`C1rd~YT!lA~M2BWsuYhv{qS~6?R+-(8tt0t=NW-xGH zucl`_zs*A@LJ5J}s^56-2I>lawKIpMYx2(E8&jE|06nI^#X4-W1svuv;MKWuL}If6 z)uPW4;YJ>v?+=tc0sKAhRymIH?tk|~B_53LBVvw?G$@U2jk@C7K|+@!SE%>$3&E! zADZf|_#q*C6gegg>+_dSp+m!3M5Pgu-qG}jKsF3bBHQ{T=l6quH#>yc>yoS631X^u z4gCeAf^+`&e!EzimwQbH6(YtM2CEe~BP3Q8mVtFWH>2WRLNK|J1QxdJnHkh?x-qjL zOY**k1?$)(#BP{{MI8{;>%LD4CNmAwBzOs+I-K zcxLPae+gXQ{V|Ij6A-#pfPK;i2S;*ci_$jT(I9y1HHxg~v^fO6Y$6yJ;z9>nL>+RX zZO_4*#4+)aE8yiu>?HiaAlrZdHiR`K9F-CE(Ryk?s{*%>Jyqk$2ayk`c+NGYd7-^V`LWgF zU?ote&zkv*9|wJ_-w9o zJXy1TnD2LoScgIhQeD^hJof0}#_Q_CRFpX|57mM)(>&jngKqd#?x(JL! zd}^`?bUlZ1I5KFBTL{RYw{L##FO!9)qsp{@HaW-^*=Ipn<#pFA48vk<2*WH^w7M8B0dINHo7mo(+0?l5!|P7lQ7 zb+%&6*JPu{nN2w1&iPgf7Xr~Rl!+{dnE{1}Foxm86Gs`Pmmw%-$+UK@36HXu= z)0&`S#{}1*>Grz~eiS)S7kYO=o+Yt4>9CaEuQeoU)#NM4&VvWp5>a#;>PoTmzbc#N ztnJ9>RC)hzFWgyDwf=8?*T~?L3-0@{bXCRDoEn#=;U#4Az&+3Q71MXd^ zhjg#-r!YEyrvh7h|NJ015AVs% zz6Y(N1yJm)9XpnX`|kwPT7f{pkYCZ%Ybo-D&{SkK(3eBHqFtUFUahgb*z^uDKRqvp}Vr~Gs zYc`sz3@MZHe`Zi6kyHJmO7yWlckuA~4xBdpSw>HueTyY3B}#|K3jg)B79aWfQN0>J zHlFt}xf*yxsv&}V3Yhumjd?0_RmX}ZS4~@@0z)a@hKz|^x$#Iu7pm;K*M1Ujk zmawLp4U<)qb`5ewm^rHpaq?xq>!smxT|{nB6k9|4huBcg_UdK7QWk3Ao}~TclmR=m zi!5IQh55+5sc2TQU4B0OOasU1_-C@)HoZnZ6z8Y(&R+fb-iK<ZPLllb5A2?8c-k;dTHgwM_GmCya_QH6RH!)zPE zZ41F?s;nANicR=GgO7*;;=#*b-!2R7ps)W)(PWQL4Nr1Cxwy96wYM*8%tb%GiF)DQ zu7;H~iTHZ)gn;U3r20lkGT)xp9zpZR<$k|l__4r1tG**oCX!~5Y;X0bIu99$Giv@W z0e%rRJeHr-ZcXo5%A#et$#2gC|_jEA6aj2>)Czp zd+pe1g*%C974p1%UE&igE@~*5Aq2TR1Iq_l1&mijyy`Db3mCuGD=@xG_wRi9Zyo-n z>G8Ub3bGjXx4W!|A>)iv-_*m8RB%AWPR zYP(bBhyyTKzcwCS)RTOUNV;7nGviO&<@+Yly3yr_P;zhL>tSW9lkN$v?$z^^Y4h#Y z5_4Cw@kLs}1(M5doRA_toO9scCF}yP=_Y(qdikS4FC2Q%fK`pbB??q`*u~;%fY;{} z%UtaJVN?$Ie`J zl^HjaUmf)l?nIbBUSFKw@bpm6PC_DZZ#=9^9~co#dc0E zMT_|s7f4%K>NKv|ydsqy;L@X^3j!orUBvE^zD@IV8TZIPh;r48^wR`H1jKvN^5$R$ z6>SRIbOat2400#`n(fi@!Z5h_)`CK?gEXV*S8RQJVK(E_^a^_VHelM+x&DkeeZAtp zl4W5Jjg!HeG4dq9_$&fSzWqM$ORl}V{i5%_=wyC}Zynq@tUM{;1D`9zC{c6WEAx_y z0^aX+GQah9$QV6Rx839R^)bgPnd*}0+A+C3+uD%xh{L*-eC>PH2U0i*EO~rCvU;7= zUJ4kFVOD1~+HqCABfO{!_C=4m8W;vXE#$6sg0`=47v(S8NzJ9)HwSlilHLPfzinC9 zGXuj4ro#e_MO|c1r?wS#SJmKi4FaD&``&6xTJ_!e1%hRIRbee8!Tl~bi)i2d+I?%Y z3nl~mXP*}tqi;azI$?~+D`2DH4*I77Lwon{AML1;3W;B!xnow4U9HICM%1hqpQ#DP zP@RC}8zEoq56@5o&Zoc)A?c_0*9~v6X4)g5hDB2053- zN0e6bN|?&=TMVcV2&|G3n?W*>{S5F-n`?zR^_H#b)8F;u8DbdzaPK z+{GZ^A{)c$414cx;<~k#vvoyU1%U6Bai!j!_o!j12`17S7|dQid26^1>Fs>E@H2Sk z|H2!_Y)((i*2}wEQsC8|e_p_u1?$5T-y_Ri3vAQE_fS>}|27K&IJ`0pnNDZi$*yFj zEX+V?L%Q^NeS6$2yEe#Q^b5R)@=={LwNz66(hn6MAi;9i)x}iaZg>A%S0_kw(o@fr z6UKTHRwwcwp2-P2KZ~qG{11=i4lJfh=t5z-Z|j~Z53oN=^h@4hBe{ZZwPO=PP54QE5%k}9NR*~uu$ptG=1SZ?UU^9H@c$2A75^O=#K_9O z8_{5v<;&MYVYCbJ5n$y)*=8_(RG`lWFeUS9=_fdg4il#Vpb~i-9mNO0m;aloG*Ctq z5gvBwzcJ~20)FFT;QY-WoXJuIPA_B(CW%|VC`|n3);OID5q~%l(Mu)n))y3ef!vOb zlKyy=k zedsh+gb#qJp_D56>gs{_%-Nn zRI68M+@qEI3$HEH=@AK%ydxs(tIp_gT^lr!cIf)kvI_R|oXfsk;$z~c!S}4}S}3au z>L?Ul!K%*Pf2N+;)E0FbYOIeUz7y-mbNeTNPvHO_Z6=YJ!T1tcb$LE@R6KH*{&pB} zs4$Mv+JU%)-{VclRjEzm3V`^~ z$vaKd2St;6>32uUQ%rVI=YRtd> z!w=V#MquW1qD$OUd87b*veERNXOY##ik|J>Wl@Jb9i96VWTTmmuBP{$9oitX;EbRA zo%X=j`JyN$eMUgV_bTOs-xY+Z(^0X4K9=UDpE$sc7eX+G1oM+q&v9@?MBVs-6ExGk z7J1*YEtKv{cW;lp1JllngS0bNFxrirONXzlFoBunW8vQL<`O zxd^B4vnwgNs;Vc#KECf^N1A4hI875gd{&}(IzsWA9$P0rxR7F*+wlj1Jeji^uM8&* zQcNluNqjXWvh1b;eLO^XnvP5ne2E5u z{TZ#^L0yY?pB|r1t?XI|$5Pw@@1Lius+TUW94M*9*zC>O)W**D&g*01?so(Y+n>{KlS9`)`sw7ybf;`0b;o3Dj=}g4mg;sZ@7B)+5J?5)V?Dje ziZ&G3;->Crm30LJsUo0x^kQj}i6GTLRMv2WbYH~}c^VG-;iiExeNoL;XnUs73GJ9nW zNjK>kcXt*l@^`Q8XEOTV&=$zACpIl|lSZOy9SnGO4XtUL5S2a6VF1=Yuwcw1zQ zkW+%&f%P~0VaAC2hPgchA0&bgAaB|1jN4lOX(2|X?hP4${RJfW6)F0P}!+rqt++B1|mhmRz+OhLHx}uwsKh>TOoHM}uw76SATF@hZsQQOBE zPE~{rxQ4@so%FbZiy4t5*65jPUnb~cjJHG!!K^6-F} z0b3}3QMj7?_~_hE^6{P0?(8`pv$S^I6HM&AsgK6eV;JRJkTWe0A?eG`Ktrl=ZI=)G z9Qx*ZY&X?yiv@v(lrDkE#!M&Ss_-SwX77w6^`#IsU#`sx?2e+h#VGDdqa#HfAn|=s zol%};exvk?Cd1U-Z%?yj83G)$$2f8%xN5bldG77`mf>z>lVP37k%k8 zZgQi1(VP%$)c+2eqLx$Q6OV~Vs!$@X&Gij}%}(Py8aKv}AK=EKM5fUSTpM6p)_G3v z*2VW*Bl*yQ9-XS07X0f4M?LWGY8{$vgnhIN&C!ob#+VkSmJ;o7c$%r1;`tfeMdMEs zT4{meV@j#3lJe3AR7VZ#sV8%4^7;%i%3gFi$5jmvv1`Bbu=wrp6Sh#*&p2ZXV|A(dQcF0 zrQD|-laeEjlz2Gh!o7jsB&mXh(!ZUa4UCmvDB-omnmyvjw;-ZT+X8QYEJv*MEUPh4 zmgk{iXmxifR|aK?ZCaI2wd&+1=fq^5$SJ#9~y5h~3iRSb2Y|7;1`mx#fgi0wm@B=wFSJB5tmIuwTW04sdNYKl*=*9ep# z_{{Mu=1a=kt=94V#FpDGR>W~OzH%q?fN}FHWKTk!Gp#P%e}?Cd%P|cYx6K%`g|$8M zaf#}tW2~QtwvMC6uy#tJq!^yA!}Q&0o%-7!29ld;yg%|$dgxWzhpFBMj`&AzoU*$N z<4)zbzdFm3PaV`Q+BelIGHjGYEPe@nYSReP(vgmTy}8wtz{aQ>n-x-!PHk}#S#xO_)MF zeT2QHd*J(;C3?&2%=fX0?vLjh=2D9ohh~?BoH6(-x#WGfPq{8Z*_x(%PR?)4*N>P{ zU2M8_S!)G@M0~+yhJjOTE+zt^p?P&na|))GWLtDZ;A5mp`l>YAFgPFH$2- zZr5>C_5n#4nL8KLVs6$evKuxA1>91}wS`~oza#U{#_0vh!+(B=iA82}O4UFSa~XEpH|jTdNS?c1YVK9CRAbZxNu?6b6(NCx4Ci}TJ;C*H?nbSn7uH#Rum z->WtaPcw?Rd!*CZm(^&1XresyY6Wno^ykbJs@y&Xg%6ev07a?U@gaf(#BjNlT ztu$aIwZqeh2U5Ht?{sLwZ<0*Qm{@6)0yoOTcQe8>OU2mtAR=7PjpNy9DVtxKZ2U5~ z8jsp)jOSBO<&C%kMiL6Nc1u66foembmSTa9bZHjL==GJUY-4da1=U0VMO8yx{L`+` zZtI`@2qB|lCZbWGyXF0vraT!7mOap*J#An%SVEzN&qbH|gMd|X@9^M|;PvFIrP@_> zhMCYek?Z#vH#!e@(wdK2;p?fD_Q_&N(YsSCn~QZ3WOnSHgmjMxze8`IY2LCq53(VE zU~m87t`Z7X7!&LZ4BAd#4|AHxfM`=OesfHI(bPe-y3Om(V?QMYk8}%rOV@=@w0BQ( zMI}YlBGDDYNV3CvM-(O=5@R7<{&W_w_dy7Paj>J~A=+AoCPbYm;EsBn%xgD_+0cq> zLHP}L=Ep@+`<_*(w5DtVjL{pxGydDHv|?WWQ^UT> z%|B7^z+BDdPjvf}W&`@sKDbxFqNFs}@|b3csu|@A>)oI_=sESLm!neG#zgnFS0Z1Fs&F=@z>VWWy?QlwDE_mBAbi9px zKR81_YP#TMBFCWAYrj`0)#if*gQGuOo#!=c3kJR#w$~|<;k}&>=I^3_{6QcJjqRrcZfuM$sf}j1x&`G39ov$6iqqH;z^erPt=IeU8uakO= z{|-F%M*>b1+$7R<46Qn>#oGrsPHfnyr%_4E#e%4j8Zem<_deSMS@~ZBO|*HAJ<|p# zHVJR*3s*vGze~vhp=f@1;;u>eyc&p6G!gfqdRrb3!rs4fzlW}SPac)3kROkLCI%H)x}j>)03t`B~$T00X+ zW2SK(8w^YO>n+1Y$-}{8Ql7HR=cr#k+DcN`wqO}gv**lK;f~rZNAXDQ_;@CW5L$6biv_FAeYh|Gh~`6^E>e+ zeb4eTMz+y~TNCUYSxNzKtRT6DTV1v!8RYwVj?|fa_K$EHk<#>muJg+pPtJw za(X7gRdT3}>})s@r@vj)2D2atdY%3_LAdDsPteEzl-oEY|J~Ao8Ee)s*g;G%id434 zcc|w#5upWjQeo%f<6@V-g<=Mh`}i3C7nUuo?~3Fi(3)a`|VYW|&{lKiGKTT=b@Z zsn)UTqu1B%NjM*|`Y6dEhZrINaQ(ITaVB+qxp6$t`StWv{qe93^nKK2&!4^{JVw-WF8(R6mDW%wpDX&mTr(JLbK0F~2YG1JxWgbqP?EdvDx z+X(~>ld7aW7qhMx!e*|nX}f;XXNGM=x$&n&;o;+)Li;4Bc5;6}90f^>14JsTwyh4%0TKGtfLX*7yyY~l9j z61px_UzietFeKu@+O<*F)W(Fc)#w0;`nc1on?E{QD-m6sd~sjFpi26LyO`bivZQ`8 z*ON}KjL%4(^sf3jp;8cU;|hV07ed5a>=U7rCV<+4N23RrOOmwII3yE)XA`E-BjK#= zVVxS!yHw&Bz7a&MH{{3=z&;z+Iv_+8cMpVGKO&@{@6r_&ng2}})GGhh>|WRPwn16; z3aVRDl~CQq+bm*V8X;R=M9U58P?Rh9zEN%!t|Bxz)ztO?&>crsFW%a@^lMXErs|O* zQxQH(u4$>yh}UB}gzcs%naBQva7!GECpb9Ofq%Ttc?o%p$V8mmQ$u>;bZ|^0=2k00 z%m1@LJ&kh%dY%Lw0(4aXb(TxKY2uEaDP*O2%7qHXSa{r@l5&qJTgr0m<#W{eG;HWq zsa{gowx_g9AW9*=K$X}`$w~x*3i*vQ4s_7WWJ$e@&vm+CLsGNWjR>Ce56S&Ct>eer zT29-WEnN~EF85ChQt7nm6y_H+H(~pd#|!3n`fS=DUn=h>*I9wQ=D zNLhBW6NxC;>ql~}Hs$SnZxWbfGXm5Oq- zhw%1PN#=~tY&ry2S31nuDBdAcO z8&H@OG~&(SnIKtgLPls(Khxp2hY>FJ>7@qr6#c5wG5|5^Q!sy#=mJ3aN61ij_|Zd}$Vj@Ywyg4~{=m zn^QG4XS>P;z65vqs@^JnKc#rH>IndsqHid-Ps1PlJc2RGH!T19G~HhKb+^7{JDkEi zN+OVO-4GaK8GwJ9;(&n__&@aG67_yXbX%2`TxIyqR<|Gv(GkOD*UqKH9^v4(eYZLA z1(}fRR~G$7c#nPkwgCL`{&1+QR1PFRu&JR$f*90vpeCa0u;v8r4so1l3VC(!tY_v! zwLY#$p4XkBz1c4H3|;aPzeC3KVH@)Z=?xWhnPH)JnEs^YB7-Zn$7fKA4<1xB?6KT? z8(B7^+HWF}d|W2Sr!*kIN6H$ttw7)n+cW#StQ~WzoRKOqiKgzuBZ}vs=w#`6-7Ptm zVAcFn1%eEp0VNx2C1F>`D~ygXz)F*x%*@t*S;GHy&MD8f(BW*Pv?iAZz#x%qN*7*> zrnR&EPTafM4;2FlcF(XE6UB_+$L7}g4OGQ0+;X_a{@4P=Oz_Smq?+mHRp|~?3lBD+ zr;wMoJUOl*;71i*YCV#9KJWx7XR}ek!3fXy=;)83IMk=Tz-w%@A;rs)XIph6glPA9 zSg|T+>EzyT*7oBM-XAf$KSj%@(>@)c*m8 z#~SNRe2Qny6j$BlbOdvakQ2uf6UEbgJs^XyjnB5vG*LYYQwtmC!IlwS{QzoxfTl(i zyK8j~*RML4`0fhr3~d0`XagnH3W^3hf*&?m4*#1gNAA;I@F+S=fKg%3FLliC>+=~> zf$ig2PthGJw<3uEOo%r{vnVf&0^2fL2dvU@1Cny858?1o7K-YnwQ z7mrn62t?ZeA7P;Ia1@R0Y?1S|Iq4km6w&RBQwY_}iA5Pcnm}iUp_I zYF4vilLtHgdL%lm$*=rKL{V;~Kx|&l-t(oiwTFshvf#^2_a;~;(LvY~DD-*q}99 z;QQ(te6>F~UDT$2mH8UY&|rwYt=5Bp(RIhA0n$4TXjq2#w^lJU*jE8=seb>kW!p?X zNe!iCTfj7>(R)Rg4S=J!;-fRS^>Cg5hUG*#-ev89s;Q8>``wrHGRC4%Qom@am0_vG z%l@nu1;b`B_6aqltC3H{YL#}?8Ls+4SpHDEF5zrC5Kg0xuGB;K;GaQ?an-IvD$1D^ zmMw+;Do+co?^K^HV%|>lzzQ?p1-z{tb}uo5O4>1vi$e~$=Q1^pvtb)kOXf<{>W>O@ z`78IG%FA|*7}cE{k@PEs0MuOVs({KInSMxW#yqpieo^GiS3-1plx7Wq3)Y8ik%U5; zFtGiN+L*IfPUYyfrs+JPyoq60wiXKgk3uH; zJlpR-t2^5qQ$4YwaGK&aq&>EIr5yamDmul-%s*y?NwAk z^Il+4{u2|V+VyS(G-w*zK^tUFCyqiO@?@viQ;`SyO^~RkJ$W>wvqCiY)vu^)$SVb z<+-934iZgO;1;lB_aX5Lf>*&5Fx|s9a8Xb&Q){bdq0?t0NT}s`7NBZlf>68=V(i@B zSpo16t4izW;;_}RGUBjzf4fzEZ$FPE;ysG9$s^9re~PRra`Sz@^nTKdf7D?4v5*nn zR!(n7WNE9Xl#9*(m$kQD!bw@?mE!{8HV7Y&BSH4=lHV1X&U7W|#@CpAZo!8y5Tq9= z^w*+<8^1B^(WnA%XoqXJ)WaD*8Gd`&QlzmC0!k>6uI21ssC0+tRz@kSLf@Y5xgXcv zy4ooMnesioxGF}Lw!dAXI2Og&S$yFO&!}}AHM;3+bJj(n4>>n$?bqRvKf*%#joryk zeJIwk)*IOi48E&|CsP~7Rkrf~%-55lm37h5!)J}XM0@{AnIcp@xHF%HMy09<^>mj1 zaupc3(OX3L*E=TV(Wp%OMqc#5UOXVG$tb(>Q%!Sv}(8<`u?exl#5H z5n4Oyv%^kR8Y260G=pf%o*4Q_Q(93BlJqhTCU17UqDDux+R&?Do4Qe>XJjSK>$4Ao zb)A&cXOMil%G*ga^LR7&5X35iq1wVQpjes zVhz57)66Hpa1X|25Yb9fy%p2B$xPQ^>%mzF_pvi8NV zW;{K@-AIMcw^_$W&hOu*hIf&7!j@bmR|53(NiP*ti~^1cSVhakf8hw-m5f$9f>V^N zK2j&UOi7J90cTi6I!3CeZ*0ondTnZ$U;kMQ8d==%c7zA&Jv@w^h>+BpRflW5(n%ZOGN+7gPx`+DMU0&b+JA_> z&5E63PWn0ZJS!sVT}cI}zh!Wk^93+;sUatw=Biva@G;)%l5L-bUp_Mv-VJ=*ak{^^ zvGz^4wlz4Z^A+TdMhf1p{Q8-u13Y~m%+0^4uTfM6o1q4^D&JAZ_L#lZ3D(xVYtmz7 zK@cQ#GFz9Dvldfip5W$OUi&V9t4&!AaNz>P=Xor2W`-MgjKto7(DK|Ec4Hk1jqBaj ze5=F1vV~593q{MtATFC}545^aXJ>yIfJN8un;yBd z>^osWJ{HA7-BjGUk65T7nw&BQVL0gz^%bl=tUXU7-+?_|4@C9d)G~fauC>KPnN@owN+y~KdB^QUuHC~B?c9Wku> zferdQgw?sYz-=+tP9r>JV>{&A!(%p$#;;Oq7rV_rL&=`hWPrXmlIgcWp!WNMSC=Qn zGN1BxMt^D5svX5?%?Y~>XIgtSRmXN8Z%qcDd$VJ-Rb6&z&J1rKM`J_PmI*AnL;H6Y zh_>tJj|*MN;w0JNa}kD#TUC!7AZ=HNJ{)kXHaa5kk-iQmqZ6a49h2*Bj8P`_L84K0 zhcXQ^`Zk&O{n%di_fgaU2HF~`#WuDpVGNH9rz~>K$ib@h?P=m(qleIjPung>5@zS~ zjsM|91ByoYf^2mHvMQH_FPT8?x{S??2(FV12gl{0aEW^EI?I@)J4Rn$gGoxxLAoIc z=$YPxBypJ zPuJsU>_BX?7OkU-axoe#obE{o*fVNpATnF5RjM*>6tjWToln)j%O8&4P21@4cPZ7I z7ZU|^o7nJmsU6hm(J`K#7wvb>rf=N=oj12{p5ne<(KM_q9W5)^)?-sQ0q!FtUA`f_s9&4OxuLVTTz;vQ zJg@UGLf$#;o6HW!)TYw__-$`#Ta00Oe8J0mlna`^CjO~UC)6DwkJ}{E^~1Nz0H;b% ztDc;nuNrH!H05VTmdaU+oW$+eAojvIxxDI~FitfoAEU2N=boK4zHc}0sP_>6P+%BN~GwJ7SM&9EIWm@s97)xsft;j?GF! zuLNsay@j!j|v5l*Tf9k=OVy zc-ZogQ>jPD`ah{;zZ?yEVT@S?Lg;AQ%k#i#&kf5hsj&r2!gBt}vkj@zIuF9X>xmfW z8h?f#+`UNFI&X0;tc7mz3-3Sqdh~T`zVTrZ3hiUybde1}Xl?FK0-dBX=F{jGz|+Jb z&2T?{=6;pD#MZ?*99|-W!wUb5+D6RzNMtnQf|ch|JL=x zRu%W9ZYS?_*}Z#c&7I|}sE3638tRzEIB*ZdB6TwQZ1;!nM*4Z4^)*Q)pRAXvhS`}M z;oW-22Q5WADirXGo>qQG$@S*A`MC=3qV>63Z;x_yM`>X5tM@#$fNDN(TpYzP*NXv# zIdfi&lONe2z|URR=v}V*of7jFygj(&ar8&d`2v3Yex4lmwJ=^96j#VdAsx`X{@k7X zg876Gkd^kT4M-iwKTTUr%^pLk@jt)zozC}i`@J*s-wtt<>j`LXX#?Qdrx7*52?LsF zkVwJl8c>Q^ls|07G#7)*`)GShfc>>gLr;QCROFb7@Df)}&@Bs3rD7#C9}5^11At>> z9A38m<9~wYD0vEWQZ^oZJGejuP9)8Q8sZgx)dfZYLxc}V?vt*_tGrxl?5E zb{c##I8Jm+w7>-3qFY+NsR1iwDgF$V!$I@fN&^Wom!Vmuf)ml~1Smpfj20jsKYist z3*4x3v>wVJAQY|IJew@8*GxOCJ`(@JgsxC5Y>E?3J@EvB7D7}<7FZe%M8(-gDBzm> z!)8l(2?Z!Y<%A|8Zaf_jo(v5BiZ7!$sPA8bX;?NNN}yte;0M~4Y}nyXl)@AxKvI57 zp%H6=tD+8GDG|){;m61h=-cbqau?Vn*`Oof@atf~9@So)5u`!uiZEObsL8PiaDDKHpbg6|0OFFM(zpM1fVM=PjzHXGx)32EkrPr8A$$dH5>yZq!i8G4 z1!bS3`t51%%B@)L|M(~~HQX&^np+2(+||+5001Q~v`U)+aea)ZWI$6W{zrd0j1@!2 z#-S--G+~`sg%iuSz=XGlxy?=^jqaPt{y(QAPMHtoa!P<;g7!3K`asqE%eL!Rf2jLX z7@HSNXE#9F(L6TS%%bnfq3|A799X{6A?KP7GqeCMQc?k{?%Nh&KSYNuM>m z`tXHJ`Vjp2pfV0IRR|Z}#nHMYf;g@px8Vj!m<_>*m#Fc&pB~|D(m;;?@S~?}YM=6i z`$;gymard(1dL}uK!*jlTTxWnO#GzF8_eXNx~&REy%b46%5WlpAz5~eBI)|&JQpB! z6Qgg=X(yNY0IvN7?Eb$UNaXZ)a2VVdhXK`<&~!LN2OP1Tnzw7-c<;DmkB4Of{olI( zOgN862g6V~eYz;Z*Z5QBgo;)Ux5=#`UU-+z&`!+yUH;06`GF)ulT=7+pb#Z6fgh_& z^vpxIs~NP2-seBb}Bc`B!B+m?;;c$PlPXbgHMxhvH=a-PGUxY=+5&&)fOi z(E>c4iZ5w;vV4vx58xnWqS=|wIBY|3JkSv}{QDe*Gn6OB;f1bP94_Egyy9^=h9wFp zW>If$%9PKq%k7qm+=jLrS+h$Sz<9S>3mA`}okSi{^9 zpmyv!M=-3p<52GBs@$|5bHyP#zpY@Fs(xE8?3s^^VK)qOz#z;C<L zyVA#T5|2jR;ld%8lnlWDMnU?dA|+l?4`M-e4+Mt! zh3bu%_rv6uc0x^EBw3Tu4~clF<%TUkQWDIF@rrfm3e_~>*oehdlzZcJf7o1?mfvZ^ ze-SwO42>u*lbVuaL_aM3+Mucch%UZ=CMm7u(TH6T3{o_K?PW%OW;}I_wn7Gb#2=1$ zV<9G!=CiAxaLg1;9&))LD0Q#ZRTpzIWAw1bvt5AWA-q|3-U6M@+SickgD7sx;$MEmjO1 z`w`I1v0N<_jes)0L>{mP1wcM$79m1zty+%xN@(QmZy5lqi%AZsf z0w)q~vgw=-$wNf@4}fPbfbNP{=xH4$Lir&%bbCtAw*ulbfQX3wm?!`iz|$z?<#*rL zWSw_(U;RnBmzv;f;4TL`Z)m-NntyaC6fXJd$`ila;895^Ha5Mz%ZWRkvD;W}5gCV3 z+HA&LV!A?&%oAG2ZB%5!2bKiC`T+zW0K`rAV+WuNR8s+jz#OP|bhRARcF2fj6w`k7 z`%A1;{OOE2>pr^vcAxIJ^{{0Ff@OorEka=O0q*_c-HnA#EPRZM=~XZ*e>yk7@@o0r zHokA%Y`Q#${=~G_o6XfW%u;o!t;`!$F4evi$(Eq@JSxJs)Wuw{sx4KZt+Dj@d0sTU zT=lZ(_<6}TPG6^hNcuDA$R!HWidGp!CB5IIoyEO;bO}gdYG!uIYCf&lwETsf4;%YQ z>#h0MjCg~qoYnL9`I*}#Q^(IQ&r|v^rxp_M*~Fp9MOhaGdio?@Hgl{%JIVVxQ@Yi4 zxompkW8@*(#A0rU+UrU3s-E7|GwGR?@<#;!V8rrm8vBkyG~xh0}=wE zHsRMz@oaQBrmP@BG=if)KQ^LW_<*ybi0N6iw86inBAU;5aS+AO1Tp|#;{$7>90jSk zotTKu+kM4P))IZM^)N5rTbH_aam3%m9I?ZnX-k?n#aR4!@X3Q zDEaVa)s?U;8{;_9I1*oaB3lo2_Zf^?TKKuTV)Ka7~O;$I^NkyYl_ z*_mF5x{;FABO4A#nP@2@eH(^7KO{bs{JFtwmcT+Q5P+KKmh^j&xZx)l?bV zvql1@Zt-x*$M$u!#5i6D4Wbw@TpDv_^qf|Kg&$ zw-6pf?pRAT*Vq0~G_vtE@YG0a+ivsxYgL{-T{p|6iTE{uaz@tB@YVC&C_W_Oz-Eoj zNYWKU&V1|Fx5zIJTU&^ks^((rC;m=FE=H}_s*XhJ>&9()klKyZ9PJ~kV>YItbg;*C z^M385)fjxuzTfd{>q6tX0mEGI56qZdq!W!AYp|Mr_*evTh`lbj!BaHrCUzFHMIgZ z$u_L+$87kRaaD#F>!BiuB0FCR!tdpszO zmTC5uqkWrry;b~Yq*(z&pqTh7s|Y_kDnO*|#_X5IB-R?<%~n6IHtUqC*)}1o1J`l; zhUPXtX_4Bq4mZ{d(*zq6D>)~K%gmIUfjVB+c+2KH$lCbbD~l*{`YBGPf@;sb1ej{OE(&!2sP%z|9o48<2;8c3isbR=N!);sG?!?5z$$AmM#zcZc`Rv z+fo!;EhJnj5^DDLx%A@-3b$m}RYmngBh{vuO7EkA<}|XiybPuuJ}+}(ICW?IRbS}8 zp}6%XtT;TN$*e4yrUW8~5OgEX1V@gnj@@LMD1OhR0@YDDSdO0gd!2Y{vb}3%xqLLM z(>=c4xH86R6lM0G4e%tl_t3Aln<{kv0ow1)x=qTUlzp+KRX))xv;qO}wVHb^MGwo& z0Jzx-kK4y)(4~&Kem5vfnl|v=QH@`Y@j907{AaL6r4s&zwH)pFb_J6@;K)q%A z`#{Rnu=+89BW^+qnI+8oVK3F`fZ)q?%4wVIE&Hr&XtKtD*bxdshx&A{9&>n$iP-V|`huFiO zv-V^IzHbEKyK-Q!y$`ls@rqSW=yvl%ItXs({iu=@h@ zos+PPvkVh4gYVY~q_$z?(B0QYi*ya1g~ZW448efc>-i0B{qyEJLX3?m>q7x0_O7X3 z=p0bh5$^K3m{s(Wvw*vYpfY=BVUKcQW!fIo%|3OG zi%Bh^N>tw_p_nhf`l@ywt%o%05swvS<>^*LjBNc!zYjO;m8L)x2NXH`PFj4ker6z) zL+{LuzJ98G1k`66(bmBRc_$&l>R+829?Ozh?96;lNk)Qn3>bm8>cZBr4J^7(nr=JV zk<;==T8a&m?H^O7-Dg1|KHfKEdPP*3Lw}Y2w2m*}sPp?=_+7&4a}1 z@=;u?J^)(2Qc>uE%EZR;%=O|+1Cf=8O|JP>r)E(YuG_k_+Kd}G^I|_xA0e~}&8FUo z%2o%aV7XNW78}`NVqh`8N zDXDvhULA4KDZMn)%ZQ$apg*?lW59~&YIP{Qrnnu&-E2k0BT+w3_r?1Kx8OeM>Ax1R z!<*V_?s@@yK7Wo7kB%3bg@EsBhfk!ww+r={s%V17_l_X_NdHT84(?08nB@#+5^xac z?hEc^EMLaUaOayxdQ?AGNicQTl z5l1L5N0{h-4(VJc!bzXV*se{lPLN25v14Z)!qUwhxoe*W>wkE;XgcEa_S9+^eeBb0 z{v&^3=lOM^d}zYtVq}pmAN{V7STTff8`@RCF>56|Sfs_VFdUBY+Or{H!N*Kirk0n+ zIHXSgE6_lqq~GmE{RHO*G!O!}Q+(mXoJK#F@t#D6&#k+5*Gq^iiy9y+t3_R(836qe zKne)=_vP+lJG4c#dhbWeic`;bCvo+;#f6ouU6>Qd&NG}2YlW2g@2#FDq^FB#&a$=d zl3v_lN?imfH8qpU#o1sUxHQ-el3h5#*wEbZQGp+Ya#w=JP(_+oK0Kfm#@2$x zin6?MqRszH6>NLpNf{t&CHUo7ZS~wO-!jH^X~*6y7Bt8F139e_@&}W8eRxQx*WO?i zGW)Chq7ED&nW;TVF=+IjYCkB_0!BZa3T{$hNIlj%h=t|@mgDq&akagSylwigR?40f zKns20`bHvxKXD~D#X|wUJ4kcACZ9eai1pB^wZZ1yYGN`%`?SWhS6vR72mrG3fxIY~ zE6{Zm{gYOGDCqV)qsO)o`kkvl>FFJ~NeYut4}@kz{N%TeNz||bH2H6s-r#^V{WhPcNuGAgiQCtK9~9_^kSaS z?ZcpneGQ{BpS+lC-9I$WDhAw-*6P;YT~0z=T#dFX)=4`YJp6#541Soirr_jKl>p~w zaaI&@F)P~;v`7fu&P=gFqM<26UY?}P)BX!VW7H=cS<23c1~|#>4nW|g>Jiz?Kn{&# z(Vcj=I*umkg`DQzxpv$CXjl++*plC$&Po*;-8XATEihd*1|YO@YPU#EH`dYC1bpZttecj5mBKMrJm)LZ3{QDp;3|&9U1r05VMD|I1X^4H?3%MIk`8NE z;4E+x<3e?ot<{drBEK^dC69ex!XOS}U3><_n@kVO_W)y-`Ud8BBNP2{XFJ-~RIPG1 zf{ zBml|^4h4)=%rYb z#Vt7LK2f434A1i2`b&*GZ&Vy0Z@5h9f@9I0V#+#L!hZ;o| z0W_PY3^Fkw^Eu~%-X0qU1u_v9st4uRS0345>Z<~TASl#`{OeLHzPs`9$?woN7*k$O zpdXH~;%KV83-&5*A0`?Np$C}~g9OhEavZW16l=sYD%zsy(7vKHrHHi_b)KJL<<*ixZOebc;Ku10 z?g=9b)6<+ZOl~Y}_*uV>#`zP><29$%x6_u4NC?GnZXwDtS>7XH*%i{^nHV4jC=Z2C zcx!S0;%N&4ul|!C1FsPKC-Mj{&&YTlBn&2;p+?k!l1;U2V#AA4@Z)7Q903&LiD$u+ zOmdbfd7>8yBOZ)WXwi~*Z{wRUf^km(3$G9&h*HFT)Z9vtf@Bdgb`6AjCD}9gI`7O2 zAF&E5`<#%F5l3Dbc!D#O;$D&jfVJ4lRBrtSo2Sx?o4HUp0F2jwV-gxB9{NO;iV0Li z`ly&hOZd?JcH=bHD%Z21+3U@)JAjQ>m=O$1#&QL50J6jYi98y3-3|^&oA>X>+xvFX zNzus?Tzgu6SaUyhil_%EuxDg`?7XJH^|EM9xYwo@wOJ5xDGJ5B5QHJBD)9_pDr-mb z3A9|RKP{vc*0$2b?eQM-;eS?A3^}P3@T@>C%|=jSZ#r*TpWr<0e(Qeq>BVFy901F^ zgl|1*wlXr)sHLBL5)Q(_H$c$js=3d3lP{t0p#8d~%DZa#fT@vRy>64t=VzE)?LSSN zvLaK>?aT@0=SuigKNKUn)0Jn5ibxmdqXTclt+u<;+$LFephUo;IuGw4EOh0rZB*Wd z{KsuvUNti@a&=hYLG$#y2n@#lLB9d_JOF|*m4ipfi{3f6q_P4{8VCf8fTY@$`=~_B zK#wz8cNUKbBBd2b2nf=M0%cjx?3PW0&kCgm;>|_udv(qY_}LVQBYEAC=*V zwiegqQMv3VZ5tDC;<|-@qB&x*3X)3-ZNF;`>Dv61S9ab4=^pl3doSpuKBBaR@^f%@ za*-1FCMV9WZiF1ZZau{zlN96j1cEHlJ=A45co{7G)yT2;zQ`5Bw)Ci6}EmCrON|f?mcyLkNNnF+d8VV{w1JMO0;Jv@Isc zGQp5x_J4B83B_qeSIsd1g9rn};i?0T07f|k3AWs2wgR+~8cp+~Jn*`-S}_C)5E!)w zZc~=qYf<-?{h92RS(Ffog5n_oJ(zlJ+_cRtstzN5FO1CO;EFi6bZ2Qsi3W0*E(akT6{r`3wOIXpKtXD^8{}J@hJ$^FHY7QqBLTOrp zJ#VZB@<)J14OKiyTaHaw#=vyz^66!VeVI+2e|$sIkA_#6mDL!T8~+ni%wzxqpnlby zsnkuzRT<-|CZ(iyp(azM+3CY2L21C_xQpvzRGl56VE{gHLRtpqh@h!9g zl(jC^-i6`>BsuU=rM{P!Yd0N-APQa~?$0*}3bo~> z=^C5)$^TjeDn>yo+BGVn1@ZlmScB{lWdC5FKKZRU;>`b8 zymFkrdJI;vz+%6jz#@i``7PCla zgBYUzRa{c$g}M_23wSimxv{uSmqpy%{QMq7!@WQbp5d-2|BFz7{=*pp{@3kMPx+LB zz&~D%4d?R}+#D7nqZOFGUcCQEu)7A{{_+@uB%|8=_q5Uv0K^o=JQQHkAK(^Q)gPD- zV4-SdEm(Vy?EsiB*r|QlA9~i`kO*ve-;e;~TTa9L?Q#QFINZN(bn#&bf`LI*zhoQ5 z-Vk_wwNxe~(%;->Q#lkUaTTT%21-@}0}wzT3_#Sh-#;Iw1azLRHr~c<**|NMTQkP! zwwP@~D3LeQGNk9|lU;1!RZ37vxmR%tTxefW-=WXNf(}bxHR6og}n`SYJVN}D2 z&`Fs5)`d<5-{V}E?X;&==uf6<8$a*kfh_N%4m@v%RUFpLLYrUgwpumL*x4E}Tdb4U z&CzU`_s8~L$CKX9Z-LO<`+V2$=b{t6^98;O{Ug87<0}AP)~DI^^;hJzPF-F{gdGj< zXWQpDsR2G8awJ>n~OBXD?Xq+pBkRJVW#rU3M3q*y9D<%p!=3MKVv5HnB0I%LUJ?@#*N930k9FEXoUmX#zx%bJ{3O%9-u(OT{XT z46q0Yo$nNNCn*V?HOJh<(_dJ}9PnzN)Xnj)Q9D)pU1+X8y{+8^<%m#6NL73yuh(g2C38 z7j?g+s|y5EwxV1ujUt*{ot;uF#mmHyJTZvJgB9XvXQY^-lw6t0JT?oYl>W6CAhkAt zbZfm1ISvf7h%8@r01mlP+NP7srENf|IzRmPp@M zia!NcxJbS)qFy#wpIz6_n}d@A5O6ZqAV%hu_MxeZ_UaD$D5}PzFG9R=bMU-pmN?Rqle? zPs!l-ob*(%^`vW5J=h{s!mIv3VXd{lr05bRvV7l_zGv&V;t>!IV=QnXfxId^YLAUT zajQmnXJsU%U8PZ~{#m@vJ2m=TmRZ3YY^Zx#_T%!@a)4mx_Wdn90a(LYhZxrW^jS8E z?y-6=8fXg-Re*s?hfM^1^3%;1$LnX4N?W#C zDbjg+8e=q!*|xg$O;qdiq}^Vib{%6YSF!qcCzHU1$W2^TTz3n1MqJ_YwNALzJ7;iq z$fZTnIYqf;+}w|#ntO*-9%zMo4J4>_!bq==-Li{(d~rRYB)W4$bNV=QdNZ-&2!*~; z$7ks83psXJTVtKY(8x58g%-_JOA8~R=+(Y=-_bfw32Y5q7vFf?M|I3wF7hvIMN#F% z9k><#N{GkpALh8OK08m))ERB-8r*d@m3{n$+I)Ee@siGN>8GUvQnu265hBfALD)B< z^^B`w1r@a-vKU)OpN^P!&aHQQySFB7{ddZ*b*Bw2VEn=zXscptggzIozNLk2l(v`a z45>W3tPT2t`8Qa2SXgw6 zm4Kn4w=czWOAAc%vlE;Q?irdh{igHD0QlYGV*)fqY2I55*1EWJsh}gv9d0B}T4C~I zcbo4(LG&oFEzoYj+FjexYr4gQ7cPOX{diD9#tqmR87bbU=Wuzhqfc$z)r6wjMIi1X z>yrAL4hU5y-eENF|8jG^(63uK099yEuCMqUbTNT_gmckNN-u}5DZEoPlv!|Zc? z`dR7Lu%c=wo>6Q`na#f0WghTcjTQ%&!$?w(&6OEon-qK>7OafcdB(c5ccSLDQR!D9 znpfz0m3|9y6WJ{LO~A>8@~iFbWtTrd- zckHIj-=kqDJ>RFgdluZELy=+egG8RCzA9I}4hkG6umN}H>$8ls#0=?rxXN_1#Dbc5 zVD)tMQ1aNFFS;*y7=2aiz~*kidrI$JYU5qpt`DAHT6c-vd_(n~p-nPgzDWH&=RHvq z@8VXaOg)|}@6%F7V3Td`6x4`zOPOJ~=yy`#YA7_9ssBI^@;!7@3m^DqN>Ac;;LKcC zi2fB7#&je^VG5+zWv)9>Q*MQ(n*=WsI%af|X`uQuK(qe$iPas?ZGd)4eSbluymbq8 z-A%7XKGpJm#>zETE{t1oFAUSS&$l=kofO**GcjNVwFgCO2WY*SR=Uiw=Nl)7d>4_= z)+{YMz+D%0!I1&0=w)S9t5N5cbK?s~G5l}5=G+@NJ7_RM?ra{u5F~s3g4V`x0zPcB z+k=qVAzit(AJq2&`OUe&=pS5Y{EX;$_&|UiaPRKQvg|w~Uwke?onB=$ioL8q-U-%iijUkt!=WSM53W>R~;HxdzBq|yTcnH-ET=f zd6{MlB^jKY-gLJY4rM@qLE>igiTYjP*2gqcze`CxA3M|CDs1zD;j2&JOKwSB5X{I`_0lY{laa&`N zXospUqNr9ev`}qi%Y-fl+fvMzemu_yo5BFg?m-%Tj^h(YjAH?gZh8qX>A9%(?t-EL zeP#3jY-wQq;psX(dymxWnEy=Rp)uP?zDH5i>u_TFej)keiVR5F^w0HzD($~HI zl16PY;dq7ULJ$u0UpuT4Gq@~E;$E^TkZBV>wBjb&NlMa~`X)cK#?5UbmpKdV+^uGK zK4CGq=DHdNGO3aNa`gb#6>E*n;5Y)J+yS4yU3mMd#hjc>>|`z)iXU1Bny5afyjm5< zGI!&gDqq~I=ha#w{n&>`!E;=ON_j?BLcu}?)TWA+$qFe!+(Sc4>C@C7)ZFdr-b$IM z7ybf%F`>q+P#5$f#4rrbYuwQ@5iB6~TB;cM+{)@%eZg?S3#r75FLej2NF4zW`?aZ^ zKDq!iWc}2J+}6oXmETsfmG-i8yu~MFd$y#zGPE@Nh5YlETC#s&17RgDaD05N;72s9 zrYPzLi@r(+@6Imf?vfW2WN~DtEb2YcVHDM_hIra_U8%wYDhUEQFQUPeym<)i;Rd>HV+4T)+gb2q>CJy z+f<)&Ri?TMpO#zVl&n{Re=6~q}LLf)e46WROWf^$$U4L_R=B#nDuF-7#wd* zMDG~S;&2&oI&bpTes!<2gn&g)f8&5x9)MyPg0`;1$|$vFeLg4CIe9C1%GOgA*nFW^ zbOAdkCw{S2-PyQTQG5>CSll79LPG!bJle6f2TSbfrhao{vGC7k|0+4lFlEPnql};- zb4msyhvg$Xy2c9c8_ps2ad3d_WV^8S zFinvGlGK7ljiqRZK#eVNtR8&2&7|N}d}3rwDmAVb;#vf*ORox#)IO)PXNOzIcK$nm z>!Z0N^e$>xaJZ5DARSAgVfFQ1Qpx|tGiLbqtm#=&<#xNnjJaE@X<{|=1NLk79y|$h z+EEwGh)QSC1HI>3;{N{8hZ-EtetYWCEbLY^LVMGFvaIm1S$|262y0hxWaLz~JKMm1 zMMbCB*E91 zey0$bBIwXY3f*vSX!K@uOZwemYsI$vl+2>nNwhyV6LH+ikM8jA6rHXm$K)I!GzFxv zVDPNY*1y~^3E?S_mQv|0K;va`PICWwkzPz3LCjjsTM+q_2`@o$u0symnW@-!3}5!| zai+ZJqGD&n+}>zZON6E|T~FSRo3(u*ZgsF?QSy4jM$rSH_Re z%fC;=y;$6H{fVF(PnS??Bsaug9S-ltL=r228ZhspO#i;4QFHA?X;b^*r1~$#9f=T8 zccVQjXbY+Oc=#Rn`q-}RVy`M`a z`P)~NR+~_B+;J`#RGRxo@v@OAS;mB6x0qb-^z_hdUTbd;>BZ|sg%bA;zkAvY_V?e# zg7gCe27M8INpG4F6&q?@du)6~8sfFd;|9LTB}~vR2*)8o^5KvRJqQkV*of;<m5GyJa+aXUpv?!|(|5;@j)l1Rj0O`G63*~Dz-GzcWzDuPnD#zfR(bE`I2F{7KA z6hxE+{?`P<@g@#e?cs1KHDDPkyvUs0s{)2r5M{9xB8oy(D`@T^q)Aja{~*CvwT5--<>OcqGbXf=?-_}z|Sq^M%`hs~_4osX4ck#nZg%FJO#9)Ag!S(T5hw6Q$N;{Sou zu!Bb5Q-UMp!W(fRDhQQTXR3L4u4fLWL^TW`wF^j$K|nJCP=tc#JOYx* zsUtZqn2cAU2usSuqr@yRZWW2g%-}zATgxoU=SLQ(YeH`l3iF`O&NRysy;^>Sy(cf# z7{(7&up%pUk3TE}iax%;4z^Va*-RRhYeLvwx0SsoS@3n%}M1o02&6iXih; zze0eg#ww^a!7}u447zxGRK>kX2Hv1Ex{1HN;ZGzQ7mB-$zTm(3MI`a2e zC3)iWz11zrP6FN$3?yHi6SmO7Lfp#`{8_b*aD_{i`t~IG-|IjAiC{=@YG*lzf)K_j z8nrVBS_8EvpzGuGBuQ(unT2U_sADWh@@k^*h=Tsyq3fc3yF~14t;p+iX+X=*@yl`D z3I*6`ws3kzK)f8a8xKVoM3pFo8@~9(@ux)9s{KrHm{JxuFbB*o^Lee(XtZ{TLbtB_ zRVFUpibNd^Fp5oh9g0XcvfO_Y>~dJssUQVmVcW1c#Bi)2 z_Z;bx(Z>kib|xTJ_C7X=2Y9NiN$(=5b*ulngX&P)^G||F_#kEA_`~F;+f35btuw_V zo?QQT^9wb|2rxZjy8mng`fsdTVkl zRx1?TT4-nsF8Im4U48^q(^x-(z@_?PWlj+6HSm-k+FT7l**D4C3fnaAv?q-rUp@xm z(TSi{1_IB958~`)1X@Fr*5c5@d?#P#iJcFOgsf9k`NxzHSt&6gnz1l7RN0DEEO!bHSWZC0>VxArWQe5J)LPRP|a}N(q3ZD9o%}ZNE(4 zw+|ppJY!rkiwnShJEoa79DyN@h>BLSV7JhWNlpsd{wy6%LE_jjkK!VjjwI=1An>4p zI$*Hwm~mkpAc3iSrKfH?3zjQDeIywX8VEvOAh0M!89CEguSq=~L5Ac_8~P8EQm0!T@uU;Z!++ z0rYe3w8(U1=9};WJ$YD%`2_iD=5RS+WReBx3lo7i7>@3f7;(|4Xy30~tx-}8Mph&+ zm;n-Nc%W?=G?dr($HI*p8ShCGc_tGlxjF<=pn4PR>Kd4pSd!t^i%x!Wb!0!G_CwC<^)L~@x?s%~A1t(1lu=ray~opq&Sk&Ti=nYLcq|yg zX@|uKkHTWI%pgBm5pnoX`|AYhEkySf}Gh6Hbd^82QC9 zpTpP&N)?vK5veh#5m_RRKV9eh!P*r`Ni=-D)#NSdRFavT98p%T@*s%zC{%L8)Dk(1 zR(vGAG56Yyy(n*r*%pQ+6!DfV$;c4Ei5*A=mcGexaEJtLif1wAlY7-k57ObJGV#J3 zAi_~a2$rm;hbgL>)fEcDkzM9I;d(7cIPp4h-YkrrDTZ`w1QR)NtzvmRW( zojZEe%p)%O)5^oNIP4jUDm7&#F?_-lN(V2iXHC~`}y!c0S}pZi3k6ugy(Ft6_| z7$dAw)qr)ejQbYO-Jvnu#6$Z0%b4JbK|xdB-DA@#N1kG(7Ab8)$?IqqRlLd_w}`Xj zuvp6A83;+{v5>WJwtez#__)nfZg#SVo)*;EOn>dF0WBSW;N@sHu`(m16V(;aeg3oBdLtNpYl!Fs6cws)TVj zi-`2tzMicXdkeaOhd(->_Pr`!kCRPIEhR$D^`6>`HFzoTm7L3!ecfXwCLeY9WRdso z!$~IQh?077s#I05!_wk|BEWYHQKQf(FJhA!N)WB~3vKi*IobtRrf8!{i0&wB*N-DgTyLSIgt;YZ9j88Q)7@60w}VU=rO=bYwV`TDxhOrL_T%r z*g0?eCx^VvrA4cXowQKM4jvw2#7APlA^}L~U75a)Ub#JWZCVfz0{{a6GeiS00AO!a zJSc(yAm6{!`t1hV0UXmNkVVGQdSi%JZ828A*r0~w-bj12{{0|OGXOJ1U`7N0oB*){ zAD{ryAIq-^+=Mpa13n2?!np`mE(m4E?#F1V8mZ2 z=>Q;cu%uePx!sbq3y{Hr0BBy2OwzNs|f-k>c{P#z*{nyezZqa&^ zw%(9^YyariJ!sy!t-rI_{p+W(rKx|{J#wbbUVNtR=a|~*hcu~ArK72#-QRT$uA4vf z@3VF4zq7h}-u?UW71fFN{O;*v;eC8&=s~3a{~Z6hzH;ifKYRAvow?IL$NuZBmwxEi z+kX1q{T%*x>qUHOKN|HJvj_F5^Rzu}%Vy*BHR_2E)S3m5W3Z-7s)&{VQJfTTcpH0- zoH^Ue%D|^yW#x`K46|<&q8B0r5d53_al&_uok+BEo8JxPyVLCzF@bYeA7$vEsZSOz zjRlY#FJMB25gt&y&c3m4^9Log^RuWSw4Xru17R1)3CMCtXB5hI39mvvGG6Kby>orO z{WZ7#_~QA!)dPN>kG{K8)Spq0SO4#*tKd7UWOuD#YZM$^(kE_<%{>I&&$0fDod=Ga zVV-)r?i^2V1<#>m-Q#oTuRcq&a>H+i)5gB>!5#!p@(dR$=dVjBmqjX>3&)Tw@+H0?9L;OGBIFpBBUs+_?p zP)~WfAT8WwXY{Roap|||&1<7>u6_N>=k`bS9ov>p#ii5N45rUZ?t`%N4`2LbjX!H= zjsh-L0fG~y2{E97Shmw`vmWO}ytNcc4BZLX)Y8bOLn5TvhLC`1B7JWfWFtyx$(pOqJjRyKUMr=AN7Oft%UVU z>KZL8Sd_$Kz?VZd)@~f*HmOOvOkOVq|M@?BJ`?CCQfVac>#f6l$X+NS?Fm{dMk=%$ zb>*=VzQtEd2`wBWF)PwxdEqF_eq(l=OBL;+JBN5T4MsoarD4X4x_WQC83YD1$?ZH|qVyis zP@*wndnjypU|T~lRxKjYoeC9`f9ZmmQmdaWrsQa=jM#u#2cg_Emga6+9yZ2l&cnnp zY`);%^2<46_iiw1_sCZpnM%TShP4=uCd#Cu%4od3d;=p{cDMY&(wMg1bN$hT_9+9^ zlRR|X*ao&YqLO3c9PpS?k32jKV;`@bCy-MR5a2Qi8!6kAgw{vylx z5bK+v0fuU7Y51!Ck>>{Dhhnm}5dvKN53axnkezO51sGN>C6daz-QzqgBi~p&?Lab+ z^x)fHKlc~Fx!|%hhVpgs&2Be((-KYQ%SF=P^D(*Ifc;&+J`@#Y*)ZC3wCle4We9lD zR!hPqQpvg z_Up<{`}ll!R|bE2xT&-HmVD$O6g}7MV}*P2F?-8=-#?6|#Au-J#(h4SXU^P6&fMgF zsSNjFJAFd7qRBVkVxtpA{K&g78md8ztIV{D`)BY#OP9T)SuH(eGMP;l(iV=+>;sGN zrJ>E>4NSjWkJRtH+`0rfMq>Jx0|J!0Us{&UIqigLPtqzy6DZ>;d5l1n)iR-FBo`daqp_biZpJ{7k zT8ic--&)SsPpmz29`5h?`MtD-WRa7F{Z3{JS8wKa%o$VoHh26Reh@>wU%Bff{r9X+ zu@tqUVHK?yFg2I8j#R$k@+e@npS)a_8Yxjs!tTKN83`trF*Z$V}I&X%XAQ?H;1V>$x8u(3-|Ee^a!b`p3=#N);W}TGkEs@SB1iol3pCJG7}uQ7O)2EJF<5FFS8Og8+g_^- zsx>>cCauC1MrY2@)!g!BN^)cebPx@vhR&(h*$}5(-wjkol3dy)7@Dmg6YwH!a4~}T zTAjm~%N4g9Tm_B9ify_}uh~5PgeT72t94-P+Jc0=)IAcLO%|6uHe)w=HC?~?_;dj9 zjFi;($ZqO9yuP_)WQf8zY!!{(Qm_fD@HmK@YIxplchtd;(Vr*In~nWzb+I3K$v9^v z3e!Qw<2R=`=6q_S3vnoTi5|ak@D$lEX2Xd7m?g(fmLj^x3SApu9+sze%L}scq-YUN zEQHahW<{~DAnRl$_->=wRSyUsT_%hzP4+ccBc&danM@`Nf}vMJW5hB0btQ+Und17l z^4Tc-Fj^@#reutM{W|?V>1T7i1AF7gmrgRCmXujY2{ln>;z2-zwR?;mYD_-9H4q`u z#a7?E*$oIFZS2(4Sj@N*Hw2m zz5|zPbnjmEtC4cJ4Kzz<_~l-#_;LNK!(Un^v8fB>4JWH8wJP>hr~eB(ZjmT9y6YpF z4gY*dS(Ru>8S7_RS*b!1HHq!~qn zZ`*;*1_MUG;WBqFY#c4$OZvhUPwaJ~jYr&LX4?$c9USHOnezTaDSgp1?&hfq>#B-R z8IN2KD=PcTw9Mw3kJE#4t;(U3tKtP%P-5?A6NfP?M%LQ-OkSU!Kkfo$-`c*RD!7!L z$C~;Vo!G>l-!6u7=Of5P((${W^jlOchmOYE`nFiPHs~)Tgm6HlHkCTlabH1#0_W&uwsqC+w2a3;CiZN;UdA%L3MmIF7_a zvrldSt@zZA+0vhW7ao@SsUP%6lZCiNTf8HI5^wH@WJ$(f-chtukiCb(B2tQ9e{0jagp;Zg{RMOAh>Mzgz&K<0Y)5!6+ zTecf<7Qa`p6>Fp9!_gzRe{=KvKfgk>6|HOknnhjThR6O)UdV!Y7{#t(**?5GEca}x zn>#7qc$G3AEMzH~iW4o^NN2hhg%01QzCUXai$GoF6}t*j7j7{9!4na#3%oL!O%~in z2ayIV)VeT1JK`!ZW}W9lgl~FlOBH!#=Z2MINcu8&EM$V374pP(MU`Xwgi$5buG^sl z9NbIWeS?l3WU84v7BUu1)CCxQ_;r=~6$9h+p-Q#NK%c^gxWGwld)~{eMY6`xRSCs= zqlknjqrcAag_qAI38A;al=zN4K1CNd?jsHW z=InQNM|7`p^>2W5i{yj6l-I&XhWs33L#8q^JAH+tK$O~J<@~V4tU;`+*3Sbho@wuO ztxG$N@QZ?^rDIC}xz0gXcv;86EX1a#Y1b3T0lsC*@u#79nX&dxsH0t|&u+Dnm*}J%mAo8#pf9`ySVt~foUF38VWjXxIbB97EqRA@Xa8NwgO8I5A zf|kk9-l4J(BHrg3O=osgv85Tl?zNdiA(PZ(k|$MHRh}VXg|U;om2i=mZ`G`}et@(z znKR$e!<2JKWHOp8yo1Wo_dd`!EcQjN>z@{h!rF%{)2w}i>%Se+>rdw%KX1pYUG%^! zli6h<7Bt_mx3X69(D&y_7gvVtBM`199>%PJ;Zo9Xn$bN{^dXSRYOna^`s;(E>{5- zIrwRqqI6`PfVU>I$wJ+tmtXwUzUb)RJ-miL^mhqfQ*iJ9JQG-PNluz%oQ!RXz2N@$ z%1-IcJ2|DD8OtPaUB+edgF}%CRS3nPs!qq3B+#+EzJRYx$&{BNMy7pY3=cc1(=P9q zpDg5rxQxD6a2bDV<#I{)mpt>|>l>Vt2RWUUx#Q%N1eHS%Eq6W{yx`lkONx)=P8P=o ze^I47S{*a5iV6Ofie&KAe5CTX8PcvVi&0q7MluEv5Lw)JIkvk;c#32Gd5gKB6m6E% z%{zTbn@(N~ds#txZ!&1TBv;E_4U~}hLyY&+mJV@JbX*l*j2`u?05<}AQ;T)E7Fw#gKJ(la^bTxzdFj%BucH2D*s!|;&zMrC#r@$kD-LCh zuw1yFcHEc`#QrlqF1-%$$Yg_A5YhL??C#xZ*LX!g3@f&yXeKO`ttz(f!#s}waunNR zP+@ifh+-?Q$T=2eGUFB#5SVm)Iqr-tE?!L|zye{#x0u2pT*P$k4N5U7x6_vO zjDo3n-sI~SPFVs8gt>PF6qCoO+fLY$=&6Z-K*37j_};VkuRLCJDoDfI{q-%mMbcD? z_*lKY8K?sX%fY;8DTt_=o9e5l60pI-gNzfCSc^rn21gQ@CANdW?(lj9(pQ*C#mHgj z=gy9iI4F3Re*ib`ho>QMY{|CXO|=9VplpNiaF&otv8O95Ojx{ovQAOT_M%R)R*J@= zf(@hy$Q&)WanJuT72b)CV_Z)P=VFk8Ed;g3g#ee|Z0SBjO!u?gPVdP>A(PT%-15(N zcP?^!a@DB_OJo3}PAjB_w@S;T#7!XOi0Vkp#Box;L8}vE2*Isgdq?JgKo!PJ7$7El zEswc1{g(GY?5G%fi?+fFY!MZTyaGDqUd-ZOlD2HC5k;agt%8HiK+0fAkW?_GM2q3b z^gVj&7qKdrIn_DLdscTtE483ZVnjLhM8#(uoN&3^^Z96j_iJzdRl-DY6c2%cL(2(X z4O2S+6dB2}?hz7eJn3mLs^5&rYD`rgMJ>xYsRkhn25rniOJEIIRi?Vx&~JL|DWOm9 z>ghX>2ua6g4NviB-?kCDcK;|nr2LzfE)S{pYiWa+&>EaShfY``LXy$|ST}-`A8pT1 zGV?TJ@sl+NO16bEd~7WO{yRe6CB|nQS`@K5jmc$L>6ix-{^Yh45r}C?<`|E}%Yop4 zLtm{oznkomjmg$fjGc^viBN0XH%pL>ikZAPcZ6rBWxf4kxahf3K@UE_jZBh*x-xBZAD=J zLr9m@Yk+feWwqQx3DWBty}CLvZ@bdDI#e())3I<8JG-}2tHo4fJear8mN2cQVbMs^ zFo=)CvyoHATjrUyD{q95H`IX$%su;&!{*2Dt~TBJ&`>TZJQYfC_3n6QYGfB)P}-0a-_OpV-_LWPG6 z5klfyB$odzgZFa@2;y}^sZHi<^!0nW<-0m2(|aKYe^DCcHi5;mA!N3QB*Ke3Ppsdv zJ+RP!ZNr!Rn~E$u(;?xj0I(2?I373`4sbW7|pM@f}O!(Nc^M(aSvk5_gLYs z;A$MhSgtpbK)JQ{v*Q?s`Xkh1c6R=Q(#H;H=b#S`_5az&wfi^7YwsAr{W7tV$xn=> z0EvRB(o(BH0xAu{X?Lb#=7IVr?kV{72ywWKf%j3GL;K9dEbSwX;{l=wGdBtpZ@@px z8r8q|&sMemQdtpGHKZ(|gN?T0YY~-e8yKs5$x=U0|4-{u$@ih7Ii;r1+X92osl8ut zWp~sJYShZ#;{(<;o4-j`xOq1ZE4rY2cn-=SL;_TWfl76 zfmRxX3&(^+BoyPISAxmExqj!KAs&)@6xJ1_QiL35gGjJJfm7pxmKZ}ZC+3;C=hOMW zDf;DkA|5HjrCLN=V(Y_@CL-Lp5tEhax~wq&hZ9H@G?L@q^Qp>a+U-5!Kn|`=i zPN*mE>WiDkmL$ZzP*T#q8tkNUW9XwTG8+*(KI8PBj(sI*YUh_6nG1mw7t}*=ZOsr= ztX5&-u~t~UX4Pn!4xS)XA|>Y@Bq4Z@sU>mCg7`+}owjU16cR$fA~h1@11Yd2jzOWP zEr3!Db7miRZUYYdKp`u+2m^<}V05LULgs^9fjw>&AT;dKKyH*AU^*Ee|bG?DY-S!8VyR(Wy>3t>unfkzDZ%AuF>zr$DBF)qkkJl_%Kts;pH ztU)DkG%5vKPq@Fp#9Hgmy!CLRA4cQLo$I0$DF*2UQgN!qD#n-s!5H6*_AxjT5%G_d zR^);PYx@>aJXj#nU=*=NeQ9fe?-O8a)ywD|%-{S|Bv>&K)lwjVAaZfaW*PUU4^uiz z{>ihmJdPeF_fIFoNYXY#SUJeJK!cG?MI(Ahs}Rn6orF^=1e?%O@;7M!zVq|=d-#%u zon0W)gfvbwF+zpMVA`R0T(2!s+!aBKVNkO^EnWUiee=yOUq12=hUQ{@$;D%`T9sLI$Q8po;@l)LuzJlz|y{#=yH;wYF(62V`td`U8C$ z-otYA>g)lTA!KEm5ulb7C<`De5SFo)5`-n>dZag*KR5qBeUDS{3FOQnNYXSzGRH>X z?ieiw6^8UhaI{TP!Q%tnLvP1_>ZiKcTH#TdT6<^CT}t_RGPMo9?wqlQ)9q{LtEb`e9FSV&*e^W;;zFgom>4pRlN3*>TzdlYOZAG2pSEuhe8FZ& z#=KsEp@e#*_TmH(7{v-G%%YZVY_9&?{lsYMd};j@9D(!yK1EU?Zd!*#Oqo_m#ZxqK z@D{Z=cMS~AZOgHSq#tqm$%Pb*LV+om3_B#W=1At5IkZ^>qE=!WdUNhvbX=9FqMSMi z2BC)}PI960b1>Ud;Ef@qrO4Tvs!Ng7JOnH4WEK)cMV2dTQkahRr8@ljaQumV+gGWN z`|r9M&2Hc_!QxIlb{4Umm||PBP(`T(In1n^kxd>?y+@ptqt2Hc03_>DEo%nr;KH;Z zv5vVbR85xNj&LuH@P`Q3$NsYF(*RI`p_PH6pea7X)g8*kh<(XY8ZQ=!;Pu1*?fs=! z)C_IZ!PbT^U_cyfdhq8CEGgYx`wkLRQb9|f{2~%`>n+p7cARrvqf8}6o>_KZ4+A=$8vImu=jU(!dMgXFdUaItnxJVygp{c@5;ANTKzXtr z;Gwv+Y}TJ+)2*z~I_Y>>a=)3@a@@sL#_NL82^mwSw@9Eg3Wg5eSt&Yhrko<8i77Wt zU``&kQ|4yy|LBjv1b2EXDHJ3@(}Zg&o-!FmV>fqSz)Nr9#b)KjRoRU8T-97~duN(Ru#$XggIyJ~>{ z!*$n{+vO3YX4+b+W9=iVpr;I>RFHNMtfi#E1YH5=0~p<65ne6gotxM7Co~n==Oc;5EoB znvfi2VvnN|LWwOU;>5He7)zx8B6| z2VGS+0N$rSd_Z%DLL!xEa`_yz2gl$c8KqEX6D&jo1Fd!UE$7jJDf2 zBy*pPgOJ^BK&f$?BbhH{`*)!H06=I+tcU=>0SHKufNt2Qn}`Xo39m=Iz@x5t(*wwa z5r`6?fa26$fKh!4uD{?^b)(uTSfUIPiv<3j#a>Hp*j;N8F%xZ?V zl+6+j3#bwr@K`_;004#tphgVf<=~NdWm~qP?Q%BFtybB*>%|@VoZv|sTb`|*%3kNq z*^Q?zKVjobQt8q4_M!B0oqEzl($2OnrkQ1j z6El40(|;fR(v!aPH(xs^|Mc;hH@@`f|M`B{&zJ7X?LUpL>Z$#8YICP2@~QN6JumB0 zXZ@G!j;zM7ZR@2z=;zp<#}_024gDT1U&muVpRMyu4E@jkee+HoQ?GjD3vthE4XxBN+1UHB7 zI0nrv5~9py{EHpkNiVK68jK9Tz`e3N*MJYq6BkzCH-|Go@>lrwe0^JcnBD2N&iXxDSj^zt+%BpwwSL_j@Cmu0 zRL^AY&W{}XFU~vj)o$1rhgwIhu>zwB5(*%T1>S8} zkm@OXJ3BKG(FAIQw_@%Ba-BVm-baTQNE^3fIV& z@*taK6|rc_w&z<`!l`M?E|V-8~NyTM4-h~Y!>Z-TkqQ9 zKstI!;RBmkqaW9y=F0F~Y)!%I-g0jfI=EMy%DFKrb<1h+0KlpHEbsae+tH@uM4ekd zt`o@a^yVogys}E{Qxixh#t3UhiS8`#Jrun?KX>k#yz+iJoib|+Nw*C%+ru~<@^|91 zeQkBaAD$KbPo}q{(!bKnUi;TN(L>~m;x;uM*%!Q$LARhDq7(|RFMJ?jiDP`?2!k5Y za9!zt78;|W%+@=)~!6p)bvL1@h+(tu|F3TG}$?+ z&nf)yf9v-qt!;F7dkA+N!V?<_jIDKLGpz+f{?PGu{KgJMRK#Ro7t_(T&@Pv(TL<6C z+RluuzrFx`0Di6NCI zZILCZ!pI&avnccpE$vSE*KPDhQ`1K~tt(F1rnZ-kcc~VM6Cx^NvM)|85w(6f4fa%v!C2VXU~$D>cod^pXPwMnRm&%V>!6!;0raIQ^sCC#*+ zt-L5$z@+YbuElITjI>_wGe11~+v3g7`1GWrf6|?V4fwdgQ@f@6pMF+nyDIv3-My-? zsg_>AYw7A65eJXwte3BCpTo<;IGO$xVei~m^CcuoT|C^VigK)rs7y1}pvlKJQ0!8p zuG~WdvTfkny7WisCEa@F+@Aye2>$Hhzk1-uG1{d(E62L1^29JYt=coQPLs_2_5C22 zy4N2C%pr-pW;4ALe<)Wc$3gS3`iz#$iEZuKo?Ba=zws@M_vQwr7U zcj+QtZ2WtJs`(Ld4_kFnqlv`b(}%mtSlv%Dj$Q9j?)r##%_yUeqwb$f_aScHpu zOsu9NsudS7RBLXwyey4$CpMkjK)L2F{U0g#<;>P7SC81E%j!vw#De$mdK~MbKodQq zcZWIF&30e1g(j_U7-Vt@Zh&L!4&9vX&HU=`#I5W^MLi|fK)mgDc;St{m4Zn%{LwU` z`GYGxiE3GQ_EyXK5E1pTSQlrT;os2q&HedFk8v)Fh%fRJ+kwuuFATa1_X&bOB3@x1 zF6v^sWn^-(QWdzZjr+&<@)2r+?3+iFt$(PybDJM|3=ssm>~+Mti^Fn8UAaJ<%QN0- zd{ao`8E%~;3{KiQ&|T(7zS~`l+@?x$tcx#C=OD`E#mTj^HTaU#{M>6B{B6p<4WBc@ z7W}E5T9$u$M?ZH;9&JIyJ?z#+l_#gw3bZ){idnzN8Hk<9hofI0vvP)4RpsUT9?qu> z2@&_A05Ce|KhT2Ddn#Us90@3H$^ct4_sZmNumxc5u({7nfi~C6xCZ*od!kk);j^>hxZFRi zv5FgB`?x^s&g2TL?HJI0&EaddOoRJoJyE!lPZf$O@j~T%VBH@|5&8ut2|p#quZ_FE zc>|`ixRS;;^>5h|nGbeLG<*H$Gqxt$v!6DO{&VKmdtwOCRjuS_a2LMK4_Z(48ITfu z2mY;pPey|WujaOfy^JLtdt+BEl&RlTW3=I&8-4cSD}oW^`1f{xVxzCx^-@H3w;RkA zFYOQTUig83TPnYJE{XNs;%-jKtj8bAoYkrtMRMV~2FO>@S$=wX0>5xl3-0@HZ(3Iu z46hmP@6+*jen_ITelrw%xb}@jtMJBr)na3P`Z>_*il7OjU(B=dZ<2{ zCvC3PZVxpu_C%CQ$)KUm0LG}rEh$BjDW_{Mg@XaswYQa$YM=^<+8i}$K$oC@SzY?OAZ(_rOlK9vKoz3qpoK`qgaccS`AVB#18gEf*xPCbOsX#@TAtHQ&1Vjbv=1<75TdI zSPt&9$bo3U4uGkfktv;$*Rv~Eo1^5bim8c`zA0RdC|n9gJSKwGrOCWvs`F7TB2o#( zWD9UAX3_%%K9Pw^HALrE3w6QaYRksh+XDf8M@KzK)AuqOTu{ZDv&HJ0DYl8Ol%$di zrrMFjSYY4`a12&Vi^E^6y1icH#PuiH+b`vy_AVbZ*QQ7bbygf#7QdENcDyIDS+uG> z&6}Pzi<4LwK_SCNaIt>fz1I&os%}}5m9RL4Z?P|QX-|$P##ft9o3<~u-}!+G(dkUn zjGSk_cTXN3`>?<3B70Zg+h?!6wT`4_h}>)vg8s&MF(KBMn2>O;T}2ysMe=G%+7)qv z3lLEal0Dp#vZnHkTS+0=tp|jz=I{g3ehrttr8A&*M9jkuPgJjDBw;b7-E^WLGu|Vl z!8b^PoTzU#@qWmH_3QKaJi!G|f^jG6RGk#{i{=denuXr8G@TyO_Y4EdfPUdfl($bGN#%e zU`?;q`Vx}4>3&iH4Qsrxsj!b%V4UkJ=J{J4TW795DFGxf;P`m4YY72nIHl~$V zt7`!EomxMQxBl<1WJDF8%oAu9O!O=)k3ZnQ+CUUGG@)A5d+dG6@E@Cs!ozyz?tZE` z@8U1s)YfD@pOvRtq!6GQn+PUksmGaZg*=^%`yxQPwKG(8VRs&C60xXcz1LdIEbf4hMOy75p{nlNQKG z+u0dxN(f6PI|MY!5|h)UHH+M71@OsQNwf3G#F(+>lw?e*ESG^Ah(g7-e!f$sO@uwW zm1h|j#=*F?*e2K`HM(_^6U%%0L($|_9La*ktlMPrAzwyva{j@Ye9c}D^v3N_HF=Pu zF=%K_k2V@oyMe`78?&nFxyIU?8ZB({?eh=IZE{oLnQ~e&;+(oBiGbL0m-^#=7A;)J zX``X7#3p2ltmW+uFi}+({<7niUHF39Vc^ifbOlVJxSkKYz;lEFm9?%N#mD5pjv6g& z+e5&n^8AzZ-ab4(`&3`8Z+>6ax3|mUeII*Q7=SU(fkY#f$OvnKo}ymY3sMS(b^W?b z-uPDIG%VtBJ=ifx7gaq;Mw=8uK%A7GU9q$dI`TcWu;9w3H;zUMzXpyOOlX8y!N%9@ zm#voQi?0#i5`|@k!3PP77`;v(?zOd8TP){GXlFO}8M5M)NWnLeRWyu8oi%KKOs`2e z>m1eaJ6t{W9}OXkHN(E*q;8ndI6F-Y6mRI9LAi|_Ie{zo^QVjR=Fp=hrk?BL6<>8z zCGH?KPWVZR7bO5g0B%9HE0`?zzp}28ln)DB(wEKSD)AfdSfxEwRb*JHP8w?kSh)LAl4BDqwi-AzCZyZS7H_NMn>F|oUV)b&7`+oc^N%aU1Yb8UMoI-d7kX@iBkWi zMM5cZCN0=>m~mx4Ljb5LP@aZ-=c}XQ*Y-Z&^z;qy6(21o79|WW5WE1f;(<`w@81l> z66CKcS4J)X5xilgeYzZd>-4=}`%hWo0*fk8i9NwMQI1MBhMTj6TYK$Q<-& zMQNssjN>1TX;Wj~Z-$QMUKh&R%9tu1=>beu8`M!2cja zX}gFPOtmb-WD*3pW44-ul|JoSr5>Q&nOgt(zUi#U?RICS<5L@yM-oxaxnQ6iV?=^f z*l2Y#o^(Z?&~;uo%{j}cPQhbKQUG2Th%qVr|a0I`K+eHWaR zD)^C5xWSjK?w1|uIsd8{E(qs}&KRRkqYzOA66|yV=^|MR&8jKV_i>)Um^z2M{~N5{ z%wsm6n6xg(7Y|_!ss?m&PsU_>s+x!Yxw%_;|9=n6HV((XoAkKbb)53=H19DQR8ay6 zQ3x?aiYI$D1r6>&Jv*y62MyE?wOL@gc;BY* zV8saJ>M^J`{-ed^U$c|yngw{_T2xA@5&NEcVAnUHLkQ}7wUPqOnsVl=7#|tJ-V1XO z*Z^iN{=fUc?uf;sS+B4~a8thsNcfLPw}j-sZkEOfHk6D-s*zYS!}!Ew>Q8nv;lGBt z@6YobG52d(?%$s87@ksZ+6tH|w6Q5M_7WjfycVz7`|0ICCEcDn^J!c`YH$W6fODq3 z03vemQRqW*6>G{idsOx2 zf;zW|`UR84ld1F7NI}I~{HLX~Ih%Us+|xgN((bHUXYDd6sbo~VXJo$+w1!AqDc=P~ zw4QzH$qju1JL)c+6jf7D=$>pg&|8ey1_Y7Gx)KJlaar5I7VG?)=OPN52aF&>Tubj% zv>c97BY;^Y@#)A_jQt}~amtGbfGCtuz1+pAgJ-UsO{|T_ZYSA&HEBj?M7{?@C`p77 z0i9xFe)H_IqhGn3FPAJ;(U;8@ft0i{%%e0|6m!1Eic}T^)oRXTTdu%;YH3LE9!y!| z0vl=uR*WdP9tuZDBa{7NIc<<#ydtr=NRU)bPjiQ7i@lBXFh&NYosrL4l*1iXske-t zk6)yf6d~eMO1U;{wnS56CS92O=Mv^>ucmAe4kQ^LmJW1ZC$yo%3x z+4$nLt2ty_VovSVVd_Z?EspnkB}B`)TYIe$mQvw>8H1TbU;!dcZb56c-tC*uG)+0X z_wpCs_t{6qm1ZFzemECR=7L3G(h4YQ$H5)4e|27C*D-rooZoh1wRuj(klW=<) zzApyaB1oJPGgP{k^wcXf?Vi33KN`jG&1)m+2}1KRQDJ8IUR;RcDU7*d@4PENLwj6V za9QuDy>jn)ntXQ2_rAird)JzRI$BqsdUU9m4GPXCpiV+XILO(a;Weo#7;oIoFD+&% z%>S36G5(c*>^)ZQgF5XI;)-ezk3x=upo$KxW%jm7x@+dK*mdx@x5!xx*SGc2@;}^b zG^DBi;{-?qgvPzu!|oY7Y83d`mH^}Or)Bx1<0t*zmksseAw(?X`vrV8Lo74L^7Wa& zz8{NchtFE0|KRO+;jblw`*q|@1&4yQ3CLp3Vp7`-U0H6$3^vAQqZr<>6CXmfMXikj2;KXtfFx0udV5{pT+hil< z?p9%g?O2c_*Tgk8N73@Jy%KL6!S_%)-73$#vDfEBolH@rUCQoLuMP(&Q zP!}=Q31Qi4=*cs4Ir1-`f~W6qfBv6O`;jby(#@EMQt{J~SX1*rT8!Yh;|M`eoXl%0 zj?Ov-*58Yb$^LBR4-Ym7VF%JyzUssnt_b4NQsj zxEMAiAOe(|UY5-h5AftjY;|Ly*Gn z2cD@HC2-e^YqV?vQz*_|Pj{O)m!H0u)<9Z4f!HGnPQ^HAM8bPeYt%$|n&@S&a&)q@ zII<720XuW)qwU={I+7wTf)m>#bG4#qq7)R`%5Hp9-1Wo#N1nVhQf|$rW7FFuL{y** zKto`^t#_NMHCJTl{$}2$S^MSY&U}AvBQtb6Ij4`oV3w+SeI>gL&SmrIwK>X1<)vyctRrC6R8LfxO}!_grlz>r#4{P$Sx)!H$dA!+q>W+nhH=%UW) zTa0BVysaEvhTe`>{u6kwD7#J4g{-6En>hi>Bmxi+0{{g8GgAXL0ANp4<|rTllux@8 zwtq>0=L3x--9uzZB#i=m+70iM*mSdvdBo43fZ_vyBAPQI0DuN48$<_^Ufb;kTX+{< zpLm5=JlDt$D}&#{wC z7S<^HvSUxBk((|hSVDY5AW8rLGXqsK1#q=ze4@1 z{O2F7e6mZgFY4K)z4l^VP#+ko^7S&b_4O>Z>S$-^YiqmuonEQA{FULX@ z>wo&vJO4XB_iLr!`q*#1ALRaFW6$;GGx{~XzV(;l)t|U;xqqE`$9`jLllFYZMz)H2b`y`wA*&Ah3WYd65GefM2w z@$?n}M2iY8TUU$d=sW0W)mUx))_!JIsdulLW0${Ma7R(2R6RaiQ`#llT*89@b*2OS zDoW~L%gwL5OGjo$34*sT+Ac?G>_)F~jFJ1jz!!c6Bkv2wv-7TW%-z=0;`ChH-EnKK zOL(X|;++yzk16gmBjj)wMHFcKQ>Rxm($mY&ZKHQD-95Q_(VA^>dEO(~tAt%51;GvtQI}uYc*;u`k%w#p_+WimIYBZ$TSXkf`^C^6sBe&%2Fs z7Y)kxy0+dd1bWo5>at{Swp)k%ZhE8F2?J|N^KMK#3N&2$4*hW`ZX^MX^{;B+@{;XMQI^m_%Bc)<4?x|`T)Au{J%lAjW=n3?8Ki77oBhg)_6DobxR|tqy zg54Tno1N*;NS7lv9jBMpPZryqPcz(4U0j@WlBnsY?2Yf&-f_OcSN57*aK#8dmTfsT zd9Q;@EM4{&nf6~U*52B(72C0dlIPmFWF0YU&qF4`gY`9qQp<-K>OAb|+eXt(b8*|0 zT-C7$XvOrp3~$0cisY^dH7x$F`941%F_CNCy_Y+rSoo(xx5-KBPymA5FL_*SA;SLf zK`mKK1X9|#U=0%;OQX*G=2s#^&HWYD{^`1t?aA@ZZfZE8Tx;Nc90Q;0(6}IJyC~6s ziA%2`wenn^?3>Ls2YqCW0FwSCe1jbfRrB!{gqpfZUKJ-4eEf8pq>t`OqXY@fXZOBc zhrspcpH_<>sea28t^80V?>g4JlX4}>^e!R(uww7PtIsVsZQD+MBo$ICM{%$1@5^sY z`d`1yn@_kumT$2?J5gxVju0E_foC4;!R!xwI!D6cAv)NT zUY`gkp`7}eEah@(6W)m#>2aFIIXd9lhJ_`;*A-dn93&jx>!BJ3#EY}2s|AI4* zd$!1Tw$%@N(sBlo>In^xhx){;_;9wNg@+K($v=rl6e01+_KYIt6*0ISv2FA*sR0d4 zXFsXMn+`)|VT%PWu^@%LPcOyBunpOhXb?feD~H2V^lU@iGEKEJtSXj`Hhx;1xj)#f z&nLM?pyI`Fq8V3t!ED&bJQJMfk*?)U4d&rPs~0U5RO#n!Rx5GfzkvrJj{3ni4NK=1 z88!|snA+(-J@MNd!=cp@a&Ce8ds-gRF?D3-8eM_cV-3!h^?A2)@!&O<>AH8|Zvf^7 z?@s!4K?Z+h8v>tgx|qAu`s^;%m0IeZ&=>X4RX;&K+vkz+6Dd5g>RJUqd{ElJSrv4@ z3ZG}jaLFR=z5_=1DNYaj?VTS9h&(Y?wY?3Wes3*<-EkSj!q^pc7Dn*KAmZnaYbNx{ zJ8zkzTtbTqL3O=6c`zO?dz@176`PqWW<_=_!_q;1|9)1n=Or{WSSr>5@x&aVw3h*aHzI!@mL4B5THvEGA7a#nXlw zk?)=Fx9A`4S&Bc~D91nPK6pP)9tx?r!?{0R6rW3fSpH?a3Ah%VGQQ{zs5>Rwm2OS6 zM|s(8xoJqbXLi4L$I)_MNo9E`8a|Lmr8d83GvJOy`8ja3i-&Dv5=OOF2j&7zKd|EF zn&p8AmffcrwH;&FYmT`|Zv-~am>&{%=sKkrxUVQ+u)o=E6fJbwfP`Ro?Z}9eo*x5( zjiqHVGGJ>cCCJb{sIrm!dLp+QO#LTKqo>gjx#@|;wZu^B0$xc%yE>#pKcKVc9!d}! zx+~j({^zg#8#QGiTeu$$WEx1jYq(In@1%`jvL0dZPw8#Ix<_TM0NBUwkXr92QM@Z^ z%5ZK&yG02*mo@!I;+>Iodqc?}at%I$-iRdxVk@z8mqnrn4?+fiPait-Mt16eEvDaZ z?{gc&{&&D{+Y}6$F5(z4oD%JdP98O)@l4!u&`Lz+=558bUX|RF9sn_}%fU*84*WV9 zw1TzpmR0bl>add+>pj=_;dLzgiu~^5hp*UPwe%lB&XVeaNl^hr+iXm7<~QrmucY%r z5I^h>(Y4Y~io<&oaGBrHfyIc7Khz)_wHyMZl!*Jol*~NzGTV}HG4<(RV!zi2Ja2g| zR@Pq><;H8-i(<5vV+{^&1l49*Aj&<6ahO*jx&ubeAv5w@Cg>D#7DgHoL~tjU1> zCkrah&EQ!XZFe5K9!Uk+;Epc-20)8pTdHkv=D1jhi<(7A=Bp7xzmnm_sIl>}BZmw% z>EDL6r5-2cy&Fs^c-_b1Bv&Ux#{%V@7nRz5x~77QY~~xsic~39fMXNhpjJ;n{$pMUPcb18YOZnsm`BvP|bLHuZdXB0Je6esWZr`K8|gp0bp zjS>KM-J*uR4=Il0?`*i`idL0>vTTDpte7CP>Vr5u_bd)OxNb9s`RML$dWG(MA{7Yc zF6yq2TY2DB;dyiLx{WBVU0d=%j(!kV1NxhMH(iWylMujZSMPcq9Mj91>;f9xhFAFq zthweKTqjLto;Wc3%R!cjq|bTK(p<&7&o3xz8h<78rzdFz)}!9W$QG=m$E2;dAm^5C z6yxoaPk(`8s1X3X5c`=-{zC2~8@=k9 z{A7^*$QaSVPlA`|V`Q?w);By?_cUk zN-7cqU)vH9?upF z)3;SAgy=-On{D_`l!Wzfn}$`)?pZ!MT!GSz)Xbnh;4^XOSGqs>`{t#Lv5)9!?^Y+L z52My;t+iwmbkYe?28a3}RTRGpJ6td89V$w%5r_K;E&)Rg~*_RW1}HO4BL*+b&$m+Q@niMvOnu)msr< zbiF%nQ&>0iQOW@NH$Tj<#NVSp%>13?6b>3DXecN}VdMbp+l+c3mQ<2$apK&&Mru}? zamu?gbxDjg!rni0Z|v_3WH8Zryuz-3jfN_7sr!^S!}vz-jzS;YvteM%?hX6AcJ;GF+h8CSTS%z6us@p%@r&$K+aOp)p0d(00WI}-#m zQ_DjY)6|`Yl*9>juWi%$6HLXdzLk ziXrth-8A)rIvkg&8AbgJ(bKaG9>B?PI=y9bB6su`2auxm;4VPKmtj|#Km~f&ejd5F zrGPH5tV?azj2IVjQx9mV0?FB_{~b>r-sTa1R!_un6*c{r9S*+3b7xs$kp(HsGiT23 z6@r^{9FPN*<=kiWP_OvfFTdSqM(+QFHiD#y7rq+gaBf#@lhJ7*3>&oSLx~u!Ysd}e zX#qF~+$>wo;$I>(T{q`6;}%e+nrU%irgHNeFY}pc5fCyCM)skI4!^A0dG4D`x!F#b z38;(Cqx|%y2~kZpKA2N<57%;U=90@~y}4bi&_z(PACm##CF^?9AB*g>%kAF}z|in= z_9_r=mB>futhDS62{AI;_W_ui*pP(>rz8_T?5LEDb~1nudGj}8jkOJ@H-nQrV(6;} zj=U|1CjVGw2M}+_d!K4j@BIY#YGSb1zmm+OJcWfsG{uLn*ZT`bIti@G#qjd(jTPH44FEY(r77gAvA|yv~?4YJdYzY&txB_RoWl+&#jBs~cMJkX}DO-BDp$I`|RnemUW`H06Bj>xYoAra(IjAE&j20mg z9*5tAx|oq;8XdE;{c-3T-@oHB(U0vF1k6%4C^am9Q)_QLn0F=>?cZm)mUENHTo(SX zy^IWCKK+%{Ocr|PA3M&cV-=gr!ZQ7H{i}5Nh}NvX37{5Dd_zaR3iJ{}W@+P9>{{`X zOj&RDE$!{MC)x+kv(;MAN1c!AQb`R^GD=7a+94UgX$Qyyxv7T5+z@AJ#p-nH%yN5e zJ~&x#If4g=Li86>khj}EPjDiSR4*XuT<^Jx|JL}P@olYY7K3`$p^q0rk?|LU{M>?2 zQ#vK~ta9Gp$#3Ii-jSawf$keCT$-u*Hw=Q{#p4VgbIv$(AbTEvgf_|ArG+`&O0Jt)jbkH z>Z5cJ9OosFyp|%SbWuPUlmJ_A7ER6C_;@j>u^N|ca|L_Bv6KQ+NSI{K~3vI1s$#ta= z^Usu5-o1q!5it*_?$J!_O5Bi2G_9aeE$`rYQdL<-R?)F>=D@%RA`%c6nUvFiR$>7G z_Q>r#&Y)wAxoMn{kNJj19(vgBZ`GVNBlCe)Ecr=FRKjj+n;?V*%?GAfCx_yv1UJ-{8d4;y6B(Qy`RK1Ho>3rDVrtnB1 z1Vfz%5Pb(`;~Wcrqag&qX(K%y@K|;c4mvn+7$lP>6Yj!sjVTE9(OeES-dxFN`l6ae zNTW|0$0poEU$ zL#*5uCTdXb0R;(g+8l*{y5c6rC&vy(S=(9IgRYGSU2jTyYeJzLlBw${>?n|twQ~?* zvD=he8b3T9*722S2G^vzfc^kt%}{QoS>AD#@Iih~is!L&&Joluq5(%~kp&aZKE`mT zT04RADr@5`aqf?^QO%nx@Nf`!4d4-SumEjV$4GhD8d+}9|%-MBDT_orzQI~+j?TLJSd z^fMIFYSP5Q)xyQRDB_wc(T*<}CJkRQA)FWr+!cyaQ$ph}D2h1xK z?U{C>&K`%Z>|ih*NkAfL61lPKVWQ_izEgyfz0-;KVEg-#`*lxu#-^^;X-Og|Q0QDg z*n97n4HCSt4Eyj1LgjCTajyupn#v3Q0HZWwU8;r~MJEkhq8r0f`3WOk^UB zr7ruMr8}NR)L>UZ4n}`aQvhXH#vv8~K+w_feMr@YapU+{iGw4w15N8q7IGBil3hn5;G(YmovKMX~%6t<{wk-7+9ogFwkhx^h^t~AZvQ+0tC(;{|@ex zN9QXO4n#XCp%&Ri4oFjj`mUa8Ouf~tMAirx3Tuu~VqZbTwN>-KToth^8lb+NV;&+& zZAmUu$$@Bve~I9Fv>*~=v|^nH2ye>yhdScw59%oJ_CkoO%~3&2#Q6+`z9|6+rRDTi z^oVAp!7_haD!}6mQ5VixfDwWu!67aM^2IQM$Rz&gU_ype9Ggr>ASKJyo6;YXc?_?D z9-@UTHW-FU5737Fmb|9Ts)938f)D+*C@b>PL?jutDjA3!Qz&2+MLU8<6Z4Ba$;K+7 z*2%(=-kVu@xKs3Lj*D2V7_E^TTRzl^L+yu++jnq7PBCyFne24rbhf81G>&x8!!0m; z5Ee#{8K071wu3BxQqaP*L=5l-;iVan>E?1+Bmegaa$90ef5L4oC|VGwd6?4-CCykz zY9x)JovmuCHgJ?8u!MPkCX0}BNyO$L^o}5*cS3{3c{b=LWoHG+5G5xSC7yT*0NZTyoSX08@;9WKWb8q65LP}?`VwE<2(@^a zl`b<58SB74VrKFB17k&lYA=#_Ya5>henGeR&!&6aIbg7|8FWPm5Q1}tz<@y+_sdpH zx@zCQTG_)80u*p7Zb!1oZ~_9Bz%!u*_*nFU4k$mjsqks%7u{>X-6wea7G7AC;Negd zzA{1zONApZs`xgymEV_v4j2Od9F-)j8e9o+nh5n9o8f;XU{~@oTUmi@wJ*C$Vu*6m z2||W4mtC1$$!ZNk+SzM*cv?xBdB(>JpFS^Pa7}SC$iC){3*4{{@4~+G<(u;TCs7BE z>EWY&P)5zcPlRDKMk~eL;l@M)y9PFSp7em@3Yn%%yc`1x$RJrJQPsnVkiq-r}BRu42IKYfkD}ZHMId|M|IE zEzhH$RfxqGe>jeun~R;1RX>R-I-z8pBZuCrI7^*TO5a2(y)$ZG@M7Qf9n>vJ7M{WY z_i#FD?xB{iB641=jrHn`i*M3^yA~aFu2AY%kqLLmi&IsR1utUgPV%6PgrVIk2P0}y zji=DH&li$cve*Xwy%BYhu~Ol^(#3;`dn$mIet&^=@K9yd&YZfQJBJ{vqG9#Ihgz%lpcX}X zE>Jih6LbtyLz~{|OfjAro{ld6t9l3mBFt<;4g#X5>P6KLhC$FN+4@@0DGdNQ6Kf(= z(1C*RbfJfGggdv2AaL9Ck+=sDz(B-C0r3Sx20IkES^I3J9_caNwnXa)LQEID>!~N3 zi0%?p@;@5scnys?YxHUFLT%Yzt!7<~`>qJJ*}~UBhSsb@U>k;5nc&qZrVY**AL2yn zpHB>u`?ZTEWFVpjq6t4n#)uq(3^KdgI^F)e5u$Mi`a4G#9waV!DaDSO_9hcSL!;AQ zt7OMv?93EyaK0&Lrfbz#_?JO>f~ZR8x~TTu3~7F|qdYHJwp8 ziz+W|WmSHt2hF7rjAJA(w%LVY!X22Sy`j>$p7z;S!RJ(ewXOq@S0hmASOl0}#VmF3>S+FL4PfhhAduNLYZBlE1KXq;T7O-U zgIeKszh%jXW^}tBU>f~pL)LO9hIgI`tDX|FtWRQY5Zq!N~gOwAntAb!4X%b7K%?_F!XjJ^PKhQzj zKx0!j51HYOHWgbF2oA0rNMF_bKsBk9#biMOu*1CU&649TJ1U9wLkS`!2v{*13Iv0Z zsGwHWojoyGFKDc2p$PkaA7;}!dklUQg{Yqaq{3Ks^q@h_piGbtpwUMSf8Zs#e@c7U zE`2OLcET=A`v=i%Db5hV*uM=+P~K9%`#%0Xww-?b>sAk)foE54CG1xoi7^y^MMh&1 zjM@%^kuX@{CBDd4tG-xseFC<2tP}CNj0kum-DAfrxnQpg3{>wGYvf997QQG@$MdDN0+cYw_zf9xaJOCkhUorH-vuGRv|~-uUx9(I6XV7g%6bnM?02n!RXm@z23)24@T1&se;oU`=Jt00MpDWPJR5>w2Wnm4 z;~ZT=xr^=S?w(lhoo?Ol_T6pp3)J$*t{Bms8T~ooTM1zpK(Y0}Z7+evCep#hrwn51 z>AaxwP(MAE{d;;@jQo||SW9~Uc7!-w<&Zc%Y~34!8uBB=&;4DaF}+GjD{khE2FVbTbn_Wk;$;&UO)#GV-u?p77Zi_{w0SOEqbZs9c@n? zK+H$@PE1<%{^6sd1B{Z2elp(Iy|X0aSH-`2xXEV=@lv7ofS5g&_;>yszBfUq32HvwtoDhy8v?j|+oTV;8 z+!4xT=+<)Y{=rmVdF0lTa=+}v`s7r(22z)qO=A=Ro|5gLo@B5#0j{7-dTE!c$c*8B z_o9WXG`E{4IfIkm5Q;fQSRFixlEOl?Io+@#a7UF8ORbc_E6mNoyrCprKBKyRa^==elHlZ>&BT0P@6OGY%4~}v=OHCalTF6_SB%|z`PUQV(YOnIK-zVg zK+nf{ITBvj_Bt}dzTCUaT6ZZTX&6qZzV4w*;9K#?n|o^WbIUR#J)bKGpecK{&j-xz2gYN$A}-RX#LUx>TN=MjqlSQ3Ty{-s0;BYH;hpR;iZ8vA2Ajckh%y-t{=l zWtShI5|rQYDN+UJanxLVHzaJi+OtxB6eW;kq#T^@Th7lp?V2LSqEJYP$a9~X9j4$Y zxv~1%@N+2SWs)*$v;T3JG0=^$(tX5ErJ%b;uA-WU!r9A#C-*dGBe|W{UaSrZ)h``W zP0eR2WK#}ySWDbi|1M}VilDt0zn7sz46Y-ZF!mHX11x>8pTq#|$}Y{9;RrP~<<6C8 zi1C*&kGMB^Vtfd_xiQ`ll=>=lVYEmeht-X_(r;Xax^V!A&-RreZ8fz!n{7cw_FO-@9ow8s!paF~;qQqnQFDC)B3LZ(!DCd1$hC+*>; zNX2e_Q2h&BB4YiMoo+}JT-&M7WX}B<|9Gfw536+4VF)6Q%;8p*>(1@DKIN6chwypU z0JVPONjEUR%35VH2fvd@+IqvQL#NHpX!Wx7@7$DQ#jxw|RR2*73EUBcnOytauwoUR zy4eDfxu@7x-6>A5%p-F1^p!9+b!^t>^OR5*wT!pF&!i)bXX?FjOJ*bRzf#2-j>*ZS z%7jq&OS*g##qe0_T7BNv9n8b()yXC0dwrP9qkpRGSsud6VT|4R%@s5+EG|Hr-C(Od zd%?(PrJ5w}$B53IgkIeC@llPQAHrVoD{YpzZyVU8^W0`eEBFWxeVT&GmTpnZEq3 z0vvyp9xgYGR?T06LZ<{M z04ihf-}uhSEE}`$K3VjtoRa5fxNkN8))M|+`znBNUp*9z2{WbnoK{*8#mO^?GA8O% zomZcfb?c`bmUHxpKZRdAQbY@Z8DHOt`#X7?cqoP5!s9+n-{Y5x?(g4+)vfk_k}Tq6 z?NtcrF6xmh8t+@Z3B8CRjt_9DaF<$Ms^Ugs zEc3K%xm4ktbWVoh?}g;E{XVEZ^-f5l9iIzD18q5`0mW;d+G|5H6TVE*mwfSEbl2Kn zSsGo|H@&Q#;86^AS$lFJ6bcP#%2d@)FNmA8Om%%Wol>W~kn@hp;L?dHDEQXV`1 zNkg*@TaMW|`+OWTC%l0;`Ca5lq+GZ{;?e~>*#!%+-t;F?f!fvLM&9yGZ4Hx?&kExi z@_|vWXCmQq9{PLKqh0~JFVJ2{TqCOI`0?&{IosLCgR@7&Ji_LZgKqGu^A1NsZvv|u z9#2%C|L;&E*S)kr^#lakBlvpu=Y{2ls-xE{EDw)a%2EYnr_2118$f&6Ph87={{ z(4aj}MBSffrb7q1P~S2c!jHrv-8W6i6N2|O9<0g|J+-&C?iSI^%L|>SkwPOL!&|Y5 zQ<~JlSeBTE#%lPg#^g$?t^L&HCAT9`b; z6smK1%zLcJg~J=(&tNAMvg>(dc6q71ce%DxS}PSk_rwR+`l5pp9VhQjYSVw})&%K5 ziutU)Gzm{R8G`xm`z*{He!KP8VrZHsXjU69ofBw%-K!I~wm)0zH72t5?T9UHL@5_^ zC!wsx0X?5Bn;Y8 zT<{!aVPDk126~85@~{jjkOUMf>v(4ie!95oBBj&3wx91HTFY! zS@6xpnAxfQ&FvS#Y1EIh|uV^*ZT1*7=;$!}AfthGarM z%=|e4GSuZ z6coFd6Q)Hx-rGLtRLlB*eTF#Tm=+{YR$$r>)_7Zz_g;KHzTwS~b*ki7M3T@&^J}8} zcYWMkJ4XkqN!T4zJf9KQoL6Acu0Aeld&qfCDMuGd`>6JiT~FyF>tety>!a1WfddM? z?lIq)8;H-kvuqueN~He0$Dp38&>X$ab-Wc^n(@lTdJhSGZ8z_L#oB54I(0^kbpz(h zIao$&UyG+TAj&z#2T=q_HZELWpa2{smR$JY9Me8YW7k>LF{kvPiE}ePxc>- z5;ZnH7^1s-T5n%om=TO0ro61(@Ey8$KH8={AZ|<^Qgsy0@ShM&UTfG@!dSbbB0g!fu1D0W|xNdLuZUu0Qwj6wU1^1b4 zVPiajYAsndoLdM1@{Oh5(0r2Y?4&lJ521hD?%)n@@7e0GszqB1&02YEhrxo%8Y z-AS4Lg(qbpGV;A`<$+}YD^h9PmX0m68S^jCjW>fZuRKChiwFM~|A86HBS+Ln!B2mq zuqCSg;EVVU+ONv4DX_;yyPjtzZM9pI(X6RMxm>`T-xZH9iC*?Pb*G!f00Jr zw&sYh7%>cMuf`e-6|-BP=rw#dAS1-XFN{BYzL$drY({nG*6E!^;|t6en$!qM@fB-* zUTaOKKeu8%DIZbO{}IHrkzf>CxcFcl5M-qNm{qo&3_m7ph7+|SHByobpe9L)DWSkI z56!;~5l_6pNz{C9?Ha6YwW6*hjoP|c@$8bqoH`_e{z-+0fJnWO%i}Z@41h7+5rjZI zdAy{lDF!}8h^*CEJ)AP&Vs7qcX}gb z_bbbmk~Gt7jUJJGRgq}681~e=R3?kn=(|;*P-2srm%cD!KjZ6IkslAapAe1gl>bR6 z-s_Fi&n>-LZ5xE_XrpLfj+=8n6V``1*c}3V$9n3PS9VbUc-);bg~~$%=v1et*+-9# z?0)hqZ!W`T&DuGucppO~Njd2(HlG683(n<0w`crsEd)FXCuB{ApktEGFwpy+EbBA-P53g8l8ne9QI8~OLjTi;nJR$ zjn8dCsC{~*jm3~=!4eoZGW?+&6@nzYMp#Hqrjpy%@$AjB71L-iGRB|?W>-qNh@uTk z|F!%cz7ljKV84*@+0wMbd2cPO-yORxdVfJE<0UMa42HBWJ1sluZOE^&q;s$b5_his zaBEYgW9ZxdGO>42fj#q|TPGZYgG)z_uXh}$yiwft8LXNtlb_2aOTzbLAw)7sK$IH4 zv#as@hFJ=q^`xzFzl)!_^-L_MVwj$w;77+^stfZToKhEQD_tYKYx3ecx4y4)s#v^o zKe$bMY5{oG)l!Wqeg=8mb&q!tKrF~n;D-qr)` zU1C~;Py2m1xPFYFmf9C;PM@ol?=P3!s#9On*4#hRQ6xJBgkt+gX4_Izun$r254MHe~>Pp3xtb2+%9cg&$PTRx;(3{7*qtC*e}F=;m;T1zmV{S#4i-! zcCDg%+9p?n%DDJ8@-@lMW^ToQb6pKU;W&oecs6L9;P5Nr?n=YeSuOJH(jvHmf}+Pf zA-EcV9tbnQpMLghL^;@f&c*yd}eZVpDi1gzg7QXnl> z@9^f~<_7H;A$n{$wh(D?$Z1ql@Gj!+1mzVLd+azO5^Zp_v}sWQ|D&u7+Bx};(t!h` zN23Sn-@6<*(k9NrCOLy$w~v->$aJb*{^Q`#`acfG+7xpCaiEM|vkoCU{8#ReXb!gC z%*S+_P&VLsmU_>BoL4v|YW~w9Dvz)7e;hDZ|EFzT)Q%%9r^8T@ih=5Faj0S3~cUe(#H4; z13`&Yh;vJym7fxx>;r#@o*I*t5goHM8r{-*xtrr{SVI`7i~|kt1nKJx2F5rCSDJL? zj2ij;c^;H$?0t>keZ1$gEjN(c!Yu)!7-^9Y`3>hNaUvgLR3TPJ1%8tkivrI=^7Q-Q z(M1n}us{?{GH5K>roNw~In}IO$zs{X&Bte4^Qel_T~oy%ps3XtG^b|ah?Ef4Ktt02 z0-jwgIRHc!I_?3Hx543C_#4aGpsRq(Yk&Sc8$X&6IvX)8XoVt;lZu1A3n{IOyO{kP z_W*PYq{%jMmywV?PVu=rq41no3)r&8-ulX}`;7e}_W)=M2q>(QaETd?iWV$3N~v;4 z5TP+~PQqu|W44FG;PPR)F(9XvK~K^`RWODSR7}7Q#h#(SDfqNlsTmErYO+#ukVC)) zkX{g&tT9{4B?D|R&?p0=%#$S*AFF?PVad?c8`Y@=+6P>~stY9=khxe~gXWQLIK!H# z2Ce0NC^k20Ixge^(bgFQr!2{P1aPqdJ>@%zJv2EZj6GgW42H$q<&_>#60eOE2q=?M{m@gsWFsFYtkH07!wijC}A#8VIprj&!Mu6 z1zcdjIwSlPnv@;meEAPCxS~2FLPQWAQ~@21$*fT)g0q*KHTMDg7VaO2Xuz27kSOpp zh-I9XztLgyLr^9l&qUO57j}WI8;sqDWMqU|$fdr*=0zaG4N?fblHg*gYp`6&w~O@) zq=Wn7Q|m+q3Y4L_QZREc-~cEH1yPQoOc5sfX#VQ@zxR>5PA+j!ew4@}MU?)q)U>dPzuRD;$wo#g(b%5nUQrkJuW&Hh{*~FKd|>pg+o%we>kpxa7YQn!Zv2pHg~x)H6m}GZ#4)l!({W__9xj*}@M8!Fyt23irjIt@|8S$GmwmvLc;} z5BM!iF{)VB2y1WQnOUycvwl@b7{%)+wTn?GCM^E{j39$l10`jUno!>)P#5bMR;@%h zCK%6KP~18QClFgqU)TC<_{HVAW%|Qm+IdlQia3La>(8&~_kML>3O(fRJ|!{fmGB(> z=L2rt$BWXE!=|D&0^R5GtSZlAW$D&a*hw0s>lwt#H$2!P7!St9T6l#%wm#CP* zY=Rd-O|kI)=bb+FRqirzt!Jv=Z=G1Z6one_Yce>Un8uW2?o!lyP+d*Ee@vbo178Cyidi72Sah}5`kSB1%NSB(9SH^r7=T?|i3e)-6$u~;(OF=FCig&zi5^ay%}juHI;x%G}H`Iehn?A8{}Og!F7 zVH=X1WD=tEU? z0E7-~1~8tBdMHzZ&7W4Yt4}%dB-k6t-BMkOMGC96d@-U&9psAXvR7L!BI$-z%8#kS zoKI9jjN(ac%|1V_HMS~Y8)(}aiH0_YpNv%P@SXJ?kD&jtR(6~V7BWZF6-^g=N7R^4 zBgCoya-U&i@5r9l?U0X}+sSSmN)+e&{;K2fIuE3ZPo~yKe%B9rNmKLl>9evV7&peu^zYI?NI#{b?Blk_UOqmw~Aut7iGIvk^BfqCxwzoAk!?fVd$XLZd=QZ!)`dtXQx8uIsvb zg@$vA%kD{redM}n8k=R*`gv4{2cJG^W1_-8V}cmnkJONh=OM4=qf3~_wGB6^3#rhn zogBX;#A2v|)XC9g7`MX8DQ1;JB)6+{t^`YL#9joVNo^h=d|pwnazsjs>!>%JY-1b) z_Q>Z^0*y8p$;%)1iXM(|t$rgE#Vhw^>G(DyscNuA=m|_{yKFG2{SbO~^reiPLE41T zw*kh{$-VKy{g{8Xag8|m2<!mHNT*grl8|VJ~KbPK7MCneQd0%g(FA3WkjEhFQ(w7-Q*WamZo<=8#NREBZ zrForn8MX0RnE`?1KovJ*O1h2M7}s$-iSA^~ysF@t!3DQJW_Ewn1SUUNgeZFt6ewb2 zY{!Ht#MObvO;vp8Qp7OrY^OlqYwC6~2--RhBsO;#Ei)ga(K7czujVMvNCeilEZU5m z{XeOV-9;YsNy%O-#4g*D?SqkojnPjQ9r?S^;7?L;>wGP{MYzUt1J`y|##Gy7XPLsr z4Udj&jpF$=Ugx^9%@$Fpl5Bm9{&|}gV_@f!*YjbxeW{JoPZpp67jE%xpYTVHP_=d& z^m(e#(SqiB(hrse1K zX1~c?MmTZugT!7336*tHK0fktm9Jff7jU_mxS;eg=4JI1%@g0Zb-%9%@{<})e0R|( zV)K-^feT;(Aox2lmd~6BS)8mMxQhRLa`$QFV3}TAB5HR$(Zz}%C0GhWCurZ<4RZH} z?7*w+b2tU4oj@*hfD*m3^}#oqsn}oVoHp{cM?}y$5=>1|(m>zvw8jyrR^fa7tl=l_(jN=LtGTJJ+&-n#1IhwT2JMOWEh?99*g3nb8lnBW5q_X}A0fG~>kHw^k@%Xi(gOuR_){wt* z=m@7o{y{+R2udSV;a31sSZg+z1m>>j#xrQ36bs zfy5>1bgA~CRR29j<&3tz z=?PYJwc>`>1-E->54eQNhAU}tKNM$ONVc_Z0=!rzrASKcWnw95_HcECKiRM1p z)IP?oJOh*bdTmgiP_AHf^`ZPo2kji?w-L9SgS&+rGoa)Lsl)TKt<6z6n9 zc(eT;+k7l^!AzPm-Eotzm?7uGKB5d>C}j9W7p5lBqvN93#Yk9=xN=&G_qH42k_%x- zGGdjUtTOe|ol$~7b0>g(%K~ICmyj$nZ=SHcIZ)Gjf9m3N1%Kj)jW52~F{kgQ;beTM zP(lJvS^?WNhYeTXK9r2^Wd??}dNYCcH_<5TCnqBBazzmi^5Z{tlw(4AvoK|6>8k;l zjRGj`$1Q*JIjuSiM7P1n=RCbcN70V!Uwm~X% z9EO(p-qQ28&DInWa{ZYe#!cUd zGuqzVim-&|>_E3UY$76zXuQXy4ww+E^?CWwt|*bi*VGrWmzkkhQ|S3+K-ngaY@ewG zDs=E&jnFB9RKZWT;dk+X{qDm-4YN2B%i-UzUO^?J`-*WeCEp)#>;}cSvH8(4qqQ(E zpRfK}hpWD+m^WO+LH>{w69TzCRGi3o9YeRtlcanp{vBgIYT_)#QX8!MCUg7e1s^I| z87(ikli0Szl;FS+__Duf&VJH)ksQ*MOJ&u%w2SJ=+|bRx@{luqVJI1|e?#ZPuK#x1 zlNl9Vkkj^&R$kYo>#%?s^^l39us694L|&m&C)^jP;N;ml0V5Qw{1bYwU_TcY8C>L@ zCaxerNd!H0GQJe(cpZ}q9f;?aKEmtRATCy~1FA&*UWA3blqx&uXaX;2J0?#LDT0#3 z1{nYc9^JkoQ?>wulyHu9C5wGyRwt=M3Y2>B#oohD_A4!BJb(#>ptIT_OB(*R#|?eU zrPiM6HAX9oOFM@9SmJvP09@@6t z%8_u85;gL@1IV{%wjZkmIuPh`!%r^Wt{|i=4cDJ&9G+|wg=*X}`J-Et;i&fj&gm@J zj^%{Z4Rz4lhfuwKFx7N@k>qHOh|L%C_U}Ftm~kcN+0iZuJp(Tuy$+6XoJbgXMdC1} zG3v!$rh3eZKQm22yfT%$>70)urerAxxl%l)1oOA1w7y2W*d03V96=2|tr@MFq{%g} z2L&E6S<*i{$$Rp4hhi~BqC^}LkZ=C6UC@nt)U60uxduR$WUvp%8+beruY7N8P z6^+m;GH%`fv2>0>nlxRvzT38KcTd~4-P1OvZQHhO8`HLJ+qR7}&wIXq89R2>&Zx}F zipa`!En52|bg?d@nybrIZ{K^5sC3p5QeR|sA0OK9N2kDito>;cEb^)Fa%) zjzzh{{pn)S0&b$Kv}wD6+UGl;QjzyxZoojyf^DqGCusE_-*0>O$>NKTw>MK%8*7w& zRWEQ6`G#jjc=$o!5RV%aCp@@Mu2NCpRFynE$abv0kuWXEati<|6kNMYI9yMRY<%o1 zc29``Aj>bD;6iQ*N)rj_`ur}3S$-6+@LJL0{Pdz(J*I}GD3ql|{-!qnGl@zNlgrjO zjUGP8(_zpC>%$^^f>P*oeR;J6-}TSSEc=gnw`I(53-a1EskVJnfyybvuPgT@!W|m! zxwCqy8=9?f=0tXBTqbA45NzS?x+0=3vMWFIhIdMf`Z=Q~t`AdtPVHzhN7CT^G)6_@ z!8|d*xVrWpKkjZvH9S6=rC5`j^<1`O!GB|v{jhmD&cVweB=dJyqPSi*AjBdxCZ_{F zR4k2De*fbkohxi^k}{tzApwzWoipjd>9-dH{Nvn9xU=(<93Qg#^1V+^%g0a&NxRC> z1Q7o6flY(i;`&UVGN5>wolZzW{nB|^ueuA$6#;+^b#xg0bB)U3AMoP`E)7FPM@TlC zQNcLR8e*w_I;V0i1&M>AKNjUj4-ZQspad<)3gQU$9ewGiE;|=2HwIxwo>ajel!XK& z=nyp_fG8(fzS9Re&q9#glD=I#o_P{}1@P;;0zm6#QRNb+D-uW{Y= zxCv$FOy;>!?ml9oF^<8`)R3NTKCojCV;@C{CD3n)smz2zs0fLJI{^LJ^`$wxt#Iu| zfoZ)y#Sndlcw_zdGy`yGeKNIakuD}Q(R?5S7n-3BHx@J99sC4yJ~D|&16o3e(v^*1 zhGNB*w(ow_lw1=wc2aXN9o6eG{1#Q~35C#xKP4ZM&7$GSGZd79h_^bGA$ z<0T>q|1egtHCS&J#Gw@=oAQtQ>dd6iuOzjezc^#dx!LfH#h`Bb!2v{}l3=?Ors#IO zsfIcdvH|*W6YX&dVa^Ctg#g5!CU6Zg0wqE`KyFxHiThtkeHnldaW?fr3&vTlO zuPtC{bZJ(JM4Sl9AxI7I^4>B^z~{Q<%FOVX?_UM677AdMSOFs!^clqH=@JT#JhDV0(E7{Y~ji2<7?wH7sG!}ZJxTblDbSx zpG{0n?N7`5(gdi1M~Q!5$25U48bZ^|^C!j_GNbn=&PoiA4$tId!{gr*n#a+Zsn`sl zGH`u+j=fFGx;yXvxD?Pb^QXo)b?VS>@FEfyF#hdW>Ha2uIUxXjD(#vsg!_yn7iM_UoQ7LNC1wfe5Tman%e{@+d}=zlwz ztN;Z)m1;^s042e9ahi7l5H3VV{79G*sgA?#8Z%^eo6JOJZZq6c#fxp;JAkmv?0@bm z(1DF^OnL+z!7wxZe{U~+I&3iT+yYx~I|p0(#w0NM$)3^pZym2|lcu|^!KrSQnoRgC zg-{&obZlB`;!%#K@$iHhTjMs5nyt~KrggGqRoL$u1_1yPfQT@_)a#FUtBA=o5~4th z@06!Yi{l0<4&fItF@PL`deqP*))Pa^cEc(!z4rPnl0PQ|(HKbuGyrWUcKStm<)icf zUnQ-$ZQP$W7)NWseoP-;sxeU>_M_#mp6kk%L6dyL(k(%kw-%|}bw(oh% zm8V<(pL|}=7e~GMzfX-Tc$nY$g}%!>!-spOsr5}it9&>wKR@4~cc}hg+d&A`KVUav zZRMS`)>|%j35bht%6fX->{_`(TA7^^4I(BvVd$9Qm+R%MfrlvQ1D%wzfg8llay%yM zZ;{V6a+y_^qjk#meqN>TuuyJoJK;^T(_jaYpidc8O|v^I3%Rw^bLoqvm5W@Rjq1a^ zGn4s)IN9Vx5!-mf7d7Lo9==VUwmIG3?#|P&2)hd}8eGAsag;ZcRlSxh^i!VlLnpU| zPd~-yTu`(hjbgc&x}v=-L#*GGqu~<>wh~(@*LejmjV!!}xftNxgpa+=XVpZbq6JO+ z+w($FC%m;5)lXH3UXB%{@@=oXi3DN>2AOVKVmNjKfhCDqDIS@X1(8t?!G>p=Cipz9 z6kC%3K%_!SvGDbp~Z&Uxo7XXWK%c)Y+vAyOH#CQ}-61E6hNTm&*F5h=JG7ioLi7 zBm%ukca5;sKgy-Ukr*BXa;!%X2rM+T`rPbMrCkawbII}YJS8d7E+IcQDVa{V8(&(= zt4X~+m&vQBQNASrn*%zl{EvjW<(vICl*%TZSETJT8m)N*p#r&S-1^?iKL5t}m!WVF z`k9%tlO6qwM&u7>8YG|t~-C18A=pvAPqH}gW1OJi>?_;SIe>saVnVnStn8WxNr>&FW^ z`+al7U|nt(4;@bPqu9VO_??5n8vZZQ1Bwml zS5EfUfygHKp)?a;r&nZ|uBc|4{=x^vTilcx zxZ&X>{xu^?EC4QhE&-Jrsw3ffwk-yD%vQ(>B?aX4m;PX>7w|`1Ep=J`K)iv_|RwGeaW-=h=nk$_>QNKy`dc=B z_E!rIt|wY{e!N_a>agneLG(ahsU&-^lOyhX?{TTvA#uIeZG-?UmWCL(Mug=py48jT zQHToT)M3T^YSH8AS65rD%_bKiyL#22qRjAI@ZF_MKmNgRpPsgqRVR+^2UqP5{({cZ zsGC&(qkNS8POAlRrmh|D+Tz##$O9p=Nh#-~{HW}uKC^Kak zhSTFe!{*Qj<(-L#i^Q(!JYO|~A3i-FD(@xMZbo1VH&lOul6Xx~m_+99Er%~MLLKj* z6TU3rWqZx^_9_L}Wf0$ZJmY2eWqVj}!97wB$6(JKkLN z!-|h6lz_5IFE6-VAr^KH7xKxFYYX)Pn?y%!yW7j^r<0#L`uz|U2qF|Ssu;bICbkxDWA_pQ;xVf^pcoGw#UfciA z&caj$=d904%CpH{3+abD*D#X`7bL{v8wzoDxGz#L6Qwr!AKXyt$|9QJ$R~MmhfyR@ zsJnFl<}j43(>d3J_U$Bz>Ny9v1L9iMl%BW6vHYJrJiY1wm(+k6QhudPDBNJ%Xn_nf zcK*}yvi-wbYlWQBmukUM`aV&v73W?DJP7@S0t&I%SgH0(lPe@RR}MoS2po$-0^IUJ zm_0&ABB|)}`E#q|Nl1%+i6oW8<-%d$V!8HeMINH>)i!CyyJw6hvGxVIi)F~Q`&WV6 zm8btK)BBJ5bIIO4#j{gK>ZKYAH#`R?I7>a{5qa^r0P9WJUGl!HZY=PWm$w|8wT^eF zRXGl0+!RaQ<59ThjYRB2`OO}y(ZRzTepJiPXhiD9n~rGiz03AVsUAoc!CZkajyIoX z>w_=7EG(RtdH-OcEQk{VK0_f%ZuOgQu5}V&d&_Sdi5r&4rkttB2M%CtSxDxvSS@9& zNX{5BHqxE6dPC;09d08%HO@0JBTNI=kQh2>2{bukUxGK#h0$_Mld`CTJ@BioPAJrb z1ltR=&6&Fqw^y8X2zfBPVHqa)Pw85mZfcDDf@}6M=y++ijUkO!c=#`>J2Z|#1A4n_ zd~0YZG`k)FRSii3g8iYsiM`(GxN0NZm@1BQLKNy_Fu~`LpW-Uv(E!ax$^t=DOlv`aXz2 z<#qZe*H0E zFjm`MJX}}tJtw13Y4;d%rN|JSlVu!xK$TF$no`zhqF@+u)(9yRV~tHboXv|043ElL zQdCb1y`0MwA-(b0j-IM?EE=fIvcRjPt3!z>!qPaG>GQ<=ewo8@Fgo_FUv2Nwm_tk3 z-Rk}>w)YMG^t~^{^v&xN{;5m1`OxE4>SI%G4{GImw*sssEuU@^o}u90MF(1vbx2kr z{+FrG%>0fz!tp(uyKhdjt`}Ly`sL6k?)S6-*+n$^rN z=yM~n+I7XkZ=T8{DVJeSqiP7Zwi)GHbuHyRjPCRMvzKO4GqZ17F;J*cx;K2Ir(plJ>rJDPF}>8b_IF>(cj=*+1q99@rO%8*LjWUg+R} zcdwqjc2|H^mk|SO<1C+CQ|ZI$zXOY|g)2_Pp`T}`d^VuD+BI7 z`D_QP+rJh@kVZp9@Q5n9hFQ|p3`V@3hLu%r1>02!b}AO9_3vZ5?wx!*e_DPPAdzC5caf)wDtgLWS#Zi1LylV7@flK-O&{lJ&reLoz#B!Bea7=yklY| zPvppc98Z_b*ml5aedI6R5Gh6#j58Ii?GK57(0{}pUL;j@^;m|2kw@Q7-zr`;-AHH~ z3+kIR>ZjWCJRU|E`%yF|1ESxtGEsL&D`Dk?Ct})R!xFiJat{x+9zxu;PrPTXqJMNJ1zW7HhVRN%eC#X5c=8~B5`}_06*-Ef7hkwkK zgXms(78_LY)}-vmi{3UvBj^GC`Y5WS<(7}B_4ej&Cz5Ll?JmLkrZKCC$kDEVX8yoH`YJy&xAMlslz{hO^%;7U&r-PqG?ur8A7w{vd42`a%$9x zksfJbu3m04AARVA+rR#08foswjTi%k@V52x+#ziPS;NeXlWGsgOEOUrV0k~|viLzID>{;=wbFGjy#K6wBy**oH{fJyrlo{WmWW$(P0X0}o{(Vh9vs>-m|*KYoy>s@k#;%yhG_=4c+@OwH{e z%>H{qomt6Ns4B3O|INbotoj=y74ZMHo3k=ZNXiiZHyf-ti&pYx8Hwyi9he-R%b|T# z0ITVN5*>xdC&s+Mt}Zmzis553nUxK?Z+J(L#-;9u$|2S{Q>AxW?Kp*~>564oG#R63V`d|Ku_3z|y(7*rk^-+AjdYT`URf4gLadp^w z8YZsqzxGOPC>;3?oAruE6_fB>G>GxQ5^} zCxJET1cX4xy>KY^pKQ}c-C!dQlG%IEzdJv1!4*g9Gk^`lBgb+5hFzvb6x6RqSrg;d z18=J>22v4@5YAbxDJv>bfn<2wUW4J+gJ>fLF@Sn798T92&NCI5w5k7vf>Xepq~kFR zssKI7QIE=1oG(G>5A}DgMlV%d4e}49Vd#oZ6e|c0w1yT7P$fD5Ty`Ayt(N3R3rhWI z3PHGy5=y@Qt^VU7^6u3$7>e@-I? zDjH&4UTRi~mLk6eUGRKt=F}oa3)*Inl8ua6Vgr#{i$a9)dYHjqQjO7N$~0m7RfZj3 zQ{mvA31Auv2{bDxhzcy44k>>%$jB6S9Z^11L9ri^$%jC%syoLowhhvE8!N1dDT2Wg z1lLdZgeJ^9$p0xr-_blAdR~5$g1Vo=Si@T~=`Y2Y;oOKKv#ia|O&rgY8bvmII{-y< zq4JVyXGYK$#Sr8hAs~l*>dIZlgJdlPR25W_?(K&oi8fnIFhoLt^ZCzCwrDl#6&Wp|HB;rsY1l8V!W+Ph>Db7O`kmylo z+!2?Ii3_EJL@rXI(l%IM(k8|3M~*x@kxPm<@&a2@Qk+`^xQAdPVW|+4IWm-s;nI`$ z*iqHo^0)Eur6c|R19;(4SHYli{0j{JXF*u-q-&7MuYi^HkY59GQOyi;Xh@Wr3Bq1u z7MfILFa?bA4SbgmGZB6mlyjDmL zK3giLBjUsC3?h+WySjW@278C!l0)wC zuope-mhX|um!?cm;L}8wyl57MU1;EhFv@t~@d~BhrMh^@g2v+GeFbRd)huz5frDe< z+;fR0Ia2SU=Q#$OiXA|pBT@>S$ea~NubUoY$EL^XX9rK|wVz=-jIBZxU6_CwP*ngk z=S{sr|7VB9{=C-aFMJ>x);iqC$}%DYS`_54sLoFU4pWmvuB zv*{oKO~%iBF5M+1B`Iyfrbj%K^RK#(xXIFW43I=_LNQC*@YQkP@?_Xu-)nK90A25xgtw5SrN z^7Z-1tcZl+y=k~Dfrh76W(T3Vzu6n!UnAAyfYBx4Pvl0iRiR;759CQJO39RezOuNz z{~V(RBZ?fMLNX2>Ge<*C%y<@9Xy}t)ODN*$CV#_#)8vRyK0r=3Ez*dbT23eGJBEf( z)Ne1@LqZ}7u>N(81V0;cBdC#gkHnHtTQ4)`T@~JiW`cJcPm17m86u4CBKTT?w#vBr zr0{|E?*;dWQ%ANT<0SOAxL+3u(+}4~-uFaz3m0s_uC5!1F{TR&G!L)$+mfK^===WQ z+4f?vHEc!}3W5UzZq#uhcFCtSED9Ua{v&Sd^ZF}t9$yF(4n96giRq<#AedzH#Oh5< zazUDGs3cchZ!K$3G=rw}AYsSbE_hfesk0P#5T|Q`Mk?w#vrY+AZ^$+F8pLbtV_7im zg6#USwFRKQ0|bFd%-KkNlmtx|ck2&H6dDgo){Ooo{Peu@rq?#ZZEG;jcoTmTa#sz* zVIgQ^j%VWdYfQz{$#>5`2>(8m!-%lrC^lPvT*ecGtlwgqDRo zN!NnsWG>r^qhyZTn*V85ECtsx9@PL$P_Rtt#8E;4h&%vgsbjRVI=y8Ly9Wh4#|BY5R2X3V2|PkTkyy4YS?<_0@0)J?fgsjNScoYK0^mb4)SrY6gbIt8RrdYWFoN6UYk3NDuQFdhT)V;=i|!L$u0(<6 zXY#_MXA5a#tVQpA63ja5vF6; z3J)~f?eq(bDpDA%BVuTp1jBm4^jkJz{_+j2olfmy4i4}`qy5t)ZEW_0sNaNV1bQ-Z z+5h!U$k;qDCo+U7t)EGru1;jh=pZ>(E!M=Wf&W$3{igq*4@*_YunIjxND2v$PRNvX z@=?P@wq- zHcPB24VA-b*FGX5L&#U(18sI9-@p2a^Yj|Q=}5+)GH(+BWbNdB4&dV~A}8bX4js|> z>x5IFZgDv@3HB>N7{(-uwp{I_h>(Sbmxxnf6k56>beA|qtCeC=shzo@*3A(2bB51{ z%8L)v38>%-10P0gL=&vo5UyagSxHbgRx_3okKh~S)%oGfXZUpevMniDYq%>n4A4Ew zgW%8$tUYpExaH*q|MXQ|!hC3YK!vUm0u`1*2?S~6)C5eAvQhYarqiD5FT)e z?5K=WI3lX$QW(;x8N#~<4THj4XO35n1|KVByEdqK}fY)H~1bEP6?8~lc z@Ev7IU7^mkWnN#Y6S4uKpjiPTz&td7fppm7?w|{)eNhT1P;>|v%zQa3GW zf>~xu5efqV9?>aleomm=se3)ScJtWCa5I?VLZLb@lE_&)5?Izdc1+mL%lB4a^jbwt z`+lHhnz2|$*paTdIt)8B@&NskzVnl$$9UGEbo*T>bt3p_lSZTKQ*TBiJ;#%2;l1Gm z-;-=Ub^Qe%bxH5>mWT48$Lz38sl|78im6EcC%^beobNffJC*EX5j8`P{8m}@SXj^a z!J&5NU8r^<*!K(?jvnIs6t!Is{riZrv1jsUK=IVq9rU$$_T!#{FhS^h!bN~2_$!sy zUm5y_9vcLh@1ucR0igeXe_MtB=|%v0DmB#Rz{x8=zioP)ixJHkpa@+-VCk8Id#}HL zbCQsXCtRrS^bj8b14P7BSpWc70T?&G5unFd*jgwmD7IbMSHBkj9W`KlC_WSzH$c8X z<4kpBNbWX)`tHO$Gs7%uZl)@tMbTy}Q78~_T^Rl&!+?m;+Sy+BJq??cCQ%b{F32k4 zzdvy4K!xeR0O`6lRM5OnY?96uwsqGFIZ5jiF(6<*fCyYKDY0&~W#!5TpyfKr5{TqD z&yC)wlk^Hm_c`(t1`*+}f?+BW1h!0G)Q|Vq=6ZKT$JgcZ^-kZ%O3&L1)yucqVULl= zgAkYBFO+#RizgE%)<#EumP}uS1>2M9b)U0NSo-Ym&F#{k@24v|$Ge%Xp3m)`uIw4F zlN;~r^~~iR)9FeL#D;*}#^PxgDZI(K#-x6Cu9S7L>zovzSe1%Z5UQ#GSBjpXi32RX45T)w6)qWkzPv~)fQxw%+BO^s?@vxL<6E=1mTKx zS=yAwzwca!l*Eu^e7YoZ-6L~G&9ne4umK@F?Tn5DcUfv&7&w&F>TuvwywexoENS(k zyFNvuYiDFJbdRnb5FMA7%!e5{GtCG)J+t(Sv{2ra-}a^r6Q9$>Dr>xiA4I1l6}Lq8 zBL+|ick3gWBeSchbd^!2X{CCLTXAwPt+RfdkZmi5^pxZAWSUgMTQVs}aAB>vV3OU@ z7Ni-0rgEq-J-4v3@WQVD%mKnC#=xaY+DI?DrI_Sp+_a>O=r0}uD4%NJ^kYcwIE_=V z++Ly?6$MN61?jl1rht*TfSoTt_R4_*Z5t^KD{jcKW$y&{3UrsCkdSho#IVhY-M&Pk z_BL>d-Uu(jRc;+bjtb*5&ls ziK)fIv#4HwZBBdD1&LksG~wp-v~aO9hxq1n(BtDNjwE5+5f$b?dDQc$u9o8xC-X{y zv6A(-$4|&7G83$$7CG*f55F$D+3`eNiS#2+NkC5yG9pJok_GsvlC;H!ujUxkSfhx1 zgn$~rq(R`C9%VR~T)Y?hNkK5eXL`Y`g)l!jOwevYVPIH|`te5DJVzDmGXH25`O1mj zx7CUCbh`GD%`Se2E`JZ$_8}u>D&Q2NRH?0Nb6g5vYD?m%f?EN3f!17FGqVYtmx?a? zV>`sceH8!ggO0mGbvot#m}X`iV4g3ZpLA=Ud5w!Lufw9&^dbNHWwsH7LK(mapBGo> zh^*GlFV4IAB4C=yeUWeFKFTZsJl76k)HWHwyR&)JJG7APUdR-rM5(mn z%h=2W*-40LPG0o1hjO;~?4y@5RxRx1SD1N4D*S9DwIDFlg5McUy`g4)H!w-?%LvM* z5a4%21Mf^PT7n@ErE!T?dO$MS!`!#m$kNVCRA+)sA}2BWae1A+<+T`7uct5`b&o{{ z{HKQ^|DUb<{PgY<0(7z?L3&_eQV@il3kT43>8DvPw^O^MtVI|{V_{mBXA?JwvODz& zTv|w@QmdQX?cXnJw3ea(E(ie%Pn|D0hbll~+n`_EBzo`NsOa2W(j-BsIHQN-7IM5R zY=i7`z8Q7&TNx3D=0a#6f%)zo$!++ggrKvg)Yb+`66lz8c^=$93HWa22L1Y=IO%SxJEg0;ASPw?rI7OPk&F=4Q~jZ=hUsl7B~ALWK>%P6ab3`g8CIM6uhYBeJ9id2 zLv{pbIn&Y+MHQyd^C;el>Qf|FzJIgEaWUDf5dRhq?o)ak5`^%Pw}x(!Ca7Q)waC#I zb3Y1g3$!xGeP>Y9e2J42n`bg(+*Sl;*meFSX6IRXuwfs()0!oR1dxQQ`t&umL6;p< z29MeiP3(k3BlFtaZ#akj$;ElWJm7ig?UX#Wg`6ew1@PFJ4I3P8wGQJXJO~8xPdHUk z^9!4kFg4)x6eg2;#Smc>Ltn_x0#g`~cbF(>XX zk9>@GHOc#9_Ths++m6~-Cvd=R#9H;(1P%J|ghlefMDZiS1N zcUuwG7r36vS)Oc}L_!X%WH4`<-O+PVyY-R9RDOgpu=sI5ts9|a0=+y`FxAeF$^h3} ztc3_xYIV!m5r^C}SF}{(9otgqY6r)gcA;{7ZffFuc~=zgik3=+sw=v=p4*TUw*WK( z?Aks-ZjT<``NYyx6>D$0jzDPz>v8A6R59Ivw%Aq0HB}zHZbuKtC+m;zeFrj%9$usK z-O3f0E3R7aHA(2xgu0gkId8HR?kdS?dVE9+V@wd_tK9ouv);Z@;&0G9QRY)&t$WIZ zQQjoNgh*sJCM~(I^!jZ2LQsn-Jk>s5IC=6pV(;Dgst244l$bnE+ZnuAdf8ibRi5}@ zxtOV$*@()DTANj-BnAWD^b`EAZ-+roXH<5!D+Kt(b7bfl9phgX?>9)HWtu`cK(#FYu!9`x;i?$1WTej<; zFYrJ7a^7qh_5l=aA`>%vxwt|ha7r;eGcF}Ld%=aKV9sY`HC|Y5%g{5)(?p5u2trhm zwW=zxNNb<1svoBS-)X2a`(tIYA)fc8x5g%!UEQmOmi}08KMqVP{WVaQY!T&ncFD3V z=KL@fI(0u6sn1&svQ1GW{t|E#wHoGcb9Uiu;dnn^nlzZih=Us}pP#I@K>ovoz*x+@ zdVW>3RR8B=%Q;*;m4We1^!zG5?6=()B40M?_G6yu$xOe-MwCkI@&2_MUQijBZb1Cg z_6}t{vzD4dGf$&peT9m$v=%H9%TMXCIn(g)PudX_*3Kq4)5=BXWD~?dp{)e6N(wku z>`i@*n}?}>W&QZE?y#1=Q&aimSW7l&Xdf6#urYKuD7D-UN%&mAn$&kN-XBcD1<{rT z5Tv99i?#Ec{2s<0u~~|uAI|Nnj_KMjd+oXjU&~L9?_*zq9K}T`amN-j*jn?h^L&d0 zX4;Il9Ee$AX+ni9*14dwp{^m7Iz~^edQ&q#%EvSx2y}5V(J8*+wtq+8kZXKBkjMs1 ztU1Hda2Ajou40_|xV#aCT^D@=vdbZgvT}aDuVP!Ogtj%4RQjhy&peARYJ#PKe7LL`x2wJtR!#UQTEQ2&U z?tMoAyo(IyCYAI9tH!E}@mNkY81fdUX83PWY{*}`6guUw^aR^6lrW6jWr;!@_6x|(fEJ(d%>hihR=`_yb;;+wyC z8E#+Ys0-q8dMW!}Ok@xEYV<50bRsvfKyki)9RC=6HAh{RZcjG%2^XEi7}qK`pA}}Nt*>!2`V{DledIkBpuR*T4{6)S=K3 zc~AFE&rB~~il1FvIO7WAXqlWln8x@k9;6$8kII`8O33FFiBEGgJ#g%Mp9MjMC)X6+Kw8YQJgWtd*#rDyx*d1g6~?nnH!Jl8gpQsbH3uZD#*Qk6l! zBlb&LHfzfC_|u1^=}_3; zwbi1s9mFy89|77cgxo@9274nnv+z!Rs#~wDdT#MbKG1_D4p_Bfi73#`GvAuQO8BDh zc-+5yxM(_+ORC|_UqZ_+e`_O-frc)mlW7)GYey4Zhi|8<_h1lTjFcl>va3O{z_NNW z4z~~zcx6BIyzwRyh6@>wQ099n2OP0JZVj$Zb2%-oqL3zWw81GTo4*%_s%Q4vBKHAU z7Vs+YiD$Mo!^Y{UG??wLibGnLiJ<3a1D9?Y{#^Ez!}V@oU}nUt#I1*xf#h$)Y$YL3|ChS#srWuh50^s;4t!dS2g#qx_1$sYJ%UgNL{Owny@(T=Gd?eL`_%PJvPf`x=FM(DpgpyCl zP?7TUr7Uut7z^5f_rjO5e4ekGj{-gA6W?uWWyu#wt6io)w%+q(_`(kHpqhjin32^Z z9>hZaCnSGpHLZ;93JQN;uc{U(-jBvvKL{ead-QZ?p4u64EPHaR!-tFvH5rR6m6^o8 zsQYI#j{JZySJOyI^ZRc{2c`{@orlyaG#z~@AK0B;@j5<*Pq%f~*2sx{sH=FPru-hR z<}>YmPh8jk7Gx#k1|53C6w;Fu%`VJ#{W&{ub!Oe9qC|fad0>7z!%{z%2^ZgBJ2Dtq z@1Q;kp*8w>@9`a9?eSFWQthi6hBPjh@mGT?Lqls{dcl0X5kubu(=I^lYFn?Uy|^|v zrFvd>)IRuho$IjFsWb`08{ldH_ZdP(J93AQ2iphIZA^OiBwqEykHi0GfVs?Ua)cK} z{pdXeRYneC8%(}&M=brU8Q+%ClDkE<>?3=cJMXkX1N)I?LIBKsbOiqBd(Px#H92m^ zOL>wThd%mvUcc(#LLXY5?|;(fq$}h>$zzh#cn#r*{`^bbnx)@}%xg^8=6kiKi+4qL z-PuPceMV)7w7b$D^M#5uY8{XG{Nq(zfhw;dxc&u%@1rI3UfOhX+<#si9WqL`{D6nu zFGIn3Nr#>(?C{o_xbOGVifxIF(Yuv9eUx{pGbZl)@R*j5=5w|34|HvI7K1=+M=eou zYc>bw-z{1{V+7HD$`7?G){8;Br-H#|K(Nh6#V88@bB}0xI$hy0aQRQM$iVK8&bo2L z?pWn*wg+3Xi^o*ulr`aVZceU1=_S*#W-qy+7(u2LAFwgwltDSb2 zsxK+k2!2>q69=s8c+ZEueBeeBy0_oB6y_!(qw8V!fU} zH3EbS4Ew_;(@1Tme}}jIA90YOnb@h}p5ho|ix_^QOojpichS0B9@C`Fx)erdWdF)l zX->%r8^iZ=eJ{h}^rO0R00;rWvd&E~OX>K0& z6*$CJGJ(K_PX@tH!VATOJmXo&sK+u6c8$}!l{&LH{}AMsSDbWy=AAkI45oVu5dHl; z@ws=bP5k@Pwo<>{YFT?v@?_Do5#j>N5pECzchBa0P#&GBigo58s9HfgnX}K)lAX3! zM!7$a*>ZnE571ub{R%S)q!sGF zAwsr|@%^GFI^~_tW@t6d)ZPomvVqO0DJhe<-iV?s>LHk4dwqSlj!q4jt#hk4n5nL@ zLeVviM_Fj4%1WG6h()*XOy)3VgEDxrl~vjXU(`7(VcuP_q_pKS%p zPagBq>N!pO?^(CE6+v4?ka>{`!v*&5c^ZvueNi_$_I&RQ*qE+c=$Hy zFdtqwSJJ!JS&p!Vv_=!1_l)mQFF!P96Pd~zV%*p6?giR@b9&JFfj+eV=JYBOOCWq< zEf(v7Y;A^KQI{xFae=%}^UqJy<>#i>WGdUub%5y4wyWHYCQ>z^_apZ0#H)LW7VFz$ zOBbVj?)?=T^b}v*W;CAlCy^*fn*XH$AC*7Z9`xvF2abPxhu=l7%}#$9g_Vu@T1!(? zA}&FGpDoMD^1jdic#=NgyN)~xF(Z>a~TWoMG2iQOT+;39x22rE;??+Cub<8 zo$7rNdWI!Oz@1T)8`uw_encZc5#mJcutQ7~PKu=L2~yx0aU^_LLS2+N8ERQMYi;O4 zubzn8gd~I*)*a>)U2yuM2-=;B=%33?fvl?J1_f$SJUnw1KtWa!9vH2O!EY5Nj5B=8 zodS(&m^XSG&V-@Hsy09chG;iXAY^hQ>%@PM-yi z@DjQ!Pxr_t^GP77-6Jcm20#+3BotO)>Y;R?!tqRH5 zNK7wZ*o6hhcY9-jCgBm*Y|&>JCl}-b*(5N(JV%;UxpUb`#|&)Mh;|DZ>X#vrMb?N( z1BSfEsTsb31n7$nPy#+wFldwV$WHkL%q~OxsTw4>>XPLM04f2=GO2@hy_jOywmpCwYE;V<+TaOBB#w+l5ZUGGKA=C{egE^{dcRQ{Gh#_12bX+<%pu zuunfcX`5HQSwInb5F;>mg=F`)T+BS=Inp>owPT)3AKbKh(QhL7mV~Oos4R=$@H>~9 zit;N$(={9gD>ImYoaVZ<0FEU{pXn@WPE|*a5$r;!EY7$hq$*?0sH#x`R?xS}Gqlad zu3jekP|fQgWOdFS5REN{-SgGu?xoV44lo2IhF<+p3SW&^k$ z8kp$vNjjseo0T5N+Kp6={`WWo&+`}1>tHP*HN$0&sM{(;Qy|VQyxY}A1ogCUU|&!; zZIW~#R%4qiJd!&75iES_U`J9P9kC!RDd;F9$ufa16&UAe%B*GN$dF${dTK` zK}uF}L*(Dk3;~T(^#P@{2NDyhDJ}RxkZ6q4j{q%>YJmOOh*%!nObb)wPYfnSOa*L> zNl9iN1<@#Ur-QnXi6$85YD;<0PYw0%>8yUB+8fLeq|nNLrPk~Oq21o8(C@2jO%deo zhIWBI>{QxBKUCSf8!+-Vofxa(V=WwXs&~iK6lm59q#vf%yHo>W2?7m(YZd&2&?Y1Y zsX;6SQVZMiqZxeYMKkoegKi~o1_0>SbsD)d0OcK!wQEr)UkRXm{Fk+)U!(@4ds7AS9;TGL72Y)t@}@PW)}ds9|pdjeGO(9zi&NfE845?2!hf?C%pqa-b}I9)2A1a z>uRk)fiP&jLO#Zo_6z7;dBgq{O8Lo_ns9!DI#kAE%M}V;?NyO92G$6ahnfq+7)&cs znK$f{NN+vS4g7w?{SS`twv{LQ5?D53aasx()?egC_cui?JfmOrkA)&FXs zeF104U-bW9)=!;g3wR&~MxGBMAvnM~3oyV8U}e?^`3)$u0r2G*(3E|BgVu58)zhi8 zFEx+5>umG^ZFKLMN&ZSfOC`MN(SHL7Ai?P%0kS|4!->(`PWWA5y6n~mJ@$BK{T=(y z2sirIXvd0vH9&u$g;K9>zei}i`aAl|m_7U=VL~vH5Vlak*-mJ#!`|$B8m(?&TodZ< zy(9CZ1pr22`{vO6s0JvLh!7o}(B0PYi)zlw(j2AVJ8t0y`r_Fcg7N5r}b8RF>6StOnv0svy_WZmixx+7-ySCJm6O`9X>N-? z{>RgdYtu9SE1oXj@8#OI4uYrJul9c;ec2IPyzWZ)>Hi17KtI3zJO1c{b?Y5_yK8%0 z>%M>2m%Y4cm;z_vCiaq04_+2jHF$0kbQ)RioJO|*vvc>U_4Ca>3*G)!+r_4yH+BEY z^br#Lft&nKS>+}D0W5M+_vDC~&ugq_le~qAJteQo|2ehOv7t~J)j`2Af@ozCI?AwC z5eyO!)`4k|g(4+1le5+`Vx3Bb9Sa;&Ll%5X2_63hu@SaZM~E%rOL&;YuB0tQMn6BX zg3_rD3y$E6n)qJ_gn}p3n6mwM_D}>JH)Od=_YV^*Q0nSX;Fy-Tl}(_V70Ci|=UD*i z`K*dZola`-KIFUAv1+A4js=dXK??)PyOdktL&Cff93Wy0F22lD$=#@2eOJXwlny!= zIHu_=Y>X`MitPR1jUDil~ynCi>myVkH9{rffS8G5_flN;3i z&84I}E{xT)rTnn>{w8+E*{eT0zappkPvXS#f(BR^CKlHX_>lc=WzfIpJb!*2=Nip@ z&U`DJHP_lZE-}_}NZC^mjIcCVei@Ps%_=xwpK123JNCDG70HB`GD0A@>6^eyLU4gt zvIQsfJq>L=#}2Qp)Y3+ezkS+?C1TpYsi@m4(piSi{BAm&uQ_K*^p9Kq0tcHWGcq2E z#P%Fw&wk#$k-~4*A9~M)+x4kGSYZnPe(WUCJMZp~oi4HO$NhUl{2!%5ap9!@UdkT} zR{q|7-mT`Jz75Fs0Vx;5tjxbf6WluNuJKU*A%@56^MvM3+l2#ONB`$zMoQAmuF%<& z&(YUkSAXNDJgd;97eQc(jU6W-ICLiX3q%qq$T1DGk6GI}P!=kTf@7B2UGX)$98y{G z0iFk668mK4{Z}#N?ZDX2gtAj%792e5C6JSal+QSq8Ly6>3ISg%+5{B8RTl2BL4+Jr z^cESqQAr0%)L9pCr@9qM5*RiM_aJ61xjRO0AGVZGV=B;s5yS(pmZ&ps;8DVu9Wu+` z{8E27B_`BM^v>EY_RV%1$88=}C8Z(Na7@BlWXmUgwY zeWh$t1;-?pIpc#wXxmH*xV=BUevnfvVS%ez1m!wi^FTkC{TT?BzpJ2Js=5~&*CwC}w zC#8_8a7?>d;YH{Rwk!YrEG2`;u1r1Pk^FOI=O9LG@Pv-3gA3b|E;>zFSuXBKn<`*> zd&Kb~L?c*%)gY?msUnp~b4M2(7~I2hz-jc`M2`D+xN@a)#C^Cw*XZ3D+}wKm3w>%| zL$NTS$5g=uItg1I+TxhQt_&d=qqe^nB_&sA51C6tq>@Srs!EtXvf!WpNvM$yw-3s1 zc!5`hT>nfyp~8<@*|{(GuGVUKb;~6w{!xNstMcqwMDeg%H4Jc@Psro~@((A!cyB%D zwA^;(cHX^>Pc0^j)LiJ&DOOh~mkZeCBzY+=f8t{EHEpEx?fBF` ziYefjuDq-&{jI?$mq!r@Iypwn5u-`v+mYsD?MG*3wcMGb_Q!N)3NPT8rM${tYYjum zd&F=7k>zoFwi$9G>GWz2)%xr>lx7ty!7<-)cMt7)u9Y+5`G{Fdb~p-E?`q3>sWlDV=Ci$ZIq1qyIXvRRF&CdZI`oe_B4sdSRtcsatq zxGJ5MsUQET5h)aFClpMDTySg%2dYx=zaz}_JT77n4Khg{YGquJ4X7Hi!V@^A;4D8} z<7So+9$fqh;rsndyDnecM>6EgC4y*Y-Daz|@TX|(nowgZ--6f7D<$tD%en_~{NHil z(cgTV3p@9S42t|5?)+_r&E7-bEysi!Q^6J-EW~ZjCYomb(OiQ1Q@Kgb6k~>BlOc#) zJ$Du!qbKa8td`QxQ>-e~n2NaImw~gC$+oM%eJX%(vrpj1OwvC}`Vdq4x!JMRg*K^p z3yw2)V)r@5hWH$_iASs-H5anK^+WYQH=U)Qe*eR-UnXcmj;Y00MI3?Nc5-BK7Ph(1 zp0RQUS%bciHEO;xcXTGh31vFFpXcL)yC@EnPl&-Jzesl%Fq9|0!~djY2-ZUHe%7&% z_vZZ3=$%5%x_)QTf}cFW*2!`M-QC`o-`HfhhvX-R!pt@Q(5n%w?sTMh(7!5*f@8AI zS z98-Q44Q zUGu>^uT1}xJ^P)jsi{(zJ^i9z1*MbHk*YW*xG(%9_oS4Qs>p2q^9CFVx3|13PIeWV zMCY~8^PkVwXey~73y!f`!3W~}PI)$Gxb-id@ix86gF(y(T5dn&``i2&)DVR$a!e_{ zv>i2*UbZYxNUg*l`CV~SY&!}B*Op8wBX%d$T37QVUhwKk&yMYXvx=cIH4r}OC20?r z%uAJKiOSY;p{L26*CC`(V=D53EAH|F<{{yXN%^+_kPpbf{j#j8t4AA|LqAeGkM1%* zNwK&>$5e#{{+kw8!Qxczd8-Uzo_DUCxirv=dnK{eE`T z4p*U5sVvyY|0FXHk0%bnd%$3K7wIN_aj;SOIMr5q4Wh-Ubko-t=n3g09SNk7kimCeT^q=g7gvL|h z791yp7JLE$0J-&0L-;zRUsgMhft?UJ{@-wnz#h}CpYxn|TSd!NW8>J@26iQ5mTySI- za5+0e4K1RwrDk*Pyvf2@qqCc7CQtL3Q|t+?rh+XvA}zw!ktnML^K2xkfH^^^eiQ4F zI|S`k`sdtPP@4&bQ}GrYV{o-$K_rt=#=+oYCYS99M>%U@i_-1$3a*6}a7>t5L}0ZW zK@#0vW5@;M;FH%|Qo0kY8$JG3u#f0Y6DvW|-1;W~L z5VhGI)qCHCMNpQQSb;nT;vgu( zTq~?Y-{^Z48dD$ivC$G5mt()*0E~&edkPe{W|ydg6->b~KWo{N?OJ>AWzZd| zxwSX)(|0J|f|aixGdq=UT$m8}$@+yFQy~@{GU!<=Qks|ov4Uapw9byv7^DyD*CvdpPSpovOgt1rxPE_rhb1WDtWSPso~&(ys1p>-v6n2NICtG!Bd z9b$~C0D~0vi?V!QqG_%OE>yZrLQPYF7aS5OlPD4ue5WKJWH8zyr^mGfRVI(jwea+F z0euRMOa)tT7T=-)-_~cOgKra|?wJZ9D9HU1wV88tzkFcTK%tJQVhhI5rN~fnDdI$> z=2+yY&2yn9udN@USjVQ@3WVU8gt$gts6kj#7-HrSj9;KXDig_vYy zlh#`eev@&P{GG1|&ZcNr6->b~HI!evI&zrbxHV&lfwp7^h@j6X6lnV}AeS+G_~Li^ zAw|rk&hh+Q_wqV=Nu7KC{eO;BICLE9>QuWnCJ`Mv>pgac?cAz=!r{Yi1kMhFrAI_D<6d{+lZUnp zp+$wqwCsEBj-7s_w65BThuzQXUvs9(y1(CbdTp2g&e>1d5`e60jJxaT}QvLUc2eM>fr!HN@=}>RIk%k7r-bfF2}$P58_KFu&H#G zh`Hl+=VHK|fMYO9A}PqmNbjHin~1Nc*)_KsCcVU0IdU(bkcSzxF{(%!VXe!vw;s}` zzJ_~0c#l&1%yktr`~De`uh?mgif z6S48MKq~?h7kT70(37%ef+kkrL&7$EK`SIxMJ*mn#X^yWQyy-!mDEH|JNdTRY$!=w znO=G_b&4dl$WkH5L7QwP^zhgEH#a>-eeD$&haeJ?o|Ql@L?sL>1OXJV9=2V4ae8#= zU43zlk^o@P(@tUxC15;!_!*SrL<0lg#@k*{-*|AV9i z7d9v-{TK|Mx(ki{^k`T*?}F@W7Kl3#^UCc=ReA-5sPT?_CT4C<2hY`8Yjq>zCDlx| zRy;&vE6zifKpMQ{B9MP)U$5-yzfsRWe(&G^_0IgYYJOAmgWFI1zIM{lN)E`>$Ry8c zz$bF}W!Ts}#3DTV?RA|R9o;<_1Pts2DPO5DJ_&;;o7^_UH#=d&69M8MQ)KxoEDI$V zX^{HcLrHj{wfI?c!KoY-DT^gx@4!`iF&H8#gp?3U$j6!K$J7|{-klOgAcGxpL(7C> zVPR>3)xUMjn6!rY{4wtZl#ie*If?nOsbnOL@|)^_g%3`y;(>-lBs@R#v4hluM~@ud ze(krePQywS_>I)j=$O-V;{eD3rIS>~2#9THNXpS5@Om#thR5c{ePP`oihcKA`+oNv zw@#-Xt6y5JY13Hl&vipTCSQ8<=0O-zuxQ>2Z|@`s4i+FXoaY>_o*BBU7@ZOLEZq?| z);W$jm83f;)s8XOmBE27NfHoQ2qOSzM=hFiEv6IS8KG4!C4%9||LRmJjR_pHV;QI3 zcAkyByF!3WZ!ro(uw#(&FB#Q|NnHOT+u^$O|Jg-7rM!40ACu5A_1CstjHE~C)yPTs zLS}Genv)z%CtvWqW=-clb@eZ`-mwflr~Vaz_^qzH5D)_Z0{}!s10VoE4^@9E#RBDZ z=T16FiFBk>0E`{X8k)?&ZQ6w-PSRqTp?hhz^0xoq{r^Bn(Et$FK|E&DGV*ul;)Ir=I)l z9~%3-hMskQjsKf^V}GBn`qXJ#?d?AOy~eEfe|^95_FS!xZ0deq(EX9we)XxB@AW== z_VBj0J$r3^#_rm-TgQ-Teh0qhTLXXV>QD9Uzq;+Q79B%#3S1?0XtdDCtl&x-lafCs z;K+Tn`NM%c)zmTg{HM=X{?oa&vjFG+{IUNy^+mt-e}?h; zQ4&`*O80RhXEUvI#!X5jf!F%W@VL(Ydzx!kwmK`3FO#K9qPcm>Nv`#56SrS;?lTT! zU6K?i!bkCYc0{}!1ZRy^pPTABd8f9rnTY77PanSqvM_i_LW0y21BK#z3yh z2Z7~l>t8eAKal$5SrJn*bosn? zVixRcOJcfT|23DkKWn142Y#U4&-#n!d$NCKt`#8pHE?u35Sn8U@CAZ#i`nmYu|&9% z^Tp3y>z_FGKX3-^mepzw#;xO-L!ONnGG<W-j#n$vL<)Hm<+hk7DXUDgzw03ISgkRf`6$c97KLh)diWbqMn0KVj zSrB9-ht)pvKD5t6WayLJY5T|{MaG(g=Bma2&3lECPbSNko0P8n_?C*XbK@)BKcU&t zp7Hzo9Xr*8>(?!Dw2V~e9ISR#8Mc$f{Ka$PLr@95kBk3}UK|J4IQaM!^WNv581bBTm3oSU zaj$o(?(TAkmfq2jh-=498~N~{9ENn^yhl2o&;nUi=3up|e4tdnJmR&k|2ygUW-%D| zb%jg*yJEbS-Vc}W^@e_8Lxzp+uAQI+r0tmNV2QZqq!ilj?5JOoJW2Q)vhXkrfw4J_ z>B2RUkrV1rRnTBrs^)CmAE{=2JZZ*QZEs8Nqj^l%4{b5NZhQxQm$9GL<+FT0U+L7g z2|tz}Dh^w_g$?}W%G}W}6D30xTOtHxX{>~YEMlM=%|5Qgx^`!Ev89l5IV~$S6$eO3ip>N76P!B7Y2f9$UmM;?X1iD)ftEbtg*x1mJ5gYzxZ;GdhZABLF;r`s zmHGKvI$kh|_p+dC`9L?bo6UQ~afYHdZcm;^W7e`LO zI(Oqgh7bD_Pe)%@H)HIjD(;Wduu@!c-&eVv?h?+0Uqnshe&pl~KBl6Rbl=(siCyk+ zZ9bJa2P2WwX~k~6PL z%g7iHlV69ZG7>>gWG@`I+%26jw7iN_mfl9%zUC~b=IbZCs?R6zm`0Se&(}&Cjuy22 zFKkYaRw!R>|8GY=PjaZOA&)F8RUBP?#$@h4D2h4QLoNOlxmFez5PrY&apUOw-+8~$ zwjtMV%iCP*%aa#rWbmO2nVW-lDcghR;~{(8?z6j|3j3r88 z&gk~@w!ZD+7VFu!&DZa6r1)PhweI=2luB3>P6^IfYRUM!8QoLgrE4h8{u-E{t<=k; zg_!1GE4W@4I%qk7BtP*=_I%e4;QKzV30K_+6(Wz@18tVoYYsq$#RD!j=J;h4<3^o1 z8>dIXDU4V)&{gD~zdU+6v<-8IY!1~N&Rmph?`CAsViiH@-SUnx-o-|ap45y@ZJRE5 zACj|UnY5x@6Bn61*7@xrwK)eXUKM!bEUn2@`D+4gr-lLJj{h;;3&yX_#*NEFm&6#q zJ(^sJYqWh%!Afz()8~&8ZfbfC7w+8hVr)+)cf-?_IF4}{x{+3Ya{DdNH8}?>#T9CN zbe@%$ZoOy;ekW+>E1!XdEd{#L4Mgpq8+`uM^*X=$T?cOjVm8LQhLLWDAbMY z(5v`KI*m@A;zM^Bzv>S6rIuJ ziSQu#>|tKdW1sQon-rBngJnLSl@vAEG19lBCX@KULqvm|Bmd^HcLH_Oq0#bx9vSv@ zX*F@DmIy!n{9ws|U?gO#s}$caDbSSh({ zeDolEr%WQu?861GK+Z3G-OYkO*;MP=>}7iO_`2Df%Gmq*^EzkZZHZJ19ISj*Pz~tj zmk;sxea&6_o)K3%Ks%o|CgoQinLp(4=+jQ1y>@Okch(|p7BEv)IcuymQ*xv(so_;*`(H-#}yIu%d zEUVod@`0?8i|E>uge@yS!nw}7B&ge4FXeP&+DC+`dflKEvdYcD!d2_vsTj5R$-OfB zU~vPFoZ}zPQ=d}iKl0Rf&`(b0eDt6D)9zA_aj@=H?l$X+{?Wc)?M?D5$P;u49CMOa z9Z$Lw?-Qq6-|2f&HBP}wamCA5M{ke&b?UE!X3}^|E#<$jPSUv$-}By2R_*GXgO%fo z6wwLzNn`>427Tu!E+HL*>hro$&6xS;qO0shwe`P!`cB%BYH|)%zABp*u9h}=@~4m8 zcQmexc!v0++UFdZr)`B86T1KZmwjkotS)n~j>86XbU8o9_Z)Z{m_p|rVPTJOHPM3( z-60kV-=;r$f#7U$ZC=i(K>|2f{iF)izfB?f#>3jNYrI(Wo(AvvWLzFT zA|xf|b=p=cD-PnLwKJC42H%%Qle|8?&Nns1ul*&sl+OhJ|K&cKj(X2Nf7(CTgNh`- z3FGA=q7MXpq5oZ6-WUt#_qoj-8_L&{yVzQ=xXdsNOwjpNB5{A49X*5mDQ-c%b}bN2PDTmZzU@Is`lWdRvy**V>=i0Xci@ zep_TO#wQ2&lyA0JaASKX56&YCGCtusLYRw>6PDPsS5K*@JoT?#8GM;Xwle*ZvrR_* z`1ka`hj-!PjC;nhQz1c=r+Dn0cKXg)jN10#JMRDh-LrJXlv@imW)w5FbpP03l!5X| zO{A0#gXXnay?s(D9OZD@R;nuw*orN7mlO;rdPiN1`4l3_D*1DS9(5tJf;GQ7y9z^Bv}W(0*B!=3qIvq)P%DGtvAF&BDo=B~~>=(}iptR8c)C0qz5 z&c)z*(b*IY$jI2{5(A+tbMESD-Dp4b6SP=Xt~sI<71`d2ok$!y=Gcb3m^VPDw+&+J|7LZ3cSGOh-pu=IRwX6DAXJ2tjn8pqZh(a-7D zdiI39vAfRRB1}#9Sk2Fryt%=~mCCpjj8O?U?pXgH#$;djpRe}m(n3ql(`Z5}%6kT6 zZ2%2yl<_SJPfFbl^>-oh@w>MFkz|;lQ$<{ynhcPX&6xLyh_>8R|96Y6ETz==%(xd@ zz={NlA+!Ss=9FwsO>vRCe{;oR9cKtM+tCd6mL8PSA(&+b96$R zQ|;Ka;G#w?frO0^xaCFeO^U+5XQi{o3JIH#h2J7*F}eh=-dlu$Pce7hr&Gb7R`ZfB z??F<|=nX_R5)92)Mf2sbm%GZ?)ziJPojk9x>E={>^}?vNw})kr9=p{@PaiY*_x$+e ztS!y+1d*Q>G>9X%V0)4ZW5E(grW7#BKQ}x*t?X?LV^7nk`_i)8Yr5U(y-<#x@y&sB zrhQN-;Hfh%EjOaB4zPE8^=fKHSfa|MS+V(XQkU}W4lI5z2zfPG71eqF)>PXb8Yht(0HTKh}!r{Lbc1L#?1vUP}D_HAylu7>4?uY zhMID0L0%VUFxwYg1|g(N_W^0N=7MmsBD`vdjB7omyxCz_YQah+-=BAb1I(C;6Sf}B zPS6#L~6>JnGSB_BmY^iaedlAwnYBEH|;MvlYL7`}F&%rG$ zY$f=hx~{=#X_QYXmV-v{gQ&j>;hekyvW>)n%0-c+!8M|49N7gF%-~@@2~64?W$VcY z+;XRw1udYcpnN4X58j2nH$;W$0kNBxoMoE422ftecj^KsUyO1=GM~ShJcgyanWe3p zy^35-9o?>Mf7XPvcyh@%C?pDSXgr*aA)Psx@T(CjIlub$(T&T_w#wAOIki1o#Rk%uKthZD4j*eG;3Onn!pBzqtZ`7PHLT7+gTBop|Ls!34=^duV6uW z_e18KdxYS!G&D;*TpHz~MT)qH+%z=xW)#xcgiR|Y3wm7flrZyFI2|ZP3k>k}D(4Zz z`}CGOA)_q@1&U2N%3)9d!zeENk}DRgQ=i(;x$XkL_tI=>dGj<5YKjtk`-sK#U>ZE~ z1s&)_fi~&P_TakpC-)j+|vZ=Ez!CdP8I)4?(rT$mCtl)|D5WW++}9&$0@Yj8)=qbY?Q zV%$94tlaC*T;=vfEzq!STW8bSF-V3Eg7%90Bb` zD4pU+#|2-&`+zf{%FN2ual5C5wi+{7 zx&Qb=j{56ev4?rY^C*8li_qEKYbkPoicvhd$F;bde~)gLB}#mSx8HkZ2J*>uO8G0x zkkcL<#ZTOszJU=yytr??a5sN4+%8#E^OcNTApUWvEP9cP!mF-5H7>+H!teeiPaect z!t-tpNgj1~i7t@>RP4Cs;eSx^1D?eP_QJ2evw!+$Hao&sJHn&QK%EazLC_ha!&Q=U^X%jhJCr?>FZuuCz7!llTiaAwip

W$9MSFwd|$qU|8 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

=uj2T^*ZzJ}Yp$<|6yhUSl$mpA^l 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�A{WwDb)MVE`&z}X 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%0Dyzp3#7`kA@dY%y;RTiVOrstx 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 diff --git a/lib/ST25RFAL002/include/rfal_analogConfig.h b/lib/ST25RFAL002/include/rfal_analogConfig.h deleted file mode 100644 index de9db7be9c..0000000000 --- a/lib/ST25RFAL002/include/rfal_analogConfig.h +++ /dev/null @@ -1,435 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_AnalogConfig.h - * - * \author bkam - * - * \brief RF Chip Analog Configuration Settings - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup AnalogConfig - * \brief RFAL Analog Config Module - * @{ - * - */ - -#ifndef RFAL_ANALOG_CONFIG_H -#define RFAL_ANALOG_CONFIG_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -#define RFAL_ANALOG_CONFIG_LUT_SIZE \ - (87U) /*!< Maximum number of Configuration IDs in the Loop Up Table */ -#define RFAL_ANALOG_CONFIG_LUT_NOT_FOUND \ - (0xFFU) /*!< Index value indicating no Configuration IDs found */ - -#define RFAL_ANALOG_CONFIG_TBL_SIZE \ - (1024U) /*!< Maximum number of Register-Mask-Value in the Setting List */ - -#define RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK \ - (0x8000U) /*!< Mask bit of Poll Mode in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_MASK \ - (0x7F00U) /*!< Mask bits for Technology in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_MASK \ - (0x00F0U) /*!< Mask bits for Bit rate in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_DIRECTION_MASK \ - (0x000FU) /*!< Mask bits for Direction in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_CHIP_SPECIFIC_MASK \ - (0x00FFU) /*!< Mask bits for Chip Specific Technology */ - -#define RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_SHIFT \ - (15U) /*!< Shift value of Poll Mode in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_SHIFT \ - (8U) /*!< Shift value for Technology in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_SHIFT \ - (4U) /*!< Shift value for Technology in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_DIRECTION_SHIFT \ - (0U) /*!< Shift value for Direction in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_POLL \ - (0x0000U) /*!< Poll Mode bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_LISTEN \ - (0x8000U) /*!< Listen Mode bit setting in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_TECH_CHIP \ - (0x0000U) /*!< Chip-Specific bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCA \ - (0x0100U) /*!< NFC-A Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCB \ - (0x0200U) /*!< NFC-B Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCF \ - (0x0400U) /*!< NFC-F Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_AP2P \ - (0x0800U) /*!< AP2P Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCV \ - (0x1000U) /*!< NFC-V Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_RFU (0x2000U) /*!< RFU for Technology bits */ - -#define RFAL_ANALOG_CONFIG_BITRATE_COMMON \ - (0x0000U) /*!< Common settings for all bit rates in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_106 \ - (0x0010U) /*!< 106kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_212 \ - (0x0020U) /*!< 212kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_424 \ - (0x0030U) /*!< 424kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_848 \ - (0x0040U) /*!< 848kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_1695 \ - (0x0050U) /*!< 1695kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_3390 \ - (0x0060U) /*!< 3390kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_6780 \ - (0x0070U) /*!< 6780kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_1OF4 \ - (0x00C0U) /*!< 1 out of 4 for NFC-V setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_1OF256 \ - (0x00D0U) /*!< 1 out of 256 for NFC-V setting in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_NO_DIRECTION \ - (0x0000U) /*!< No direction setting in Analog Conf ID (Chip Specific only) */ -#define RFAL_ANALOG_CONFIG_TX \ - (0x0001U) /*!< Transmission bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_RX \ - (0x0002U) /*!< Reception bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_ANTICOL \ - (0x0003U) /*!< Anticollision setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_DPO \ - (0x0004U) /*!< DPO setting in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_CHIP_INIT \ - (0x0000U) /*!< Chip-Specific event: Startup;Reset;Initialize */ -#define RFAL_ANALOG_CONFIG_CHIP_DEINIT \ - (0x0001U) /*!< Chip-Specific event: Deinitialize */ -#define RFAL_ANALOG_CONFIG_CHIP_FIELD_ON \ - (0x0002U) /*!< Chip-Specific event: Field On */ -#define RFAL_ANALOG_CONFIG_CHIP_FIELD_OFF \ - (0x0003U) /*!< Chip-Specific event: Field Off */ -#define RFAL_ANALOG_CONFIG_CHIP_WAKEUP_ON \ - (0x0004U) /*!< Chip-Specific event: Wake-up On */ -#define RFAL_ANALOG_CONFIG_CHIP_WAKEUP_OFF \ - (0x0005U) /*!< Chip-Specific event: Wake-up Off */ -#define RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON \ - (0x0006U) /*!< Chip-Specific event: Listen On */ -#define RFAL_ANALOG_CONFIG_CHIP_LISTEN_OFF \ - (0x0007U) /*!< Chip-Specific event: Listen Off */ -#define RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON \ - (0x0008U) /*!< Chip-Specific event: Poll common */ -#define RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON \ - (0x0009U) /*!< Chip-Specific event: Listen common */ -#define RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_ON \ - (0x000AU) /*!< Chip-Specific event: Low Power On */ -#define RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_OFF \ - (0x000BU) /*!< Chip-Specific event: Low Power Off */ - -#define RFAL_ANALOG_CONFIG_UPDATE_LAST \ - (0x00U) /*!< Value indicating Last configuration set during update */ -#define RFAL_ANALOG_CONFIG_UPDATE_MORE \ - (0x01U) /*!< Value indicating More configuration set coming during update */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -#define RFAL_ANALOG_CONFIG_ID_GET_POLL_LISTEN(id) \ - (RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK & (id)) /*!< Check if id indicates Listen mode */ - -#define RFAL_ANALOG_CONFIG_ID_GET_TECH(id) \ - (RFAL_ANALOG_CONFIG_TECH_MASK & (id)) /*!< Get the technology of Configuration ID */ -#define RFAL_ANALOG_CONFIG_ID_IS_CHIP(id) \ - (RFAL_ANALOG_CONFIG_TECH_MASK & (id)) /*!< Check if ID indicates Chip-specific */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCA(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCA & (id)) /*!< Check if ID indicates NFC-A */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCB(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCB & (id)) /*!< Check if ID indicates NFC-B */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCF(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCF & (id)) /*!< Check if ID indicates NFC-F */ -#define RFAL_ANALOG_CONFIG_ID_IS_AP2P(id) \ - (RFAL_ANALOG_CONFIG_TECH_AP2P & (id)) /*!< Check if ID indicates AP2P */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCV(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCV & (id)) /*!< Check if ID indicates NFC-V */ - -#define RFAL_ANALOG_CONFIG_ID_GET_BITRATE(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_MASK & (id)) /*!< Get Bitrate of Configuration ID */ -#define RFAL_ANALOG_CONFIG_ID_IS_COMMON(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_MASK & (id)) /*!< Check if ID indicates common bitrate */ -#define RFAL_ANALOG_CONFIG_ID_IS_106(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_106 & (id)) /*!< Check if ID indicates 106kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_212(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_212 & (id)) /*!< Check if ID indicates 212kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_424(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_424 & (id)) /*!< Check if ID indicates 424kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_848(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_848 & (id)) /*!< Check if ID indicates 848kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_1695(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_1695 & (id)) /*!< Check if ID indicates 1695kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_3390(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_3390 & (id)) /*!< Check if ID indicates 3390kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_6780(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_6780 & (id)) /*!< Check if ID indicates 6780kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_1OF4(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_1OF4 & (id)) /*!< Check if ID indicates 1 out of 4 bitrate */ -#define RFAL_ANALOG_CONFIG_ID_IS_1OF256(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_1OF256 & (id)) /*!< Check if ID indicates 1 out of 256 bitrate */ - -#define RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(id) \ - (RFAL_ANALOG_CONFIG_DIRECTION_MASK & (id)) /*!< Get Direction of Configuration ID */ -#define RFAL_ANALOG_CONFIG_ID_IS_TX(id) \ - (RFAL_ANALOG_CONFIG_TX & (id)) /*!< Check if id indicates TX */ -#define RFAL_ANALOG_CONFIG_ID_IS_RX(id) \ - (RFAL_ANALOG_CONFIG_RX & (id)) /*!< Check if id indicates RX */ - -#define RFAL_ANALOG_CONFIG_CONFIG_NUM(x) \ - (sizeof(x) / sizeof((x)[0])) /*!< Get Analog Config number */ - -/*! Set Analog Config ID value by: Mode, Technology, Bitrate and Direction */ -#define RFAL_ANALOG_CONFIG_ID_SET(mode, tech, br, direction) \ - (RFAL_ANALOG_CONFIG_ID_GET_POLL_LISTEN(mode) | RFAL_ANALOG_CONFIG_ID_GET_TECH(tech) | \ - RFAL_ANALOG_CONFIG_ID_GET_BITRATE(br) | RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(direction)) - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -typedef uint8_t - rfalAnalogConfigMode; /*!< Polling or Listening Mode of Configuration */ -typedef uint8_t - rfalAnalogConfigTech; /*!< Technology of Configuration */ -typedef uint8_t - rfalAnalogConfigBitrate; /*!< Bitrate of Configuration */ -typedef uint8_t - rfalAnalogConfigDirection; /*!< Transmit/Receive direction of Configuration */ - -typedef uint8_t - rfalAnalogConfigRegAddr[2]; /*!< Register Address to ST Chip */ -typedef uint8_t - rfalAnalogConfigRegMask; /*!< Register Mask Value */ -typedef uint8_t - rfalAnalogConfigRegVal; /*!< Register Value */ - -typedef uint16_t - rfalAnalogConfigId; /*!< Analog Configuration ID */ -typedef uint16_t - rfalAnalogConfigOffset; /*!< Analog Configuration offset address in the table */ -typedef uint8_t - rfalAnalogConfigNum; /*!< Number of Analog settings for the respective Configuration ID */ - -/*! Struct that contain the Register-Mask-Value set. Make sure that the whole structure size is even and unaligned! */ -typedef struct { - rfalAnalogConfigRegAddr addr; /*!< Register Address */ - rfalAnalogConfigRegMask mask; /*!< Register Mask Value */ - rfalAnalogConfigRegVal val; /*!< Register Value */ -} rfalAnalogConfigRegAddrMaskVal; - -/*! Struct that represents the Analog Configs */ -typedef struct { - uint8_t id[sizeof(rfalAnalogConfigId)]; /*!< Configuration ID */ - rfalAnalogConfigNum num; /*!< Number of Config Sets to follow */ - rfalAnalogConfigRegAddrMaskVal regSet[]; - /*!< Register-Mask-Value sets */ /* PRQA S 1060 # MISRA 18.7 - Flexible Array Members are the only meaningful way of denoting a variable length input buffer which follows a fixed header structure. */ -} rfalAnalogConfig; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize the Analog Configuration - * - * Reset the Analog Configuration LUT pointer to reference to default settings. - * - ***************************************************************************** - */ -void rfalAnalogConfigInitialize(void); - -/*! - ***************************************************************************** - * \brief Indicate if the current Analog Configuration Table is complete and ready to be used. - * - * \return true if current Analog Configuration Table is complete and ready to be used. - * \return false if current Analog Configuration Table is incomplete - * - ***************************************************************************** - */ -bool rfalAnalogConfigIsReady(void); - -/*! - ***************************************************************************** - * \brief Write the whole Analog Configuration table in raw format - * - * Writes the Analog Configuration and Look Up Table with the given raw table - * - * NOTE: Function does not check the validity of the given Table contents - * - * \param[in] configTbl: location of config Table to be loaded - * \param[in] configTblSize: size of the config Table to be loaded - * - * \return ERR_NONE : if setting is updated - * \return ERR_PARAM : if configTbl is invalid - * \return ERR_NOMEM : if the given Table is bigger exceeds the max size - * \return ERR_REQUEST : if the update Configuration Id is disabled - * - ***************************************************************************** - */ -ReturnCode rfalAnalogConfigListWriteRaw(const uint8_t* configTbl, uint16_t configTblSize); - -/*! - ***************************************************************************** - * \brief Write the Analog Configuration table with new analog settings. - * - * Writes the Analog Configuration and Look Up Table with the new list of register-mask-value - * and Configuration ID respectively. - * - * NOTE: Function does not check for the validity of the Register Address. - * - * \param[in] more: 0x00 indicates it is last Configuration ID settings; - * 0x01 indicates more Configuration ID setting(s) are coming. - * \param[in] *config: reference to the configuration list of current Configuration ID. - * - * \return ERR_PARAM : if Configuration ID or parameter is invalid - * \return ERR_NOMEM : if LUT is full - * \return ERR_REQUEST : if the update Configuration Id is disabled - * \return ERR_NONE : if setting is updated - * - ***************************************************************************** - */ -ReturnCode rfalAnalogConfigListWrite(uint8_t more, const rfalAnalogConfig* config); - -/*! - ***************************************************************************** - * \brief Read the whole Analog Configuration table in raw format - * - * Reads the whole Analog Configuration Table in raw format - * - * \param[out] tblBuf: location to the buffer to place the Config Table - * \param[in] tblBufLen: length of the buffer to place the Config Table - * \param[out] configTblSize: Config Table size - * - * \return ERR_PARAM : if configTbl or configTblSize is invalid - * \return ERR_NOMEM : if configTblSize is not enough for the whole table - * \return ERR_NONE : if read is successful - * - ***************************************************************************** - */ -ReturnCode - rfalAnalogConfigListReadRaw(uint8_t* tblBuf, uint16_t tblBufLen, uint16_t* configTblSize); - -/*! - ***************************************************************************** - * \brief Read the Analog Configuration table. - * - * Read the Analog Configuration Table - * - * \param[in] configOffset: offset to the next Configuration ID in the List Table to be read. - * \param[out] more: 0x00 indicates it is last Configuration ID settings; - * 0x01 indicates more Configuration ID setting(s) are coming. - * \param[out] config: configuration id, number of configuration sets and register-mask-value sets - * \param[in] numConfig: the remaining configuration settings space available; - * - * \return ERR_NOMEM : if number of Configuration for respective Configuration ID is greater the the remaining configuration setting space available - * \return ERR_NONE : if read is successful - * - ***************************************************************************** - */ -ReturnCode rfalAnalogConfigListRead( - rfalAnalogConfigOffset* configOffset, - uint8_t* more, - rfalAnalogConfig* config, - rfalAnalogConfigNum numConfig); - -/*! - ***************************************************************************** - * \brief Set the Analog settings of indicated Configuration ID. - * - * Update the chip with indicated analog settings of indicated Configuration ID. - * - * \param[in] configId: configuration ID - * - * \return ERR_PARAM if Configuration ID is invalid - * \return ERR_INTERNAL if error updating setting to chip - * \return ERR_NONE if new settings is applied to chip - * - ***************************************************************************** - */ -ReturnCode rfalSetAnalogConfig(rfalAnalogConfigId configId); - -/*! - ***************************************************************************** - * \brief Generates Analog Config mode ID - * - * Converts RFAL mode and bitrate into Analog Config Mode ID. - * - * Update the chip with indicated analog settings of indicated Configuration ID. - * - * \param[in] md: RFAL mode format - * \param[in] br: RFAL bit rate format - * \param[in] dir: Analog Config communication direction - * - * \return Analog Config Mode ID - * - ***************************************************************************** - */ -uint16_t rfalAnalogConfigGenModeID(rfalMode md, rfalBitRate br, uint16_t dir); - -#endif /* RFAL_ANALOG_CONFIG_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_chip.h b/lib/ST25RFAL002/include/rfal_chip.h deleted file mode 100644 index 296136b418..0000000000 --- a/lib/ST25RFAL002/include/rfal_chip.h +++ /dev/null @@ -1,287 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_chip.h - * - * \author Gustavo Patricio - * - * \brief RF Chip specific Layer - * - * \warning This layer, which provides direct access to RF chip, should - * only be used for debug purposes and/or advanced features - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup Chip - * \brief RFAL RF Chip Module - * @{ - * - */ - -#ifndef RFAL_CHIP_H -#define RFAL_CHIP_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/***************************************************************************** - * RF Chip * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Writes a register on the RF Chip - * - * Checks if the given register is valid and if so, writes the value(s) - * on the RF Chip register - * - * \param[in] reg: register address to be written, or the first if len > 1 - * \param[in] values: pointer with content to be written on the register(s) - * \param[in] len: number of consecutive registers to be written - * - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Write done with no error - ***************************************************************************** - */ -ReturnCode rfalChipWriteReg(uint16_t reg, const uint8_t* values, uint8_t len); - -/*! - ***************************************************************************** - * \brief Reads a register on the RF Chip - * - * Checks if the given register is valid and if so, reads the value(s) - * of the RF Chip register(s) - * - * \param[in] reg: register address to be read, or the first if len > 1 - * \param[out] values: pointer where the register(s) read content will be placed - * \param[in] len: number of consecutive registers to be read - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Read done with no error - ***************************************************************************** - */ -ReturnCode rfalChipReadReg(uint16_t reg, uint8_t* values, uint8_t len); - -/*! - ***************************************************************************** - * \brief Change a register on the RF Chip - * - * Change the value of the register bits on the RF Chip Test set in the valueMask. - * - * \param[in] reg: register address to be modified - * \param[in] valueMask: mask value of the register bits to be changed - * \param[in] value: register value to be set - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_OK : Change done with no error - ***************************************************************************** - */ -ReturnCode rfalChipChangeRegBits(uint16_t reg, uint8_t valueMask, uint8_t value); - -/*! - ***************************************************************************** - * \brief Writes a Test register on the RF Chip - * - * Writes the value on the RF Chip Test register - * - * \param[in] reg: register address to be written - * \param[in] value: value to be written on the register - * - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Write done with no error - ***************************************************************************** - */ -ReturnCode rfalChipWriteTestReg(uint16_t reg, uint8_t value); - -/*! - ***************************************************************************** - * \brief Reads a Test register on the RF Chip - * - * Reads the value of the RF Chip Test register - * - * \param[in] reg: register address to be read - * \param[out] value: pointer where the register content will be placed - * - * \return ERR_PARAM :Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Read done with no error - ***************************************************************************** - */ -ReturnCode rfalChipReadTestReg(uint16_t reg, uint8_t* value); - -/*! - ***************************************************************************** - * \brief Change a Test register on the RF Chip - * - * Change the value of the register bits on the RF Chip Test set in the valueMask. - * - * \param[in] reg: test register address to be modified - * \param[in] valueMask: mask value of the register bits to be changed - * \param[in] value: register value to be set - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_OK : Change done with no error - ***************************************************************************** - */ -ReturnCode rfalChipChangeTestRegBits(uint16_t reg, uint8_t valueMask, uint8_t value); - -/*! - ***************************************************************************** - * \brief Execute command on the RF Chip - * - * Checks if the given command is valid and if so, executes it on - * the RF Chip - * - * \param[in] cmd: direct command to be executed - * - * \return ERR_PARAM : Invalid command or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Direct command executed with no error - ***************************************************************************** - */ -ReturnCode rfalChipExecCmd(uint16_t cmd); - -/*! - ***************************************************************************** - * \brief Set RFO - * - * Sets the RFO value to be used when the field is on (unmodulated/active) - * - * \param[in] rfo : the RFO value to be used - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipSetRFO(uint8_t rfo); - -/*! - ***************************************************************************** - * \brief Get RFO - * - * Gets the RFO value used used when the field is on (unmodulated/active) - * - * \param[out] result : the current RFO value - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipGetRFO(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Amplitude - * - * Measures the RF Amplitude - * - * \param[out] result : result of RF measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasureAmplitude(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Phase - * - * Measures the Phase - * - * \param[out] result : result of Phase measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasurePhase(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Capacitance - * - * Measures the Capacitance - * - * \param[out] result : result of Capacitance measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasureCapacitance(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Power Supply - * - * Measures the Power Supply - * - * \param[in] param : measurement parameter (chip specific) - * \param[out] result : result of the measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasurePowerSupply(uint8_t param, uint8_t* result); - -#endif /* RFAL_CHIP_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_crc.h b/lib/ST25RFAL002/include/rfal_crc.h deleted file mode 100644 index 134318cd1d..0000000000 --- a/lib/ST25RFAL002/include/rfal_crc.h +++ /dev/null @@ -1,74 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_crc.h - * - * \author Ulrich Herrmann - * - * \brief CRC calculation module - * - */ -/*! - * - */ - -#ifndef RFAL_CRC_H_ -#define RFAL_CRC_H_ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief Calculate CRC according to CCITT standard. - * - * This function takes \a length bytes from \a buf and calculates the CRC - * for this data. The result is returned. - * \note This implementation calculates the CRC with LSB first, i.e. all - * bytes are "read" from right to left. - * - * \param[in] preloadValue : Initial value of CRC calculation. - * \param[in] buf : buffer to calculate the CRC for. - * \param[in] length : size of the buffer. - * - * \return 16 bit long crc value. - * - ***************************************************************************** - */ -extern uint16_t rfalCrcCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length); - -#endif /* RFAL_CRC_H_ */ diff --git a/lib/ST25RFAL002/include/rfal_dpo.h b/lib/ST25RFAL002/include/rfal_dpo.h deleted file mode 100644 index 7dd2cde5e2..0000000000 --- a/lib/ST25RFAL002/include/rfal_dpo.h +++ /dev/null @@ -1,207 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_dpo.h - * - * \author Martin Zechleitner - * - * \brief Dynamic Power adjustment - * - * This module provides an interface to perform the power adjustment dynamically - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup DPO - * \brief RFAL Dynamic Power Module - * @{ - * - */ - -#ifndef RFAL_DPO_H -#define RFAL_DPO_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_DPO_TABLE_SIZE_MAX 15U /*!< Max DPO table size */ -#define RFAL_DPO_TABLE_PARAMETER 3U /*!< DPO table Parameter length */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! DPO table entry struct */ -typedef struct { - uint8_t rfoRes; /*!< Setting for the resistance level of the RFO */ - uint8_t inc; /*!< Threshold for incrementing the output power */ - uint8_t dec; /*!< Threshold for decrementing the output power */ -} rfalDpoEntry; - -/*! Function pointer to method doing the reference measurement */ -typedef ReturnCode (*rfalDpoMeasureFunc)(uint8_t*); - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize dynamic power table - * - * This function sets the internal dynamic power table to the default - * values stored in rfal_DpoTbl.h - * - ***************************************************************************** - */ -void rfalDpoInitialize(void); - -/*! - ***************************************************************************** - * \brief Set the measurement method - * - * This function sets the measurement method used for reference measurement. - * Based on the measurement the power will then be adjusted - * - * \param[in] dpoMeasureFunc: callback of measurement function - * - ***************************************************************************** - */ -void rfalDpoSetMeasureCallback(rfalDpoMeasureFunc dpoMeasureFunc); - -/*! - ***************************************************************************** - * \brief Write dynamic power table - * - * Load the dynamic power table - * - * \param[in] powerTbl: location of power Table to be loaded - * \param[in] powerTblEntries: number of entries of the power Table to be loaded - * - * \return ERR_NONE : No error - * \return ERR_PARAM : if configTbl is invalid - * \return ERR_NOMEM : if the given Table is bigger exceeds the max size - ***************************************************************************** - */ -ReturnCode rfalDpoTableWrite(rfalDpoEntry* powerTbl, uint8_t powerTblEntries); - -/*! - ***************************************************************************** - * \brief Dynamic power table Read - * - * Read the dynamic power table - * - * \param[out] tblBuf: location to the rfalDpoEntry[] to place the Table - * \param[in] tblBufEntries: number of entries available in tblBuf to place the power Table - * \param[out] tableEntries: returned number of entries actually written into tblBuf - * - * \return ERR_NONE : No error - * \return ERR_PARAM : if configTbl is invalid or parameters are invalid - ***************************************************************************** - */ -ReturnCode rfalDpoTableRead(rfalDpoEntry* tblBuf, uint8_t tblBufEntries, uint8_t* tableEntries); - -/*! - ***************************************************************************** - * \brief Dynamic power adjust - * - * It measures the current output and adjusts the power accordingly to - * the dynamic power table - * - * \return ERR_NONE : No error - * \return ERR_PARAM : if configTbl is invalid or parameters are invalid - * \return ERR_WRONG_STATE : if the current state is valid for DPO Adjustment - ***************************************************************************** - */ -ReturnCode rfalDpoAdjust(void); - -/*! - ***************************************************************************** - * \brief Get Current Dynamic power table entry - * - * Return current used DPO power table entry settings - * - * \return ERR_NONE : Current DpoEntry. This includes d_res, inc and dec - * - ***************************************************************************** - */ -rfalDpoEntry* rfalDpoGetCurrentTableEntry(void); - -/*! - ***************************************************************************** - * \brief Dynamic power set enabled state - * - * \param[in] enable: new active state - * - * Set state to enable or disable the Dynamic power adjustment - * - ***************************************************************************** - */ -void rfalDpoSetEnabled(bool enable); - -/*! - ***************************************************************************** - * \brief Get the Dynamic power enabled state - * - * Get state of the Dynamic power adjustment - * - * \return true : enabled - * \return false : disabled - ***************************************************************************** - */ -bool rfalDpoIsEnabled(void); - -#endif /* RFAL_DPO_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_iso15693_2.h b/lib/ST25RFAL002/include/rfal_iso15693_2.h deleted file mode 100644 index 9949812050..0000000000 --- a/lib/ST25RFAL002/include/rfal_iso15693_2.h +++ /dev/null @@ -1,206 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_iso15693_2.h - * - * \author Ulrich Herrmann - * - * \brief Implementation of ISO-15693-2 - * - */ -/*! - * - */ - -#ifndef RFAL_ISO_15693_2_H -#define RFAL_ISO_15693_2_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" - -/* -****************************************************************************** -* GLOBAL DATATYPES -****************************************************************************** -*/ -/*! Enum holding possible VCD codings */ -typedef enum { ISO15693_VCD_CODING_1_4, ISO15693_VCD_CODING_1_256 } iso15693VcdCoding_t; - -/*! Enum holding possible VICC datarates */ - -/*! Configuration parameter used by #iso15693PhyConfigure */ -typedef struct { - iso15693VcdCoding_t coding; /*!< desired VCD coding */ - uint32_t - speedMode; /*!< 0: normal mode, 1: 2^1 = x2 Fast mode, 2 : 2^2 = x4 mode, 3 : 2^3 = x8 mode - all rx pulse numbers and times are divided by 1,2,4,8 */ -} iso15693PhyConfig_t; - -/*! Parameters how the stream mode should work */ -struct iso15693StreamConfig { - uint8_t useBPSK; /*!< 0: subcarrier, 1:BPSK */ - uint8_t din; /*!< the divider for the in subcarrier frequency: fc/2^din */ - uint8_t dout; /*!< the divider for the in subcarrier frequency fc/2^dout */ - uint8_t report_period_length; /*!< the length of the reporting period 2^report_period_length*/ -}; -/* -****************************************************************************** -* GLOBAL CONSTANTS -****************************************************************************** -*/ - -#define ISO15693_REQ_FLAG_TWO_SUBCARRIERS \ - 0x01U /*!< Flag indication that communication uses two subcarriers */ -#define ISO15693_REQ_FLAG_HIGH_DATARATE \ - 0x02U /*!< Flag indication that communication uses high bitrate */ -#define ISO15693_MASK_FDT_LISTEN \ - (65) /*!< t1min = 308,2us = 4192/fc = 65.5 * 64/fc */ - -/*! t1max = 323,3us = 4384/fc = 68.5 * 64/fc - * 12 = 768/fc unmodulated time of single subcarrior SoF */ -#define ISO15693_FWT (69 + 12) - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief Initialize the ISO15693 phy - * - * \param[in] config : ISO15693 phy related configuration (See #iso15693PhyConfig_t) - * \param[out] needed_stream_config : return a pointer to the stream config - * needed for this iso15693 config. To be used for configure RF chip. - * - * \return ERR_IO : Error during communication. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693PhyConfigure( - const iso15693PhyConfig_t* config, - const struct iso15693StreamConfig** needed_stream_config); - -/*! - ***************************************************************************** - * \brief Return current phy configuration - * - * This function returns current Phy configuration previously - * set by #iso15693PhyConfigure - * - * \param[out] config : ISO15693 phy configuration. - * - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693PhyGetConfiguration(iso15693PhyConfig_t* config); - -/*! - ***************************************************************************** - * \brief Code an ISO15693 compatible frame - * - * This function takes \a length bytes from \a buffer, perform proper - * encoding and sends out the frame to the ST25R391x. - * - * \param[in] buffer : data to send, modified to adapt flags. - * \param[in] length : number of bytes to send. - * \param[in] sendCrc : If set to true, CRC is appended to the frame - * \param[in] sendFlags: If set to true, flag field is sent according to - * ISO15693. - * \param[in] picopassMode : If set to true, the coding will be according to Picopass - * \param[out] subbit_total_length : Return the complete bytes which need to - * be send for the current coding - * \param[in,out] offset : Set to 0 for first transfer, function will update it to - point to next byte to be coded - * \param[out] outbuf : buffer where the function will store the coded subbit stream - * \param[out] outBufSize : the size of the output buffer - * \param[out] actOutBufSize : the amount of data stored into the buffer at this call - * - * \return ERR_IO : Error during communication. - * \return ERR_AGAIN : Data was not coded all the way. Call function again with a new/emptied buffer - * \return ERR_NO_MEM : In case outBuf is not big enough. Needs to have at - least 5 bytes for 1of4 coding and 65 bytes for 1of256 coding - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693VCDCode( - uint8_t* buffer, - uint16_t length, - bool sendCrc, - bool sendFlags, - bool picopassMode, - uint16_t* subbit_total_length, - uint16_t* offset, - uint8_t* outbuf, - uint16_t outBufSize, - uint16_t* actOutBufSize); - -/*! - ***************************************************************************** - * \brief Receive an ISO15693 compatible frame - * - * This function receives an ISO15693 frame from the ST25R391x, decodes the frame - * and writes the raw data to \a buffer. - * \note Buffer needs to be big enough to hold CRC also (+2 bytes) - * - * \param[in] inBuf : buffer with the hamming coded stream to be decoded - * \param[in] inBufLen : number of bytes to decode (=length of buffer). - * \param[out] outBuf : buffer where received data shall be written to. - * \param[in] outBufLen : Length of output buffer, should be approx twice the size of inBuf - * \param[out] outBufPos : The number of decoded bytes. Could be used in - * extended implementation to allow multiple calls - * \param[out] bitsBeforeCol : in case of ERR_COLLISION this value holds the - * number of bits in the current byte where the collision happened. - * \param[in] ignoreBits : number of bits in the beginning where collisions will be ignored - * \param[in] picopassMode : if set to true, the decoding will be according to Picopass - * - * \return ERR_COLLISION : collision occurred, data incorrect - * \return ERR_CRC : CRC error, data incorrect - * \return ERR_TIMEOUT : timeout waiting for data. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693VICCDecode( - const uint8_t* inBuf, - uint16_t inBufLen, - uint8_t* outBuf, - uint16_t outBufLen, - uint16_t* outBufPos, - uint16_t* bitsBeforeCol, - uint16_t ignoreBits, - bool picopassMode); - -#endif /* RFAL_ISO_15693_2_H */ diff --git a/lib/ST25RFAL002/include/rfal_isoDep.h b/lib/ST25RFAL002/include/rfal_isoDep.h deleted file mode 100644 index 9b2d32c64d..0000000000 --- a/lib/ST25RFAL002/include/rfal_isoDep.h +++ /dev/null @@ -1,1092 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_isoDep.h - * - * \author Gustavo Patricio - * - * \brief Implementation of ISO-DEP protocol - * - * This implementation was based on the following specs: - * - ISO/IEC 14443-4 2nd Edition 2008-07-15 - * - NFC Forum Digital Protocol 1.1 2014-01-14 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup ISO-DEP - * \brief RFAL ISO-DEP Module - * @{ - * - */ - -#ifndef RFAL_ISODEP_H_ -#define RFAL_ISODEP_H_ -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "rfal_nfcb.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_ISO_DEP -#define RFAL_FEATURE_ISO_DEP \ - false /*!< ISO-DEP module configuration missing. Disabled by default */ -#endif - -/* If module is disabled remove the need for the user to set lengths */ -#if !RFAL_FEATURE_ISO_DEP -#undef RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN -#undef RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN - -#define RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN (1U) /*!< ISO-DEP I-Block max length, set to "none" */ -#define RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN (1U) /*!< ISO-DEP APDU max length, set to "none" */ -#endif /* !RFAL_FEATURE_NFC_DEP */ - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -#define RFAL_ISODEP_PROLOGUE_SIZE \ - (3U) /*!< Length of Prologue Field for I-Block Format */ - -#define RFAL_ISODEP_PCB_LEN \ - (1U) /*!< PCB length */ -#define RFAL_ISODEP_DID_LEN \ - (1U) /*!< DID length */ -#define RFAL_ISODEP_NAD_LEN \ - (1U) /*!< NAD length */ -#define RFAL_ISODEP_NO_DID \ - (0x00U) /*!< DID value indicating the ISO-DEP layer not to use DID */ -#define RFAL_ISODEP_NO_NAD \ - (0xFFU) /*!< NAD value indicating the ISO-DEP layer not to use NAD */ - -#define RFAL_ISODEP_FWI_MASK \ - (0xF0U) /*!< Mask bits of FWI */ -#define RFAL_ISODEP_FWI_SHIFT \ - (4U) /*!< Shift val of FWI */ -#define RFAL_ISODEP_FWI_DEFAULT \ - (4U) /*!< Default value for FWI Digital 1.0 11.6.2.17 */ -#define RFAL_ISODEP_ADV_FEATURE \ - (0x0FU) /*!< Indicate 256 Bytes FSD and Advanc Proto Feature support:NAD & DID */ - -#define RFAL_ISODEP_DID_MAX \ - (14U) /*!< Maximum DID value */ - -#define RFAL_ISODEP_BRI_MASK \ - (0x07U) /*!< Mask bits for Poll to Listen Send bitrate */ -#define RFAL_ISODEP_BSI_MASK \ - (0x70U) /*!< Mask bits for Listen to Poll Send bitrate */ -#define RFAL_ISODEP_SAME_BITRATE_MASK \ - (0x80U) /*!< Mask bit indicate only same bit rate D for both direction support */ -#define RFAL_ISODEP_BITRATE_RFU_MASK \ - (0x08U) /*!< Mask bit for RFU */ - -/*! Maximum Frame Waiting Time = ((256 * 16/fc) * 2^FWImax) = ((256*16/fc)*2^14) = (67108864)/fc = 2^26 (1/fc) */ -#define RFAL_ISODEP_MAX_FWT ((uint32_t)1U << 26) - -#define RFAL_ISODEP_FSDI_DEFAULT \ - RFAL_ISODEP_FSXI_256 /*!< Default Frame Size Integer in Poll mode */ -#define RFAL_ISODEP_FSX_KEEP (0xFFU) /*!< Flag to keep FSX from activation */ -#define RFAL_ISODEP_DEFAULT_FSCI \ - RFAL_ISODEP_FSXI_256 /*!< FSCI default value to be used in Listen Mode */ -#define RFAL_ISODEP_DEFAULT_FSC \ - RFAL_ISODEP_FSX_256 /*!< FSC default value (aligned RFAL_ISODEP_DEFAULT_FSCI) */ -#define RFAL_ISODEP_DEFAULT_SFGI (0U) /*!< SFGI Default value to be used in Listen Mode */ -#define RFAL_ISODEP_DEFAULT_FWI (8U) /*!< Default Listener FWI (Max) Digital 2.0 B7 & B3 */ - -#define RFAL_ISODEP_APDU_MAX_LEN \ - RFAL_ISODEP_FSX_1024 /*!< Max APDU length */ - -#define RFAL_ISODEP_ATTRIB_RES_MBLI_NO_INFO \ - (0x00U) /*!< MBLI indicating no information on its internal input buffer size */ -#define RFAL_ISODEP_ATTRIB_REQ_PARAM1_DEFAULT \ - (0x00U) /*!< Default values of Param 1 of ATTRIB_REQ Digital 1.0 12.6.1.3-5 */ -#define RFAL_ISODEP_ATTRIB_HLINFO_LEN \ - (32U) /*!< Maximum Size of Higher Layer Information */ -#define RFAL_ISODEP_ATS_HB_MAX_LEN \ - (15U) /*!< Maximum length of Historical Bytes Digital 1.1 13.6.2.23 */ -#define RFAL_ISODEP_ATTRIB_REQ_MIN_LEN \ - (9U) /*!< Minimum Length of ATTRIB_REQ command */ -#define RFAL_ISODEP_ATTRIB_RES_MIN_LEN \ - (1U) /*!< Minimum Length of ATTRIB_RES response */ - -#define RFAL_ISODEP_SPARAM_VALUES_MAX_LEN \ - (16U) /*!< Maximum Length of the value field on S(PARAMETERS) */ -#define RFAL_ISODEP_SPARAM_TAG_BLOCKINFO \ - (0xA0U) /*!< S(PARAMETERS) tag Block information */ -#define RFAL_ISODEP_SPARAM_TAG_BRREQ \ - (0xA1U) /*!< S(PARAMETERS) tag Bit rates Request */ -#define RFAL_ISODEP_SPARAM_TAG_BRIND \ - (0xA2U) /*!< S(PARAMETERS) tag Bit rates Indication */ -#define RFAL_ISODEP_SPARAM_TAG_BRACT \ - (0xA3U) /*!< S(PARAMETERS) tag Bit rates Activation */ -#define RFAL_ISODEP_SPARAM_TAG_BRACK \ - (0xA4U) /*!< S(PARAMETERS) tag Bit rates Acknowledgement */ - -#define RFAL_ISODEP_SPARAM_TAG_SUP_PCD2PICC \ - (0x80U) /*!< S(PARAMETERS) tag Supported bit rates from PCD to PICC */ -#define RFAL_ISODEP_SPARAM_TAG_SUP_PICC2PCD \ - (0x81U) /*!< S(PARAMETERS) tag Supported bit rates from PICC to PCD */ -#define RFAL_ISODEP_SPARAM_TAG_SUP_FRAME \ - (0x82U) /*!< S(PARAMETERS) tag Supported framing options PICC to PCD */ -#define RFAL_ISODEP_SPARAM_TAG_SEL_PCD2PICC \ - (0x83U) /*!< S(PARAMETERS) tag Selected bit rate from PCD to PICC */ -#define RFAL_ISODEP_SPARAM_TAG_SEL_PICC2PCD \ - (0x84U) /*!< S(PARAMETERS) tag Selected bit rate from PICC to PCD */ -#define RFAL_ISODEP_SPARAM_TAG_SEL_FRAME \ - (0x85U) /*!< S(PARAMETERS) tag Selected framing options PICC to PCD */ - -#define RFAL_ISODEP_SPARAM_TAG_LEN \ - (1) /*!< S(PARAMETERS) Tag Length */ -#define RFAL_ISODEP_SPARAM_TAG_BRREQ_LEN \ - (0U) /*!< S(PARAMETERS) tag Bit rates Request Length */ -#define RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN \ - (2U) /*!< S(PARAMETERS) bit rates from PCD to PICC Length */ -#define RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN \ - (2U) /*!< S(PARAMETERS) bit rates from PICC to PCD Length */ -#define RFAL_ISODEP_SPARAM_TAG_BRACK_LEN \ - (0U) /*!< S(PARAMETERS) tag Bit rates Acknowledgement Length */ - -#define RFAL_ISODEP_ATS_TA_DPL_212 \ - (0x01U) /*!< ATS TA DSI 212 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DPL_424 \ - (0x02U) /*!< ATS TA DSI 424 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DPL_848 \ - (0x04U) /*!< ATS TA DSI 848 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DLP_212 \ - (0x10U) /*!< ATS TA DSI 212 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DLP_424 \ - (0x20U) /*!< ATS TA DRI 424 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DLP_848 \ - (0x40U) /*!< ATS TA DRI 848 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_SAME_D \ - (0x80U) /*!< ATS TA same bit both directions bit mask */ -#define RFAL_ISODEP_ATS_TB_FWI_MASK \ - (0xF0U) /*!< Mask bits for FWI (Frame Waiting Integer) in TB byte */ -#define RFAL_ISODEP_ATS_TB_SFGI_MASK \ - (0x0FU) /*!< Mask bits for SFGI (Start-Up Frame Guard Integer) in TB byte */ - -#define RFAL_ISODEP_ATS_T0_TA_PRESENCE_MASK \ - (0x10U) /*!< Mask bit for TA presence */ -#define RFAL_ISODEP_ATS_T0_TB_PRESENCE_MASK \ - (0x20U) /*!< Mask bit for TB presence */ -#define RFAL_ISODEP_ATS_T0_TC_PRESENCE_MASK \ - (0x40U) /*!< Mask bit for TC presence */ -#define RFAL_ISODEP_ATS_T0_FSCI_MASK \ - (0x0FU) /*!< Mask bit for FSCI presence */ -#define RFAL_ISODEP_ATS_T0_OFFSET \ - (0x01U) /*!< Offset of T0 in ATS Response */ - -#define RFAL_ISODEP_MAX_I_RETRYS \ - (2U) /*!< Number of retries for a I-Block Digital 2.0 16.2.5.4 */ -#define RFAL_ISODEP_MAX_R_RETRYS \ - (3U) /*!< Number of retries for a R-Block Digital 2.0 B9 - nRETRY ACK/NAK: [2,5] */ -#define RFAL_ISODEP_MAX_WTX_NACK_RETRYS \ - (3U) /*!< Number of S(WTX) replied with NACK Digital 2.0 B9 - nRETRY WTX[2,5] */ -#define RFAL_ISODEP_MAX_WTX_RETRYS \ - (20U) /*!< Number of overall S(WTX) retries Digital 2.0 16.2.5.2 */ -#define RFAL_ISODEP_MAX_WTX_RETRYS_ULTD \ - (255U) /*!< Use unlimited number of overall S(WTX) */ -#define RFAL_ISODEP_MAX_DSL_RETRYS \ - (0U) /*!< Number of retries for a S(DESELECT) Digital 2.0 B9 - nRETRY DESELECT: [0,5] */ -#define RFAL_ISODEP_RATS_RETRIES \ - (1U) /*!< RATS retries upon fail Digital 2.0 B7 - nRETRY RATS [0,1] */ - -/*! Frame Size for Proximity Card Integer definitions */ -typedef enum { - RFAL_ISODEP_FSXI_16 = - 0, /*!< Frame Size for Proximity Card Integer with 16 bytes */ - RFAL_ISODEP_FSXI_24 = - 1, /*!< Frame Size for Proximity Card Integer with 24 bytes */ - RFAL_ISODEP_FSXI_32 = - 2, /*!< Frame Size for Proximity Card Integer with 32 bytes */ - RFAL_ISODEP_FSXI_40 = - 3, /*!< Frame Size for Proximity Card Integer with 40 bytes */ - RFAL_ISODEP_FSXI_48 = - 4, /*!< Frame Size for Proximity Card Integer with 48 bytes */ - RFAL_ISODEP_FSXI_64 = - 5, /*!< Frame Size for Proximity Card Integer with 64 bytes */ - RFAL_ISODEP_FSXI_96 = - 6, /*!< Frame Size for Proximity Card Integer with 96 bytes */ - RFAL_ISODEP_FSXI_128 = - 7, /*!< Frame Size for Proximity Card Integer with 128 bytes */ - RFAL_ISODEP_FSXI_256 = - 8, /*!< Frame Size for Proximity Card Integer with 256 bytes */ - RFAL_ISODEP_FSXI_512 = - 9, /*!< Frame Size for Proximity Card Integer with 512 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSXI_1024 = - 10, /*!< Frame Size for Proximity Card Integer with 1024 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSXI_2048 = - 11, /*!< Frame Size for Proximity Card Integer with 2048 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSXI_4096 = - 12 /*!< Frame Size for Proximity Card Integer with 4096 bytes ISO14443-3 Amd2 2012 */ -} rfalIsoDepFSxI; - -/*! Frame Size for Proximity Card definitions */ -typedef enum { - RFAL_ISODEP_FSX_16 = - 16, /*!< Frame Size for Proximity Card with 16 bytes */ - RFAL_ISODEP_FSX_24 = - 24, /*!< Frame Size for Proximity Card with 24 bytes */ - RFAL_ISODEP_FSX_32 = - 32, /*!< Frame Size for Proximity Card with 32 bytes */ - RFAL_ISODEP_FSX_40 = - 40, /*!< Frame Size for Proximity Card with 40 bytes */ - RFAL_ISODEP_FSX_48 = - 48, /*!< Frame Size for Proximity Card with 48 bytes */ - RFAL_ISODEP_FSX_64 = - 64, /*!< Frame Size for Proximity Card with 64 bytes */ - RFAL_ISODEP_FSX_96 = - 96, /*!< Frame Size for Proximity Card with 96 bytes */ - RFAL_ISODEP_FSX_128 = - 128, /*!< Frame Size for Proximity Card with 128 bytes */ - RFAL_ISODEP_FSX_256 = - 256, /*!< Frame Size for Proximity Card with 256 bytes */ - RFAL_ISODEP_FSX_512 = - 512, /*!< Frame Size for Proximity Card with 512 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSX_1024 = - 1024, /*!< Frame Size for Proximity Card with 1024 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSX_2048 = - 2048, /*!< Frame Size for Proximity Card with 2048 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSX_4096 = - 4096, /*!< Frame Size for Proximity Card with 4096 bytes ISO14443-3 Amd2 2012 */ -} rfalIsoDepFSx; - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -/*! RATS format Digital 1.1 13.6.1 */ -typedef struct { - uint8_t CMD; /*!< RATS command byte: 0xE0 */ - uint8_t PARAM; /*!< Param indicating FSDI and DID */ -} rfalIsoDepRats; - -/*! ATS response format Digital 1.1 13.6.2 */ -typedef struct { - uint8_t TL; /*!< Length Byte, including TL byte itself */ - uint8_t T0; /*!< Format Byte T0 indicating if TA, TB, TC */ - uint8_t TA; /*!< Interface Byte TA(1) */ - uint8_t TB; /*!< Interface Byte TB(1) */ - uint8_t TC; /*!< Interface Byte TC(1) */ - uint8_t HB[RFAL_ISODEP_ATS_HB_MAX_LEN]; /*!< Historical Bytes */ -} rfalIsoDepAts; - -/*! PPS Request format (Protocol and Parameter Selection) ISO14443-4 5.3 */ -typedef struct { - uint8_t PPSS; /*!< Start Byte: [ 1101b | CID[4b] ] */ - uint8_t PPS0; /*!< Parameter 0:[ 000b | PPS1[1n] | 0001b ] */ - uint8_t PPS1; /*!< Parameter 1:[ 0000b | DSI[2b] | DRI[2b] ]*/ -} rfalIsoDepPpsReq; - -/*! PPS Response format (Protocol and Parameter Selection) ISO14443-4 5.4 */ -typedef struct { - uint8_t PPSS; /*!< Start Byte: [ 1101b | CID[4b] ] */ -} rfalIsoDepPpsRes; - -/*! ATTRIB Command Format Digital 1.1 15.6.1 */ -typedef struct { - uint8_t cmd; /*!< ATTRIB_REQ command byte */ - uint8_t nfcid0[RFAL_NFCB_NFCID0_LEN]; /*!< NFCID0 of the card to be selected */ - struct { - uint8_t PARAM1; /*!< PARAM1 of ATTRIB command */ - uint8_t PARAM2; /*!< PARAM2 of ATTRIB command */ - uint8_t PARAM3; /*!< PARAM3 of ATTRIB command */ - uint8_t PARAM4; /*!< PARAM4 of ATTRIB command */ - } Param; /*!< Parameter of ATTRIB command */ - uint8_t HLInfo[RFAL_ISODEP_ATTRIB_HLINFO_LEN]; /*!< Higher Layer Information */ -} rfalIsoDepAttribCmd; - -/*! ATTRIB Response Format Digital 1.1 15.6.2 */ -typedef struct { - uint8_t mbliDid; /*!< Contains MBLI and DID */ - uint8_t HLInfo[RFAL_ISODEP_ATTRIB_HLINFO_LEN]; /*!< Higher Layer Information */ -} rfalIsoDepAttribRes; - -/*! S(Parameters) Command Format ISO14443-4 (2016) Table 4 */ -typedef struct { - uint8_t tag; /*!< S(PARAMETERS) Tag field */ - uint8_t length; /*!< S(PARAMETERS) Length field */ - uint8_t value[RFAL_ISODEP_SPARAM_VALUES_MAX_LEN]; /*!< S(PARAMETERS) Value field */ -} rfalIsoDepSParameter; - -/*! Activation info as Poller and Listener for NFC-A and NFC-B */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently, device is only of type A or B at a time. Thus no problem can occur. */ - - /*! NFC-A information */ - union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently, device is only PCD or PICC at a time. Thus no problem can occur. */ - struct { - rfalIsoDepAts ATS; /*!< ATS response (Poller mode) */ - uint8_t ATSLen; /*!< ATS response length (Poller mode) */ - } Listener; - struct { - rfalIsoDepRats RATS; /*!< RATS request (Listener mode) */ - } Poller; - } A; - - /*! NFC-B information */ - union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently, device is only PCD or PICC at a time. Thus no problem can occur. */ - struct { - rfalIsoDepAttribRes ATTRIB_RES; /*!< ATTRIB_RES (Poller mode) */ - uint8_t ATTRIB_RESLen; /*!< ATTRIB_RES length (Poller mode) */ - } Listener; - struct { - rfalIsoDepAttribCmd ATTRIB; /*!< ATTRIB request (Listener mode) */ - uint8_t ATTRIBLen; /*!< ATTRIB request length (Listener mode) */ - } Poller; - } B; -} rfalIsoDepActivation; - -/*! ISO-DEP device Info */ -typedef struct { - uint8_t FWI; /*!< Frame Waiting Integer */ - uint32_t FWT; /*!< Frame Waiting Time (1/fc) */ - uint32_t dFWT; /*!< Delta Frame Waiting Time (1/fc) */ - uint32_t SFGI; /*!< Start-up Frame Guard time Integer */ - uint32_t SFGT; /*!< Start-up Frame Guard Time (ms) */ - uint8_t FSxI; /*!< Frame Size Device/Card Integer (FSDI or FSCI) */ - uint16_t FSx; /*!< Frame Size Device/Card (FSD or FSC) */ - uint32_t MBL; /*!< Maximum Buffer Length (optional for NFC-B) */ - rfalBitRate DSI; /*!< Bit Rate coding from Listener (PICC) to Poller (PCD) */ - rfalBitRate DRI; /*!< Bit Rate coding from Poller (PCD) to Listener (PICC) */ - uint8_t DID; /*!< Device ID */ - uint8_t NAD; /*!< Node ADdress */ - bool supDID; /*!< DID supported flag */ - bool supNAD; /*!< NAD supported flag */ - bool supAdFt; /*!< Advanced Features supported flag */ -} rfalIsoDepInfo; - -/*! ISO-DEP Device structure */ -typedef struct { - rfalIsoDepActivation activation; /*!< Activation Info */ - rfalIsoDepInfo info; /*!< ISO-DEP (ISO14443-4) device Info */ -} rfalIsoDepDevice; - -/*! ATTRIB Response parameters */ -typedef struct { - uint8_t mbli; /*!< MBLI */ - uint8_t HLInfo[RFAL_ISODEP_ATTRIB_HLINFO_LEN]; /*!< Hi Layer Information */ - uint8_t HLInfoLen; /*!< Hi Layer Information Length */ -} rfalIsoDepAttribResParam; - -/*! ATS Response parameter */ -typedef struct { - uint8_t fsci; /*!< Frame Size of Proximity Card Integer */ - uint8_t fwi; /*!< Frame Waiting Time Integer */ - uint8_t sfgi; /*!< Start-Up Frame Guard Time Integer */ - bool didSupport; /*!< DID Supported */ - uint8_t ta; /*!< Max supported bitrate both direction */ - uint8_t* hb; /*!< Historical Bytes data */ - uint8_t hbLen; /*!< Historical Bytes Length */ -} rfalIsoDepAtsParam; - -/*! Structure of I-Block Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_ISODEP_PROLOGUE_SIZE]; /*!< Prologue/SoD buffer */ - uint8_t - inf[RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN]; /*!< INF/Payload buffer */ -} rfalIsoDepBufFormat; - -/*! Structure of APDU Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_ISODEP_PROLOGUE_SIZE]; /*!< Prologue/SoD buffer */ - uint8_t apdu[RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN]; /*!< APDU/Payload buffer */ -} rfalIsoDepApduBufFormat; - -/*! Listen Activation Parameters Structure */ -typedef struct { - rfalIsoDepBufFormat* rxBuf; /*!< Receive Buffer struct reference */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - bool* isRxChaining; /*!< Received data is not complete */ - rfalIsoDepDevice* isoDepDev; /*!< ISO-DEP device info */ -} rfalIsoDepListenActvParam; - -/*! Structure of parameters used on ISO DEP Transceive */ -typedef struct { - rfalIsoDepBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in Bytes*/ - bool isTxChaining; /*!< Transmit data is not complete */ - rfalIsoDepBufFormat* rxBuf; /*!< Receive Buffer struct reference in Bytes */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - bool* isRxChaining; /*!< Received data is not complete */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t ourFSx; /*!< Our device Frame Size (FSD or FSC) */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalIsoDepTxRxParam; - -/*! Structure of parameters used on ISO DEP APDU Transceive */ -typedef struct { - rfalIsoDepApduBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in Bytes*/ - rfalIsoDepApduBufFormat* rxBuf; /*!< Receive Buffer struct reference in Bytes */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - rfalIsoDepBufFormat* tmpBuf; /*!< Temp buffer for Rx I-Blocks (internal) */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint16_t ourFSx; /*!< Our device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalIsoDepApduTxRxParam; - -/* - ****************************************************************************** - * GLOBAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -/*! - ****************************************************************************** - * \brief Initialize the ISO-DEP protocol - * - * Initialize the ISO-DEP protocol layer with default config - ****************************************************************************** - */ -void rfalIsoDepInitialize(void); - -/*! - ****************************************************************************** - * \brief Initialize the ISO-DEP protocol - * - * Initialize the ISO-DEP protocol layer with additional parameters allowing - * to customise the protocol layer for specific behaviours - * - - * \param[in] compMode : Compliance mode to be performed - * \param[in] maxRetriesR : Number of retries for a R-Block - * Digital 2.0 B9 - nRETRY ACK/NAK: [2,5] - * \param[in] maxRetriesSnWTX : Number of retries for a S(WTX) (only in case - * of NAKs) Digital 2.0 B9 - nRETRY WTX[2,5] - * \param[in] maxRetriesSWTX : Number of overall S(WTX) retries. - * Use RFAL_ISODEP_MAX_WTX_RETRYS_ULTD for disabling - * this limit check Digital 2.0 16.2.5.2 - * \param[in] maxRetriesSDSL : Number of retries for a S(DESELECT) - * Digital 2.0 B9 - nRETRY DESELECT: [0,5] - * \param[in] maxRetriesI : Number of retries for a I-Block - * Digital 2.0 16.2.5.4 - * \param[in] maxRetriesRATS : Number of retries for RATS - * Digital 2.0 B7 - nRETRY RATS [0,1] - * - ****************************************************************************** - */ -void rfalIsoDepInitializeWithParams( - rfalComplianceMode compMode, - uint8_t maxRetriesR, - uint8_t maxRetriesSnWTX, - uint8_t maxRetriesSWTX, - uint8_t maxRetriesSDSL, - uint8_t maxRetriesI, - uint8_t maxRetriesRATS); - -/*! - ***************************************************************************** - * \brief FSxI to FSx - * - * Convert Frame Size for proximity coupling Device Integer (FSxI) to - * Frame Size for proximity coupling Device (FSx) - * - * FSD - maximum frame size for NFC Forum Device in Poll Mode - * FSC - maximum frame size for NFC Forum Device in Listen Mode - * - * FSxI = FSDI or FSCI - * FSx = FSD or FSC - * - * The FSD/FSC value includes the header and CRC - * - * \param[in] FSxI : Frame Size for proximity coupling Device Integer - * - * \return fsx : Frame Size for proximity coupling Device (FSD or FSC) - * - ***************************************************************************** - */ -uint16_t rfalIsoDepFSxI2FSx(uint8_t FSxI); - -/*! - ***************************************************************************** - * \brief FWI to FWT - * - * Convert Frame Waiting time Integer (FWI) to Frame Waiting Time (FWT) in - * 1/fc units - * - * \param[in] fwi : Frame Waiting time Integer - * - * \return fwt : Frame Waiting Time in 1/fc units - * - ***************************************************************************** - */ -uint32_t rfalIsoDepFWI2FWT(uint8_t fwi); - -/*! - ***************************************************************************** - * \brief Check if the buffer data contains a valid RATS command - * - * Check if it is a well formed RATS command with 2 bytes - * This function does not check the validity of FSDI and DID - * - * \param[in] buf : reference to buffer containing the data to be checked - * \param[in] bufLen : length of data in the buffer in bytes - * - * \return true if the data indicates a RATS command; false otherwise - ***************************************************************************** - */ -bool rfalIsoDepIsRats(const uint8_t* buf, uint8_t bufLen); - -/*! - ***************************************************************************** - * \brief Check if the buffer data contains a valid ATTRIB command - * - * Check if it is a well formed ATTRIB command, but does not check the - * validity of the information inside - * - * \param[in] buf : reference to buffer containing the data to be checked - * \param[in] bufLen : length of data in the buffer in bytes - * - * \return true if the data indicates a ATTRIB command; false otherwise - ***************************************************************************** - */ -bool rfalIsoDepIsAttrib(const uint8_t* buf, uint8_t bufLen); - -/*! - ***************************************************************************** - * \brief Start Listen Activation Handling - * - * Start Listen Activation Handling and setup to receive first I-block which may - * contain complete or partial APDU after activation is completed - * - * Pass in RATS for T4AT, or ATTRIB for T4BT, to handle ATS or ATTRIB Response respectively - * The Activation Handling handles ATS and ATTRIB Response; and additionally PPS Response - * if a PPS is received for T4AT. - * The method uses the current RFAL state machine to determine if it is expecting RATS or ATTRIB - * - * Activation is completed if PPS Response is sent or if first PDU is received in T4T-A - * Activation is completed if ATTRIB Response is sent in T4T-B - * - * \ref rfalIsoDepListenGetActivationStatus provide status if activation is completed. - * \ref rfalIsoDepStartTransceive shall be called right after activation is completed - * - * \param[in] atsParam : reference to ATS parameters - * \param[in] attribResParam : reference to ATTRIB_RES parameters - * \param[in] buf : reference to buffer containing RATS or ATTRIB - * \param[in] bufLen : length in bytes of the given buffer - * \param[in] actParam : reference to incoming reception information will be placed - * - * - * \warning Once the Activation has been completed the method - * rfalIsoDepGetTransceiveStatus() must be called. - * If activation has completed due to reception of a data block (not PPS) the - * buffer owned by the caller and passed on actParam must still contain this data. - * The first data will be processed (I-Block or S-DSL) by rfalIsoDepGetTransceiveStatus() - * inform the caller and then for the next transaction use rfalIsoDepStartTransceive() - * - * \return ERR_NONE : RATS/ATTRIB is valid and activation has started - * \return ERR_PARAM : Invalid parameters - * \return ERR_PROTO : Invalid request - * \return ERR_NOTSUPP : Feature not supported - ***************************************************************************** - */ -ReturnCode rfalIsoDepListenStartActivation( - rfalIsoDepAtsParam* atsParam, - const rfalIsoDepAttribResParam* attribResParam, - const uint8_t* buf, - uint16_t bufLen, - rfalIsoDepListenActvParam actParam); - -/*! - ***************************************************************************** - * \brief Get the current Activation Status - * - * \return ERR_NONE if Activation is already completed - * \return ERR_BUSY if Activation is ongoing - * \return ERR_LINK_LOSS if Remote Field is turned off - ***************************************************************************** - */ -ReturnCode rfalIsoDepListenGetActivationStatus(void); - -/*! - ***************************************************************************** - * \brief Get the ISO-DEP Communication Information - * - * Gets the maximum INF length in bytes based on current Frame Size - * for proximity coupling Device (FSD or FSC) excluding the header and CRC - * - * \return maximum INF length in bytes - ***************************************************************************** - */ -uint16_t rfalIsoDepGetMaxInfLen(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Start Transceive - * - * This method triggers a ISO-DEP Transceive containing a complete or - * partial APDU - * It transmits the given message and handles all protocol retransmitions, - * error handling and control messages - * - * The txBuf contains a complete or partial APDU (INF) to be transmitted - * The Prologue field will be manipulated by the Transceive - * - * If the buffer contains a partial APDU and is not the last block, - * then isTxChaining must be set to true - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalIsoDepStartTransceive(rfalIsoDepTxRxParam param); - -/*! - ***************************************************************************** - * \brief Get the Transceive status - * - * Returns the status of the ISO-DEP Transceive - * - * \warning When the other device is performing chaining once a chained - * block is received the error ERR_AGAIN is sent. At this point - * caller must handle the received data immediately. - * When ERR_AGAIN is returned an ACK has already been sent to - * the other device and the next block might be incoming. - * If rfalWorker() is called frequently it will place the next - * block on the given buffer - * - * - * \return ERR_NONE : Transceive has been completed successfully - * \return ERR_BUSY : Transceive is ongoing - * \return ERR_PROTO : Protocol error occurred - * \return ERR_TIMEOUT : Timeout error occurred - * \return ERR_SLEEP_REQ : Deselect has been received and responded - * \return ERR_NOMEM : The received INF does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : Communication is lost because Reader/Writer - * has turned off its field - * \return ERR_AGAIN : received one chaining block, continue to call - * this method to retrieve the remaining blocks - ***************************************************************************** - */ -ReturnCode rfalIsoDepGetTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Start APDU Transceive - * - * This method triggers a ISO-DEP Transceive containing a complete APDU - * It transmits the given message and handles all protocol retransmitions, - * error handling and control messages - * - * The txBuf contains a complete APDU to be transmitted - * The Prologue field will be manipulated by the Transceive - * - * \warning the txBuf will be modified during the transmission - * \warning the maximum RF frame which can be received is limited by param.tmpBuf - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalIsoDepStartApduTransceive(rfalIsoDepApduTxRxParam param); - -/*! - ***************************************************************************** - * \brief Get the APDU Transceive status - * - * \return ERR_NONE : if Transceive has been completed successfully - * \return ERR_BUSY : if Transceive is ongoing - * \return ERR_PROTO : if a protocol error occurred - * \return ERR_TIMEOUT : if a timeout error occurred - * \return ERR_SLEEP_REQ : if Deselect is received and responded - * \return ERR_NOMEM : if the received INF does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : if communication is lost because Reader/Writer - * has turned off its field - ***************************************************************************** - */ -ReturnCode rfalIsoDepGetApduTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Send RATS - * - * This sends a RATS to make a NFC-A Listen Device to enter - * ISO-DEP layer (ISO14443-4) and checks if the received ATS is valid - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[out] ats : pointer to place the ATS Response - * \param[out] atsLen : pointer to place the ATS length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, ATS received - ***************************************************************************** - */ -ReturnCode rfalIsoDepRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen); - -/*! - ***************************************************************************** - * \brief ISO-DEP Send PPS - * - * This sends a PPS to make a NFC-A Listen Device change the communications - * bit rate from 106kbps to one of the supported bit rates - * Additionally checks if the received PPS response is valid - * - * \param[in] DID : Device ID - * \param[in] DSI : DSI code the divisor from Listener (PICC) to Poller (PCD) - * \param[in] DRI : DRI code the divisor from Poller (PCD) to Listener (PICC) - * \param[out] ppsRes : pointer to place the PPS Response - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, PPS Response received - ***************************************************************************** - */ -ReturnCode rfalIsoDepPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes); - -/*! - ***************************************************************************** - * \brief ISO-DEP Send ATTRIB - * - * This sends a ATTRIB to make a NFC-B Listen Device to enter - * ISO-DEP layer (ISO14443-4) and checks if the received ATTRIB Response is valid - * - * \param[in] nfcid0 : NFCID0 to be used for the ATTRIB - * \param[in] PARAM1 : ATTRIB PARAM1 byte (communication parameters) - * \param[in] DSI : DSI code the divisor from Listener (PICC) to Poller (PCD) - * \param[in] DRI : DRI code the divisor from Poller (PCD) to Listener (PICC) - * \param[in] FSDI : PCD's Frame Size to be announced on the ATTRIB - * \param[in] PARAM3 : ATTRIB PARAM1 byte (protocol type) - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] HLInfo : pointer to Higher layer INF (NULL if none) - * \param[in] HLInfoLen : Length HLInfo - * \param[in] fwt : Frame Waiting Time to be used (from SENSB_RES) - * \param[out] attribRes : pointer to place the ATTRIB Response - * \param[out] attribResLen : pointer to place the ATTRIB Response length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, ATTRIB Response received - ***************************************************************************** - */ -ReturnCode rfalIsoDepATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen); - -/*! - ***************************************************************************** - * \brief Deselects PICC - * - * This function sends a deselect command to PICC and waits for it`s - * response in a blocking way - * - * \return ERR_NONE : Deselect successfully sent and acknowledged by PICC - * \return ERR_TIMEOUT: No response rcvd from PICC - * - ***************************************************************************** - */ -ReturnCode rfalIsoDepDeselect(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Handle NFC-A Activation - * - * This performs a NFC-A Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It sends RATS and if the higher bit rates are supported by - * both devices it additionally sends PPS - * Once Activated all details of the device are provided on isoDepDev - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollAHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Handle NFC-B Activation - * - * This performs a NFC-B Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It sends ATTRIB and calculates supported higher bit rates of both - * devices and performs activation. - * Once Activated all details of the device are provided on isoDepDev - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[in] PARAM1 : ATTRIB PARAM1 byte (communication parameters) - * \param[in] nfcbDev : pointer to the NFC-B Device containing the SENSB_RES - * \param[in] HLInfo : pointer to Higher layer INF (NULL if none) - * \param[in] HLInfoLen : Length HLInfo - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollBHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Handle S(Parameters) - * - * This checks if PICC supports S(PARAMETERS), retrieves PICC's - * capabilities and sets the Bit Rate at the highest supported by both - * devices - * - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * \param[in] maxTxBR : Maximum Tx bit rate supported by PCD - * \param[in] maxRxBR : Maximum Rx bit rate supported by PCD - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, S(PARAMETERS) selection successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollHandleSParameters( - rfalIsoDepDevice* isoDepDev, - rfalBitRate maxTxBR, - rfalBitRate maxRxBR); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Start NFC-A Activation - * - * This starts a NFC-A Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It sends RATS and if the higher bit rates are supported by - * both devices it additionally sends PPS - * Once Activated all details of the device are provided on isoDepDev - * - * - * \see rfalIsoDepPollAGetActivationStatus - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, start of asynchronous operation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollAStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Get NFC-A Activation Status - * - * Returns the activation status started by rfalIsoDepPollAStartActivation - * - * \see rfalIsoDepPollAStartActivation - * - * \return ERR_BUSY : Operation is ongoing - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollAGetActivationStatus(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Start NFC-B Activation - * - * This starts a NFC-B Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It will send ATTRIB and calculate supported higher bit rates of both - * devices and perform activation. - * Once Activated all details of the device are provided on isoDepDev - * - * \see rfalIsoDepPollBGetActivationStatus - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[in] PARAM1 : ATTRIB PARAM1 byte (communication parameters) - * \param[in] nfcbDev : pointer to the NFC-B Device containing the SENSB_RES - * \param[in] HLInfo : pointer to Higher layer INF (NULL if none) - * \param[in] HLInfoLen : Length HLInfo - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, start of asynchronous operation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollBStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Get NFC-B Activation Status - * - * Returns the activation status started by rfalIsoDepPollBStartActivation - * - * \see rfalIsoDepPollBStartActivation - * - * \return ERR_BUSY : Operation is ongoing - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollBGetActivationStatus(void); - -#endif /* RFAL_ISODEP_H_ */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfc.h b/lib/ST25RFAL002/include/rfal_nfc.h deleted file mode 100644 index 88a740a0a7..0000000000 --- a/lib/ST25RFAL002/include/rfal_nfc.h +++ /dev/null @@ -1,425 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfc.h - * - * \brief RFAL NFC device - * - * This module provides the required features to behave as an NFC Poller - * or Listener device. It grants an easy to use interface for the following - * activities: Technology Detection, Collision Resolution, Activation, - * Data Exchange, and Deactivation - * - * This layer is influenced by (but not fully aligned with) the NFC Forum - * specifications, in particular: Activity 2.0 and NCI 2.0 - * - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HL - * \brief RFAL Higher Layer - * @{ - * - * \addtogroup NFC - * \brief RFAL NFC Device - * @{ - * - */ - -#ifndef RFAL_NFC_H -#define RFAL_NFC_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_nfca.h" -#include "rfal_nfcb.h" -#include "rfal_nfcf.h" -#include "rfal_nfcv.h" -#include "rfal_st25tb.h" -#include "rfal_nfcDep.h" -#include "rfal_isoDep.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define RFAL_NFC_TECH_NONE 0x0000U /*!< No technology */ -#define RFAL_NFC_POLL_TECH_A 0x0001U /*!< NFC-A technology Flag */ -#define RFAL_NFC_POLL_TECH_B 0x0002U /*!< NFC-B technology Flag */ -#define RFAL_NFC_POLL_TECH_F 0x0004U /*!< NFC-F technology Flag */ -#define RFAL_NFC_POLL_TECH_V 0x0008U /*!< NFC-V technology Flag */ -#define RFAL_NFC_POLL_TECH_AP2P 0x0010U /*!< AP2P technology Flag */ -#define RFAL_NFC_POLL_TECH_ST25TB 0x0020U /*!< ST25TB technology Flag */ -#define RFAL_NFC_LISTEN_TECH_A 0x1000U /*!< NFC-V technology Flag */ -#define RFAL_NFC_LISTEN_TECH_B 0x2000U /*!< NFC-V technology Flag */ -#define RFAL_NFC_LISTEN_TECH_F 0x4000U /*!< NFC-V technology Flag */ -#define RFAL_NFC_LISTEN_TECH_AP2P 0x8000U /*!< NFC-V technology Flag */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Checks if a device is currently activated */ -#define rfalNfcIsDevActivated(st) \ - (((st) >= RFAL_NFC_STATE_ACTIVATED) && ((st) < RFAL_NFC_STATE_DEACTIVATION)) - -/*! Checks if a device is in discovery */ -#define rfalNfcIsInDiscovery(st) \ - (((st) >= RFAL_NFC_STATE_START_DISCOVERY) && ((st) < RFAL_NFC_STATE_ACTIVATED)) - -/*! Checks if remote device is in Poll mode */ -#define rfalNfcIsRemDevPoller(tp) \ - (((tp) >= RFAL_NFC_POLL_TYPE_NFCA) && ((tp) <= RFAL_NFC_POLL_TYPE_AP2P)) - -/*! Checks if remote device is in Listen mode */ -#define rfalNfcIsRemDevListener(tp) \ - (((int16_t)(tp) >= (int16_t)RFAL_NFC_LISTEN_TYPE_NFCA) && ((tp) <= RFAL_NFC_LISTEN_TYPE_AP2P)) - -/* -****************************************************************************** -* GLOBAL ENUMS -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Main state */ -typedef enum { - RFAL_NFC_STATE_NOTINIT = 0, /*!< Not Initialized state */ - RFAL_NFC_STATE_IDLE = 1, /*!< Initialize state */ - RFAL_NFC_STATE_START_DISCOVERY = 2, /*!< Start Discovery loop state */ - RFAL_NFC_STATE_WAKEUP_MODE = 3, /*!< Wake-Up state */ - RFAL_NFC_STATE_POLL_TECHDETECT = 10, /*!< Technology Detection state */ - RFAL_NFC_STATE_POLL_COLAVOIDANCE = 11, /*!< Collision Avoidance state */ - RFAL_NFC_STATE_POLL_SELECT = 12, /*!< Wait for Selection state */ - RFAL_NFC_STATE_POLL_ACTIVATION = 13, /*!< Activation state */ - RFAL_NFC_STATE_LISTEN_TECHDETECT = 20, /*!< Listen Tech Detect */ - RFAL_NFC_STATE_LISTEN_COLAVOIDANCE = 21, /*!< Listen Collision Avoidance */ - RFAL_NFC_STATE_LISTEN_ACTIVATION = 22, /*!< Listen Activation state */ - RFAL_NFC_STATE_LISTEN_SLEEP = 23, /*!< Listen Sleep state */ - RFAL_NFC_STATE_ACTIVATED = 30, /*!< Activated state */ - RFAL_NFC_STATE_DATAEXCHANGE = 31, /*!< Data Exchange Start state */ - RFAL_NFC_STATE_DATAEXCHANGE_DONE = 33, /*!< Data Exchange terminated */ - RFAL_NFC_STATE_DEACTIVATION = 34 /*!< Deactivation state */ -} rfalNfcState; - -/*! Device type */ -typedef enum { - RFAL_NFC_LISTEN_TYPE_NFCA = 0, /*!< NFC-A Listener device type */ - RFAL_NFC_LISTEN_TYPE_NFCB = 1, /*!< NFC-B Listener device type */ - RFAL_NFC_LISTEN_TYPE_NFCF = 2, /*!< NFC-F Listener device type */ - RFAL_NFC_LISTEN_TYPE_NFCV = 3, /*!< NFC-V Listener device type */ - RFAL_NFC_LISTEN_TYPE_ST25TB = 4, /*!< ST25TB Listener device type */ - RFAL_NFC_LISTEN_TYPE_AP2P = 5, /*!< AP2P Listener device type */ - RFAL_NFC_POLL_TYPE_NFCA = 10, /*!< NFC-A Poller device type */ - RFAL_NFC_POLL_TYPE_NFCB = 11, /*!< NFC-B Poller device type */ - RFAL_NFC_POLL_TYPE_NFCF = 12, /*!< NFC-F Poller device type */ - RFAL_NFC_POLL_TYPE_NFCV = 13, /*!< NFC-V Poller device type */ - RFAL_NFC_POLL_TYPE_AP2P = 15 /*!< AP2P Poller device type */ -} rfalNfcDevType; - -/*! Device interface */ -typedef enum { - RFAL_NFC_INTERFACE_RF = 0, /*!< RF Frame interface */ - RFAL_NFC_INTERFACE_ISODEP = 1, /*!< ISO-DEP interface */ - RFAL_NFC_INTERFACE_NFCDEP = 2 /*!< NFC-DEP interface */ -} rfalNfcRfInterface; - -/*! Device struct containing all its details */ -typedef struct { - rfalNfcDevType type; /*!< Device's type */ - union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one technology at a time */ - rfalNfcaListenDevice nfca; /*!< NFC-A Listen Device instance */ - rfalNfcbListenDevice nfcb; /*!< NFC-B Listen Device instance */ - rfalNfcfListenDevice nfcf; /*!< NFC-F Listen Device instance */ - rfalNfcvListenDevice nfcv; /*!< NFC-V Listen Device instance */ - rfalSt25tbListenDevice st25tb; /*!< ST25TB Listen Device instance*/ - } dev; /*!< Device's instance */ - - uint8_t* nfcid; /*!< Device's NFCID */ - uint8_t nfcidLen; /*!< Device's NFCID length */ - rfalNfcRfInterface rfInterface; /*!< Device's interface */ - - union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one protocol at a time */ - rfalIsoDepDevice isoDep; /*!< ISO-DEP instance */ - rfalNfcDepDevice nfcDep; /*!< NFC-DEP instance */ - } proto; /*!< Device's protocol */ -} rfalNfcDevice; - -/*! Discovery parameters */ -typedef struct { - rfalComplianceMode compMode; /*!< Compliance mode to be used */ - uint16_t techs2Find; /*!< Technologies to search for */ - uint16_t totalDuration; /*!< Duration of a whole Poll + Listen cycle */ - uint8_t devLimit; /*!< Max number of devices */ - rfalBitRate maxBR; /*!< Max Bit rate to be used for communications */ - - rfalBitRate nfcfBR; /*!< Bit rate to poll for NFC-F */ - uint8_t - nfcid3[RFAL_NFCDEP_NFCID3_LEN]; /*!< NFCID3 to be used on the ATR_REQ/ATR_RES */ - uint8_t GB[RFAL_NFCDEP_GB_MAX_LEN]; /*!< General bytes to be used on the ATR-REQ */ - uint8_t GBLen; /*!< Length of the General Bytes */ - rfalBitRate ap2pBR; /*!< Bit rate to poll for AP2P */ - - rfalLmConfPA lmConfigPA; /*!< Configuration for Passive Listen mode NFC-A */ - rfalLmConfPF lmConfigPF; /*!< Configuration for Passive Listen mode NFC-A */ - - void (*notifyCb)(rfalNfcState st); /*!< Callback to Notify upper layer */ - - bool wakeupEnabled; /*!< Enable Wake-Up mode before polling */ - bool wakeupConfigDefault; /*!< Wake-Up mode default configuration */ - rfalWakeUpConfig wakeupConfig; /*!< Wake-Up mode configuration */ - - bool activate_after_sak; // Set device to Active mode after SAK response -} rfalNfcDiscoverParam; - -/*! Buffer union, only one interface is used at a time */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one interface at a time */ - uint8_t rfBuf[RFAL_FEATURE_NFC_RF_BUF_LEN]; /*!< RF buffer */ - rfalIsoDepApduBufFormat isoDepBuf; /*!< ISO-DEP buffer format (with header/prologue) */ - rfalNfcDepPduBufFormat nfcDepBuf; /*!< NFC-DEP buffer format (with header/prologue) */ -} rfalNfcBuffer; - -/*******************************************************************************/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief RFAL NFC Worker - * - * It runs the internal state machine and runs the RFAL RF worker. - ***************************************************************************** - */ -void rfalNfcWorker(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Initialize - * - * It initializes this module and its dependencies - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcInitialize(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Discovery - * - * It set the device in Discovery state. - * In discovery it will Poll and/or Listen for the technologies configured, - * and perform Wake-up mode if configured to do so. - * - * The device list passed on disParams must not be empty. - * The number of devices on the list is indicated by the devLimit and shall - * be at >= 1. - * - * \param[in] disParams : discovery configuration parameters - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcDiscover(const rfalNfcDiscoverParam* disParams); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get State - * - * It returns the current state - * - * \return rfalNfcState : the current state - ***************************************************************************** - */ -rfalNfcState rfalNfcGetState(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get Devices Found - * - * It returns the location of the device list and the number of - * devices found. - * - * \param[out] devList : device list location - * \param[out] devCnt : number of devices found - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * Discovery still ongoing - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcGetDevicesFound(rfalNfcDevice** devList, uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get Active Device - * - * It returns the location of the device current Active device - * - * \param[out] dev : device info location - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * No device activated - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcGetActiveDevice(rfalNfcDevice** dev); - -/*! - ***************************************************************************** - * \brief RFAL NFC Select Device - * - * It selects the device to be activated. - * It shall be called when more than one device has been identified to - * indiacte which device shall be active - * - * \param[in] devIdx : device index to be activated - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * Not in select state - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcSelect(uint8_t devIdx); - -/*! - ***************************************************************************** - * \brief RFAL NFC Start Data Exchange - * - * After a device has been activated, it starts a data exchange. - * It handles automatically which interface/protocol to be used and acts accordingly. - * - * In Listen mode the first frame/data shall be sent by the Reader/Initiator - * therefore this method must be called first with txDataLen set to zero - * to retrieve the rxData and rcvLen locations. - * - * - * \param[in] txData : data to be transmitted - * \param[in] txDataLen : size of the data to be transmitted - * \param[out] rxData : location of the received data after operation is completed - * \param[out] rvdLen : location of thelength of the received data - * \param[in] fwt : FWT to be used in case of RF interface. - * If ISO-DEP or NFC-DEP interface is used, this will be ignored - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcDataExchangeStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t tx_flag); - -ReturnCode rfalNfcDataExchangeCustomStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t flags); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get Data Exchange Status - * - * Gets current Data Exchange status - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_AGAIN : received one chaining block, copy received data - * and continue to call this method to retrieve the - * remaining blocks - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalNfcDataExchangeGetStatus(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Deactivate - * - * It triggers the deactivation procedure to terminate communications with - * remote device. At the end the field will be turned off. - * - * \param[in] discovery : TRUE if after deactivation go back into discovery - * : FALSE if after deactivation remain in idle - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcDeactivate(bool discovery); - -#endif /* RFAL_NFC_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcDep.h b/lib/ST25RFAL002/include/rfal_nfcDep.h deleted file mode 100644 index 9cf770e53b..0000000000 --- a/lib/ST25RFAL002/include/rfal_nfcDep.h +++ /dev/null @@ -1,830 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcDep.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-DEP protocol - * - * NFC-DEP is also known as NFCIP - Near Field Communication - * Interface and Protocol - * - * This implementation was based on the following specs: - * - NFC Forum Digital 1.1 - * - ECMA 340 3rd Edition 2013 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-DEP - * \brief RFAL NFC-DEP Module - * @{ - */ - -#ifndef RFAL_NFCDEP_H_ -#define RFAL_NFCDEP_H_ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFC_DEP -#define RFAL_FEATURE_NFC_DEP \ - false /*!< NFC-DEP module configuration missing. Disabled by default */ -#endif - -/* If module is disabled remove the need for the user to set lengths */ -#if !RFAL_FEATURE_NFC_DEP -#undef RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN -#undef RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN - -#define RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN 1U /*!< NFC-DEP Block/Payload length, set to "none" */ -#define RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN 1U /*!< NFC-DEP PDU length, set to "none" */ -#endif /* !RFAL_FEATURE_NFC_DEP */ - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define RFAL_NFCDEP_FRAME_SIZE_MAX_LEN \ - 254U /*!< Maximum Frame Size Digital 2.0 Table 90 */ -#define RFAL_NFCDEP_DEPREQ_HEADER_LEN \ - 5U /*!< DEP_REQ header length: CMD_TYPE + CMD_CMD + PBF + DID + NAD */ - -/*! Length NFCIP DEP REQ or RES header (incl LEN) */ -#define RFAL_NFCDEP_DEP_HEADER \ - (RFAL_NFCDEP_LEN_LEN + RFAL_NFCDEP_CMDTYPE_LEN + RFAL_NFCDEP_CMD_LEN + RFAL_NFCDEP_DEP_PFB_LEN) -#define RFAL_NFCDEP_HEADER \ - (RFAL_NFCDEP_CMDTYPE_LEN + RFAL_NFCDEP_CMD_LEN) /*!< NFCIP header length */ -#define RFAL_NFCDEP_SB_LEN \ - 1U /*!< SB length on NFCIP fram for NFC-A */ -#define RFAL_NFCDEP_LEN_LEN \ - 1U /*!< LEN length on NFCIP frame */ -#define RFAL_NFCDEP_CMDTYPE_LEN \ - 1U /*!< Length of the cmd type (REQ | RES) on NFCIP frame */ -#define RFAL_NFCDEP_CMD_LEN \ - 1U /*!< Length of the cmd on NFCIP frame */ -#define RFAL_NFCDEP_DID_LEN \ - 1U /*!< Length of did on NFCIP frame */ -#define RFAL_NFCDEP_DEP_PFB_LEN \ - 1U /*!< Length of the PFB field on NFCIP frame */ - -#define RFAL_NFCDEP_DSL_RLS_LEN_NO_DID \ - (RFAL_NFCDEP_LEN_LEN + RFAL_NFCDEP_CMDTYPE_LEN + \ - RFAL_NFCDEP_CMD_LEN) /*!< Length of DSL_REQ and RLS_REQ with no DID */ -#define RFAL_NFCDEP_DSL_RLS_LEN_DID \ - (RFAL_NFCDEP_DSL_RLS_LEN_NO_DID + \ - RFAL_NFCDEP_DID_LEN) /*!< Length of DSL_REQ and RLS_REQ with DID */ - -#define RFAL_NFCDEP_FS_VAL_MIN \ - 64U /*!< Minimum LR value */ -#define RFAL_NFCDEP_LR_VAL_MASK \ - 0x03U /*!< Bit mask for a LR value */ -#define RFAL_NFCDEP_PP_LR_MASK \ - 0x30U /*!< Bit mask for LR value in PP byte on a ATR REQ/RES */ -#define RFAL_NFCDEP_PP_LR_SHIFT \ - 4U /*!< Position of LR value in PP byte on a ATR REQ/RES */ - -#define RFAL_NFCDEP_DID_MAX \ - 14U /*!< Max DID value Digital 14.6.2.3 */ -#define RFAL_NFCDEP_DID_KEEP \ - 0xFFU /*!< Keep DID value already configured */ -#define RFAL_NFCDEP_DID_NO \ - 0x00U /*!< No DID shall be used */ -#define RFAL_NFCDEP_NAD_NO \ - 0x00U /*!< No NAD shall be used */ - -#define RFAL_NFCDEP_OPER_RTOX_REQ_DIS \ - 0x01U /*!< Operation config: RTOX REQ disable */ -#define RFAL_NFCDEP_OPER_RTOX_REQ_EN \ - 0x00U /*!< Operation config: RTOX REQ enable */ - -#define RFAL_NFCDEP_OPER_ATN_DIS \ - 0x00U /*!< Operation config: ATN disable */ -#define RFAL_NFCDEP_OPER_ATN_EN \ - 0x02U /*!< Operation config: ATN enable */ - -#define RFAL_NFCDEP_OPER_EMPTY_DEP_DIS \ - 0x04U /*!< Operation config: empty DEPs disable */ -#define RFAL_NFCDEP_OPER_EMPTY_DEP_EN \ - 0x00U /*!< Operation config: empty DEPs enable */ - -#define RFAL_NFCDEP_OPER_FULL_MI_DIS \ - 0x00U /*!< Operation config: full chaining DEPs disable */ -#define RFAL_NFCDEP_OPER_FULL_MI_EN \ - 0x08U /*!< Operation config: full chaining DEPs enable */ - -#define RFAL_NFCDEP_BRS_MAINTAIN \ - 0xC0U /*!< Value signalling that BR is to be maintained (no PSL) */ -#define RFAL_NFCDEP_BRS_Dx_MASK \ - 0x07U /*!< Value signalling that BR is to be maintained (no PSL) */ -#define RFAL_NFCDEP_BRS_DSI_POS \ - 3U /*!< Value signalling that BR is to be maintained (no PSL) */ - -#define RFAL_NFCDEP_WT_DELTA \ - (16U - RFAL_NFCDEP_WT_DELTA_ADJUST) /*!< NFC-DEP dWRT (adjusted) Digital 2.0 B.10 */ -#define RFAL_NFCDEP_WT_DELTA_ADJUST \ - 4U /*!< dWRT value adjustment */ - -#define RFAL_NFCDEP_ATR_REQ_NFCID3_POS \ - 2U /*!< NFCID3 offset in ATR_REQ frame */ -#define RFAL_NFCDEP_NFCID3_LEN \ - 10U /*!< NFCID3 Length */ - -#define RFAL_NFCDEP_LEN_MIN \ - 3U /*!< Minimum length byte LEN value */ -#define RFAL_NFCDEP_LEN_MAX \ - 255U /*!< Maximum length byte LEN value */ - -#define RFAL_NFCDEP_ATRRES_HEADER_LEN \ - 2U /*!< ATR RES Header Len: CmdType: 0xD5 + Cod: 0x01 */ -#define RFAL_NFCDEP_ATRRES_MIN_LEN \ - 17U /*!< Minimum length for an ATR RES */ -#define RFAL_NFCDEP_ATRRES_MAX_LEN \ - 64U /*!< Maximum length for an ATR RES Digital 1.0 14.6.1 */ -#define RFAL_NFCDEP_ATRREQ_MIN_LEN \ - 16U /*!< Minimum length for an ATR REQ */ -#define RFAL_NFCDEP_ATRREQ_MAX_LEN \ - RFAL_NFCDEP_ATRRES_MAX_LEN /*!< Maximum length for an ATR REQ Digital 1.0 14.6.1 */ - -#define RFAL_NFCDEP_GB_MAX_LEN \ - (RFAL_NFCDEP_ATRREQ_MAX_LEN - \ - RFAL_NFCDEP_ATRREQ_MIN_LEN) /*!< Maximum length the General Bytes on ATR Digital 1.1 16.6.3 */ - -#define RFAL_NFCDEP_WT_INI_DEFAULT \ - RFAL_NFCDEP_WT_INI_MAX /*!< WT Initiator default value Digital 1.0 14.6.3.8 */ -#define RFAL_NFCDEP_WT_INI_MIN 0U /*!< WT Initiator minimum value Digital 1.0 14.6.3.8 */ -#define RFAL_NFCDEP_WT_INI_MAX 14U /*!< WT Initiator maximum value Digital 1.0 14.6.3.8 A.10 */ -#define RFAL_NFCDEP_RWT_INI_MAX \ - rfalNfcDepWT2RWT(RFAL_NFCDEP_WT_INI_MAX) /*!< RWT Initiator maximum value */ - -#define RFAL_NFCDEP_WT_TRG_MAX_D10 8U /*!< WT target max Digital 1.0 14.6.3.8 A.10 */ -#define RFAL_NFCDEP_WT_TRG_MAX_D11 14U /*!< WT target max Digital 1.1 16.6.3.9 A.9 */ -#define RFAL_NFCDEP_WT_TRG_MAX_L13 10U /*!< WT target max [LLCP] 1.3 6.2.1 */ -#define RFAL_NFCDEP_WT_TRG_MAX \ - RFAL_NFCDEP_WT_TRG_MAX_D11 /*!< WT target max Digital x.x | LLCP x.x */ -#define RFAL_NFCDEP_RWT_TRG_MAX \ - rfalNfcDepWT2RWT(RFAL_NFCDEP_WT_TRG_MAX) /*!< RWT Initiator maximum value */ - -/*! Maximum Frame Waiting Time = ((256 * 16/fc)*2^FWImax) = ((256*16/fc)*2^14) = (1048576 / 64)/fc = (100000h*64)/fc */ -#define RFAL_NFCDEP_MAX_FWT ((uint32_t)1U << 20) - -#define RFAL_NFCDEP_WT_MASK \ - 0x0FU /*!< Bit mask for the Wait Time value */ - -#define RFAL_NFCDEP_BR_MASK_106 \ - 0x01U /*!< Enable mask bit rate 106 */ -#define RFAL_NFCDEP_BR_MASK_212 \ - 0x02U /*!< Enable mask bit rate 242 */ -#define RFAL_NFCDEP_BR_MASK_424 \ - 0x04U /*!< Enable mask bit rate 424 */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -#define rfalNfcDepWT2RWT(wt) \ - ((uint32_t)1U \ - << (((uint32_t)(wt)&RFAL_NFCDEP_WT_MASK) + \ - 12U)) /*!< Converts WT value to RWT (1/fc) */ - -/*! Returns the BRS value from the given bit rate */ -#define rfalNfcDepDx2BRS(br) \ - ((((uint8_t)(br)&RFAL_NFCDEP_BRS_Dx_MASK) << RFAL_NFCDEP_BRS_DSI_POS) | \ - ((uint8_t)(br)&RFAL_NFCDEP_BRS_Dx_MASK)) - -#define rfalNfcDepBRS2DRI(brs) \ - (uint8_t)(( \ - uint8_t)(brs)&RFAL_NFCDEP_BRS_Dx_MASK) /*!< Returns the DRI value from the given BRS byte */ -#define rfalNfcDepBRS2DSI(brs) \ - (uint8_t)( \ - ((uint8_t)(brs) >> RFAL_NFCDEP_BRS_DSI_POS) & \ - RFAL_NFCDEP_BRS_Dx_MASK) /*!< Returns the DSI value from the given BRS byte */ - -#define rfalNfcDepPP2LR(PPx) \ - (((uint8_t)(PPx)&RFAL_NFCDEP_PP_LR_MASK) >> \ - RFAL_NFCDEP_PP_LR_SHIFT) /*!< Returns the LR value from the given PPx byte */ -#define rfalNfcDepLR2PP(LRx) \ - (((uint8_t)(LRx) << RFAL_NFCDEP_PP_LR_SHIFT) & \ - RFAL_NFCDEP_PP_LR_MASK) /*!< Returns the PP byte with the given LRx value */ - -/*! Returns the Frame size value from the given LRx value */ -#define rfalNfcDepLR2FS(LRx) \ - (uint16_t)( \ - MIN((RFAL_NFCDEP_FS_VAL_MIN * ((uint16_t)(LRx) + 1U)), RFAL_NFCDEP_FRAME_SIZE_MAX_LEN)) - -/*! - * Despite DIGITAL 1.0 14.6.2.1 stating that the last two bytes may filled with - * any value, some devices (Samsung Google Nexus) only accept when these are 0 */ -#define rfalNfcDepSetNFCID(dst, src, len) \ - ST_MEMSET((dst), 0x00, RFAL_NFCDEP_NFCID3_LEN); \ - if((len) > 0U) { \ - ST_MEMCPY((dst), (src), (len)); \ - } - -/* - ****************************************************************************** - * GLOBAL ENUMERATIONS - ****************************************************************************** - */ - -/*! Enumeration of NFC-DEP bit rate in ATR Digital 1.0 Table 93 and 94 */ -enum { - RFAL_NFCDEP_Bx_NO_HIGH_BR = 0x00, /*!< Peer supports no high bit rates */ - RFAL_NFCDEP_Bx_08_848 = 0x01, /*!< Peer also supports 848 */ - RFAL_NFCDEP_Bx_16_1695 = 0x02, /*!< Peer also supports 1695 */ - RFAL_NFCDEP_Bx_32_3390 = 0x04, /*!< Peer also supports 3390 */ - RFAL_NFCDEP_Bx_64_6780 = 0x08 /*!< Peer also supports 6780 */ -}; - -/*! Enumeration of NFC-DEP bit rate Divider in PSL Digital 1.0 Table 100 */ -enum { - RFAL_NFCDEP_Dx_01_106 = RFAL_BR_106, /*!< Divisor D = 1 : bit rate = 106 */ - RFAL_NFCDEP_Dx_02_212 = RFAL_BR_212, /*!< Divisor D = 2 : bit rate = 212 */ - RFAL_NFCDEP_Dx_04_424 = RFAL_BR_424, /*!< Divisor D = 4 : bit rate = 424 */ - RFAL_NFCDEP_Dx_08_848 = RFAL_BR_848, /*!< Divisor D = 8 : bit rate = 848 */ - RFAL_NFCDEP_Dx_16_1695 = RFAL_BR_1695, /*!< Divisor D = 16 : bit rate = 1695 */ - RFAL_NFCDEP_Dx_32_3390 = RFAL_BR_3390, /*!< Divisor D = 32 : bit rate = 3390 */ - RFAL_NFCDEP_Dx_64_6780 = RFAL_BR_6780 /*!< Divisor D = 64 : bit rate = 6780 */ -}; - -/*! Enumeration of NFC-DEP Length Reduction (LR) Digital 1.0 Table 91 */ -enum { - RFAL_NFCDEP_LR_64 = 0x00, /*!< Maximum payload size is 64 bytes */ - RFAL_NFCDEP_LR_128 = 0x01, /*!< Maximum payload size is 128 bytes */ - RFAL_NFCDEP_LR_192 = 0x02, /*!< Maximum payload size is 192 bytes */ - RFAL_NFCDEP_LR_254 = 0x03 /*!< Maximum payload size is 254 bytes */ -}; - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -/*! NFC-DEP callback to check if upper layer has deactivation pending */ -typedef bool (*rfalNfcDepDeactCallback)(void); - -/*! Enumeration of the nfcip communication modes */ -typedef enum { - RFAL_NFCDEP_COMM_PASSIVE, /*!< Passive communication mode */ - RFAL_NFCDEP_COMM_ACTIVE /*!< Active communication mode */ -} rfalNfcDepCommMode; - -/*! Enumeration of the nfcip roles */ -typedef enum { - RFAL_NFCDEP_ROLE_INITIATOR, /*!< Perform as Initiator */ - RFAL_NFCDEP_ROLE_TARGET /*!< Perform as Target */ -} rfalNfcDepRole; - -/*! Struct that holds all NFCIP configs */ -typedef struct { - rfalNfcDepRole role; /*!< Current NFCIP role */ - rfalNfcDepCommMode commMode; /*!< Current NFCIP communication mode */ - uint8_t oper; /*!< Operation config similar to NCI 1.0 Table 81 */ - - uint8_t did; /*!< Current Device ID (DID) */ - uint8_t nad; /*!< Current Node Addressing (NAD) */ - uint8_t bs; /*!< Bit rate in Sending Direction */ - uint8_t br; /*!< Bit rate in Receiving Direction */ - uint8_t nfcid[RFAL_NFCDEP_NFCID3_LEN]; /*!< Pointer to the NFCID to be used */ - uint8_t nfcidLen; /*!< Length of the given NFCID in nfcid */ - uint8_t gb[RFAL_NFCDEP_GB_MAX_LEN]; /*!< Pointer General Bytes (GB) to be used */ - uint8_t gbLen; /*!< Length of the given GB in gb */ - uint8_t lr; /*!< Length Reduction (LR) to be used */ - uint8_t to; /*!< Timeout (TO) to be used */ - uint32_t fwt; /*!< Frame Waiting Time (FWT) to be used */ - uint32_t dFwt; /*!< Delta Frame Waiting Time (dFWT) to be used */ -} rfalNfcDepConfigs; - -/*! ATR_REQ command Digital 1.1 16.6.2 */ -typedef struct { - uint8_t CMD1; /*!< Command format 0xD4 */ - uint8_t CMD2; /*!< Command Value */ - uint8_t NFCID3[RFAL_NFCDEP_NFCID3_LEN]; /*!< NFCID3 value */ - uint8_t DID; /*!< DID */ - uint8_t BSi; /*!< Sending Bitrate for Initiator */ - uint8_t BRi; /*!< Receiving Bitrate for Initiator */ - uint8_t PPi; /*!< Optional Parameters presence indicator */ - uint8_t GBi[RFAL_NFCDEP_GB_MAX_LEN]; /*!< General Bytes */ -} rfalNfcDepAtrReq; - -/*! ATR_RES response Digital 1.1 16.6.3 */ -typedef struct { - uint8_t CMD1; /*!< Response Byte 0xD5 */ - uint8_t CMD2; /*!< Command Value */ - uint8_t NFCID3[RFAL_NFCDEP_NFCID3_LEN]; /*!< NFCID3 value */ - uint8_t DID; /*!< DID */ - uint8_t BSt; /*!< Sending Bitrate for Initiator */ - uint8_t BRt; /*!< Receiving Bitrate for Initiator */ - uint8_t TO; /*!< Timeout */ - uint8_t PPt; /*!< Optional Parameters presence indicator */ - uint8_t GBt[RFAL_NFCDEP_GB_MAX_LEN]; /*!< General Bytes */ -} rfalNfcDepAtrRes; - -/*! Structure of transmit I-PDU Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_NFCDEP_DEPREQ_HEADER_LEN]; /*!< Prologue space for NFC-DEP header*/ - uint8_t inf[RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN]; /*!< INF | Data area of the buffer */ -} rfalNfcDepBufFormat; - -/*! Structure of APDU Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_NFCDEP_DEPREQ_HEADER_LEN]; /*!< Prologue/SoD buffer */ - uint8_t pdu[RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN]; /*!< Complete PDU/Payload buffer */ -} rfalNfcDepPduBufFormat; - -/*! Activation info as Initiator and Target */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently , device is only initiatior or target a time. No problem can occur. */ - struct { - rfalNfcDepAtrRes ATR_RES; /*!< ATR RES (Initiator mode) */ - uint8_t ATR_RESLen; /*!< ATR RES length (Initiator mode) */ - } Target; /*!< Target */ - struct { - rfalNfcDepAtrReq ATR_REQ; /*!< ATR REQ (Target mode) */ - uint8_t ATR_REQLen; /*!< ATR REQ length (Target mode) */ - } Initiator; /*!< Initiator */ -} rfalNfcDepActivation; - -/*! NFC-DEP device Info */ -typedef struct { - uint8_t GBLen; /*!< General Bytes length */ - uint8_t WT; /*!< WT to be used (ignored in Listen Mode) */ - uint32_t FWT; /*!< FWT to be used (1/fc)(ignored Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used (1/fc) */ - uint8_t LR; /*!< Length Reduction coding the max payload */ - uint16_t FS; /*!< Frame Size */ - rfalBitRate DSI; /*!< Bit Rate coding from Initiator to Target */ - rfalBitRate DRI; /*!< Bit Rate coding from Target to Initiator */ - uint8_t DID; /*!< Device ID (RFAL_NFCDEP_DID_NO if no DID) */ - uint8_t NAD; /*!< Node ADdress (RFAL_NFCDEP_NAD_NO if no NAD)*/ -} rfalNfcDepInfo; - -/*! NFC-DEP Device structure */ -typedef struct { - rfalNfcDepActivation activation; /*!< Activation Info */ - rfalNfcDepInfo info; /*!< NFC-DEP device Info */ -} rfalNfcDepDevice; - -/*! NFCIP Protocol structure for P2P Target - * - * operParam : derives from NFC-Forum NCI NFC-DEP Operation Parameter - * NCI 1.1 Table 86: NFC-DEP Operation Parameter - * and it's a bit mask composed as: - * [ 0000b - * | Chain SHALL use max. Transport Data Byte[1b] - * | I-PDU with no Transport Data SHALL NOT be sent [1b] - * | NFC-DEP Target SHALL NOT send RTOX request [1b] - * ] - * - */ -typedef struct { - rfalNfcDepCommMode commMode; /*!< Initiator in Active P2P or Passive P2P*/ - uint8_t operParam; /*!< NFC-DEP Operation Parameter */ - uint8_t* nfcid; /*!< Initiator's NFCID2 or NFCID3 */ - uint8_t nfcidLen; /*!< Initiator's NFCID length (NFCID2/3) */ - uint8_t DID; /*!< Initiator's Device ID DID */ - uint8_t NAD; /*!< Initiator's Node ID NAD */ - uint8_t BS; /*!< Initiator's Bit Rates supported in Tx */ - uint8_t BR; /*!< Initiator's Bit Rates supported in Rx */ - uint8_t LR; /*!< Initiator's Length reduction */ - uint8_t* GB; /*!< Initiator's General Bytes (Gi) */ - uint8_t GBLen; /*!< Initiator's General Bytes length */ -} rfalNfcDepAtrParam; - -/*! Structure of parameters to be passed in for nfcDepListenStartActivation */ -typedef struct { - rfalNfcDepBufFormat* rxBuf; /*!< Receive Buffer struct reference */ - uint16_t* rxLen; /*!< Receive INF data length in bytes */ - bool* isRxChaining; /*!< Received data is not complete */ - rfalNfcDepDevice* nfcDepDev; /*!< NFC-DEP device info */ -} rfalNfcDepListenActvParam; - -/*! NFCIP Protocol structure for P2P Target - * - * operParam : derives from NFC-Forum NCI NFC-DEP Operation Parameter - * NCI 1.1 Table 86: NFC-DEP Operation Parameter - * and it's a bit mask composed as: - * [ 0000b - * | Chain SHALL use max. Transport Data Byte[1b] - * | I-PDU with no Transport Data SHALL NOT be sent [1b] - * | NFC-DEP Target SHALL NOT send RTOX request [1b] - * ] - * - */ -typedef struct { - rfalNfcDepCommMode commMode; /*!< Target in Active P2P or Passive P2P */ - uint8_t nfcid3[RFAL_NFCDEP_NFCID3_LEN]; /*!< Target's NFCID3 */ - uint8_t bst; /*!< Target's Bit Rates supported in Tx */ - uint8_t brt; /*!< Target's Bit Rates supported in Rx */ - uint8_t to; /*!< Target's timeout (TO) value */ - uint8_t ppt; /*!< Target's Presence optional Params(PPt)*/ - uint8_t GBt[RFAL_NFCDEP_GB_MAX_LEN]; /*!< Target's General Bytes (Gt) */ - uint8_t GBtLen; /*!< Target's General Bytes length */ - uint8_t operParam; /*!< NFC-DEP Operation Parameter */ -} rfalNfcDepTargetParam; - -/*! Structure of parameters to be passed in for nfcDepStartIpduTransceive */ -typedef struct { - rfalNfcDepBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in bytes */ - bool isTxChaining; /*!< Transmit data is not complete */ - rfalNfcDepBufFormat* rxBuf; /*!< Receive Buffer struct reference */ - uint16_t* rxLen; /*!< Receive INF data length */ - bool* isRxChaining; /*!< Received data is not complete */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalNfcDepTxRxParam; - -/*! Structure of parameters used on NFC DEP PDU Transceive */ -typedef struct { - rfalNfcDepPduBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in Bytes*/ - rfalNfcDepPduBufFormat* rxBuf; /*!< Receive Buffer struct reference in Bytes */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - rfalNfcDepBufFormat* tmpBuf; /*!< Temp buffer for single PDUs (internal) */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalNfcDepPduTxRxParam; - -/* - * ***************************************************************************** - * GLOBAL VARIABLE DECLARATIONS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -/*! - ****************************************************************************** - * \brief NFCIP Initialize - * - * This method resets all NFC-DEP inner states, counters and context and sets - * default values - * - ****************************************************************************** - */ -void rfalNfcDepInitialize(void); - -/*! - ****************************************************************************** - * \brief Set deactivating callback - * - * Sets the deactivating callback so that nfcip layer can check if upper layer - * has a deactivation pending, and not perform error recovery upon specific - * errors - * - * \param[in] pFunc : method pointer to deactivation flag check - ****************************************************************************** - */ -void rfalNfcDepSetDeactivatingCallback(rfalNfcDepDeactCallback pFunc); - -/*! - ****************************************************************************** - * \brief Calculate Response Waiting Time - * - * Calculates the Response Waiting Time (RWT) from the given Waiting Time (WT) - * - * \param[in] wt : the WT value to calculate RWT - * - * \return RWT value in 1/fc - ****************************************************************************** - */ -uint32_t rfalNfcDepCalculateRWT(uint8_t wt); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator ATR (Attribute Request) - * - * This method configures the NFC-DEP layer with given parameters and then - * sends an ATR to the Target with and checks for a valid response response - * - * \param[in] param : parameters to initialize and compose the ATR - * \param[out] atrRes : location to store the ATR_RES - * \param[out] atrResLen : length of the ATR_RES received - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode - rfalNfcDepATR(const rfalNfcDepAtrParam* param, rfalNfcDepAtrRes* atrRes, uint8_t* atrResLen); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator PSL (Parameter Selection) - * - * This method sends a PSL to the Target with the given parameters and checks - * for a valid response response - * - * The parameters must be coded according to Digital 1.1 16.7.1 - * - * \param[in] BRS : the selected Bit Rates for Initiator and Target - * \param[in] FSL : the maximum length of Commands and Responses - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode rfalNfcDepPSL(uint8_t BRS, uint8_t FSL); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator DSL (Deselect) - * - * This method checks if the NFCIP module is configured as initiator and if - * so sends a DSL REQ, waits the target's response and checks it - * - * In case of performing as target no action is taken - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_MAX_RERUNS : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode rfalNfcDepDSL(void); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator RLS (Release) - * - * This method checks if the NFCIP module is configured as initiator and if - * so sends a RLS REQ, waits target's response and checks it - * - * In case of performing as target no action is taken - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_MAX_RERUNS : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode rfalNfcDepRLS(void); - -/*! - ***************************************************************************** - * \brief NFC-DEP Initiator Handle Activation - * - * This performs a Activation into NFC-DEP layer with the given - * parameters. It sends ATR_REQ and if the higher bit rates are supported by - * both devices it additionally sends PSL - * Once Activated all details of the device are provided on nfcDepDev - * - * \param[in] param : required parameters to initialize and send ATR_REQ - * \param[in] desiredBR : Desired bit rate supported by the Poller - * \param[out] nfcDepDev : NFC-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalNfcDepInitiatorHandleActivation( - rfalNfcDepAtrParam* param, - rfalBitRate desiredBR, - rfalNfcDepDevice* nfcDepDev); - -/*! - ****************************************************************************** - * \brief Check if buffer contains valid ATR_REQ - * - * This method checks if the given ATR_REQ is valid - * - * - * \param[in] buf : buffer holding Initiator's received request - * \param[in] bufLen : size of the msg contained on the buf in Bytes - * \param[out] nfcid3 : pointer to where the NFCID3 may be outputted, - * nfcid3 has NFCF_SENSF_NFCID3_LEN as length - * Pass NULL if output parameter not desired - * - * \return true : Valid ATR_REQ received, the ATR_RES has been computed in txBuf - * \return false : Invalid protocol request - * - ****************************************************************************** - */ -bool rfalNfcDepIsAtrReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid3); - -/*! - ****************************************************************************** - * \brief Check is Target has received ATR - * - * This method checks if the NFCIP module is configured as target and if a - * ATR REQ has been received ( whether is in activation or in data exchange) - * - * \return true : a ATR has already been received - * \return false : no ATR has been received - ****************************************************************************** - */ -bool rfalNfcDepTargetRcvdATR(void); - -/*! - ***************************************************************************** - * \brief NFCDEP Start Listen Activation Handling - * - * Start Activation Handling and setup to receive first frame which may - * contain complete or partial DEP-REQ after activation is completed - * - * Pass in ATR_REQ for NFC-DEP to handle ATR_RES. The Activation Handling - * handles ATR_RES and PSL_RES if a PSL_REQ is received - * - * Activation is completed if PSL_RES is sent or if first I-PDU is received - * - * \ref rfalNfcDepListenGetActivationStatus() provide status of the - * ongoing activation - * - * \warning nfcDepGetTransceiveStatus() shall be called right after activation - * is completed (i.e. rfalNfcDepListenGetActivationStatus() return ERR_NONE) - * to check for first received frame. - * - * \param[in] param : Target parameters to be used - * \param[in] atrReq : reference to buffer containing ATR_REQ - * \param[in] atrReqLength: Length of ATR_REQ - * \param[out] rxParam : references to buffer, length and chaining indication - * for first complete LLCP to be received - * - * \return ERR_NONE : ATR_REQ is valid and activation ongoing - * \return ERR_PARAM : ATR_REQ or other params are invalid - * \return ERR_LINK_LOSS : Remote Field is turned off - ***************************************************************************** - */ -ReturnCode rfalNfcDepListenStartActivation( - const rfalNfcDepTargetParam* param, - const uint8_t* atrReq, - uint16_t atrReqLength, - rfalNfcDepListenActvParam rxParam); - -/*! - ***************************************************************************** - * \brief Get the current NFC-DEP Activation Status - * - * \return ERR_NONE : Activation has completed successfully - * \return ERR_BUSY : Activation is ongoing - * \return ERR_LINK_LOSS : Remote Field was turned off - ***************************************************************************** - */ -ReturnCode rfalNfcDepListenGetActivationStatus(void); - -/*! - ***************************************************************************** - * \brief Start Transceive - * - * Transceives a complete or partial DEP block - * - * The txBuf contains complete or partial of DEP to be transmitted. - * The Prologue field of the I-PDU is handled internally - * - * If the buffer contains partial LLCP and is not the last block, then - * isTxChaining must be set to true - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalNfcDepStartTransceive(const rfalNfcDepTxRxParam* param); - -/*! - ***************************************************************************** - * \brief Return the Transceive status - * - * Returns the status of the NFC-DEP Transceive - * - * \warning When the other device is performing chaining once a chained - * block is received the error ERR_AGAIN is sent. At this point - * caller must handle the received data immediately. - * When ERR_AGAIN is returned an ACK has already been sent to - * the other device and the next block might be incoming. - * If rfalWorker() is called frequently it will place the next - * block on the given buffer - * - * \return ERR_NONE : Transceive has been completed successfully - * \return ERR_BUSY : Transceive is ongoing - * \return ERR_PROTO : Protocol error occurred - * \return ERR_TIMEOUT : Timeout error occurred - * \return ERR_SLEEP_REQ : Deselect has been received and responded - * \return ERR_NOMEM : The received I-PDU does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : Communication is lost because Reader/Writer - * has turned off its field - * \return ERR_AGAIN : received one chaining block, continue to call - * this method to retrieve the remaining blocks - ***************************************************************************** - */ -ReturnCode rfalNfcDepGetTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief Start PDU Transceive - * - * This method triggers a NFC-DEP Transceive containing a complete PDU - * It transmits the given message and handles all protocol retransmitions, - * error handling and control messages - * - * The txBuf contains a complete PDU to be transmitted - * The Prologue field will be manipulated by the Transceive - * - * \warning the txBuf will be modified during the transmission - * \warning the maximum RF frame which can be received is limited by param.tmpBuf - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalNfcDepStartPduTransceive(rfalNfcDepPduTxRxParam param); - -/*! - ***************************************************************************** - * \brief Return the PSU Transceive status - * - * Returns the status of the NFC-DEP PDU Transceive - * - * - * \return ERR_NONE : Transceive has been completed successfully - * \return ERR_BUSY : Transceive is ongoing - * \return ERR_PROTO : Protocol error occurred - * \return ERR_TIMEOUT : Timeout error occurred - * \return ERR_SLEEP_REQ : Deselect has been received and responded - * \return ERR_NOMEM : The received I-PDU does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : Communication is lost because Reader/Writer - * has turned off its field - ***************************************************************************** - */ -ReturnCode rfalNfcDepGetPduTransceiveStatus(void); - -#endif /* RFAL_NFCDEP_H_ */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfca.h b/lib/ST25RFAL002/include/rfal_nfca.h deleted file mode 100644 index 4772586cbb..0000000000 --- a/lib/ST25RFAL002/include/rfal_nfca.h +++ /dev/null @@ -1,497 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfca.h - * - * \author Gustavo Patricio - * - * \brief Provides several NFC-A convenience methods and definitions - * - * It provides a Poller (ISO14443A PCD) interface and as well as - * some NFC-A Listener (ISO14443A PICC) helpers. - * - * The definitions and helpers methods provided by this module are only - * up to ISO14443-3 layer - * - * - * An usage example is provided here: \ref exampleRfalNfca.c - * \example exampleRfalNfca.c - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-A - * \brief RFAL NFC-A Module - * @{ - * - */ - -#ifndef RFAL_NFCA_H -#define RFAL_NFCA_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_t1t.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCA_CASCADE_1_UID_LEN \ - 4U /*!< UID length of cascade level 1 only tag */ -#define RFAL_NFCA_CASCADE_2_UID_LEN \ - 7U /*!< UID length of cascade level 2 only tag */ -#define RFAL_NFCA_CASCADE_3_UID_LEN \ - 10U /*!< UID length of cascade level 3 only tag */ - -#define RFAL_NFCA_SENS_RES_PLATFORM_MASK \ - 0x0FU /*!< SENS_RES (ATQA) platform configuration mask Digital 1.1 Table 10 */ -#define RFAL_NFCA_SENS_RES_PLATFORM_T1T \ - 0x0CU /*!< SENS_RES (ATQA) T1T platform configuration Digital 1.1 Table 10 */ - -#define RFAL_NFCA_SEL_RES_CONF_MASK \ - 0x60U /*!< SEL_RES (SAK) platform configuration mask Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_T2T \ - 0x00U /*!< SEL_RES (SAK) T2T configuration Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_T4T \ - 0x20U /*!< SEL_RES (SAK) T4T configuration Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_NFCDEP \ - 0x40U /*!< SEL_RES (SAK) NFC-DEP configuration Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_T4T_NFCDEP \ - 0x60U /*!< SEL_RES (SAK) T4T and NFC-DEP configuration Digital 1.1 Table 19 */ - -/*! NFC-A minimum FDT(listen) = ((n * 128 + (84)) / fc) with n_min = 9 Digital 1.1 6.10.1 - * = (1236)/fc - * Relax with 3etu: (3*128)/fc as with multiple NFC-A cards, response may take longer (JCOP cards) - * = (1236 + 384)/fc = 1620 / fc */ -#define RFAL_NFCA_FDTMIN 1620U -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Checks if device is a T1T given its SENS_RES */ -#define rfalNfcaIsSensResT1T(sensRes) \ - ((((rfalNfcaSensRes*)(sensRes))->platformInfo & RFAL_NFCA_SENS_RES_PLATFORM_MASK) == \ - RFAL_NFCA_SENS_RES_PLATFORM_T1T) - -/*! Checks if device is a T2T given its SENS_RES */ -#define rfalNfcaIsSelResT2T(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_T2T) - -/*! Checks if device is a T4T given its SENS_RES */ -#define rfalNfcaIsSelResT4T(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_T4T) - -/*! Checks if device supports NFC-DEP protocol given its SENS_RES */ -#define rfalNfcaIsSelResNFCDEP(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_NFCDEP) - -/*! Checks if device supports ISO-DEP and NFC-DEP protocol given its SENS_RES */ -#define rfalNfcaIsSelResT4TNFCDEP(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_T4T_NFCDEP) - -/*! Checks if a NFC-A listener device supports multiple protocols (ISO-DEP and NFC-DEP) */ -#define rfalNfcaLisDevIsMultiProto(lisDev) \ - (((rfalNfcaListenDevice*)(lisDev))->type == RFAL_NFCA_T4T_NFCDEP) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A Listen device types */ -typedef enum { - RFAL_NFCA_T1T = - 0x01, /* Device configured for T1T Digital 1.1 Table 9 */ - RFAL_NFCA_T2T = - 0x00, /* Device configured for T2T Digital 1.1 Table 19 */ - RFAL_NFCA_T4T = - 0x20, /* Device configured for T4T Digital 1.1 Table 19 */ - RFAL_NFCA_NFCDEP = - 0x40, /* Device configured for NFC-DEP Digital 1.1 Table 19 */ - RFAL_NFCA_T4T_NFCDEP = - 0x60 /* Device configured for NFC-DEP and T4T Digital 1.1 Table 19 */ -} rfalNfcaListenDeviceType; - -/*! SENS_RES (ATQA) format Digital 1.1 6.6.3 & Table 7 */ -typedef struct { - uint8_t - anticollisionInfo; /*!< SENS_RES Anticollision Information */ - uint8_t - platformInfo; /*!< SENS_RES Platform Information */ -} rfalNfcaSensRes; - -/*! SDD_REQ (Anticollision) format Digital 1.1 6.7.1 & Table 11 */ -typedef struct { - uint8_t - selCmd; /*!< SDD_REQ SEL_CMD: cascade Level */ - uint8_t - selPar; /*!< SDD_REQ SEL_PAR: Byte Count[4b] | Bit Count[4b] (NVB: Number of Valid Bits)*/ -} rfalNfcaSddReq; - -/*! SDD_RES (UID CLn) format Digital 1.1 6.7.2 & Table 15 */ -typedef struct { - uint8_t nfcid1 - [RFAL_NFCA_CASCADE_1_UID_LEN]; /*!< NFCID1 cascade level NFCID */ - uint8_t bcc; /*!< BCC Exclusive-OR over first 4 bytes of SDD_RES */ -} rfalNfcaSddRes; - -/*! SEL_REQ (Select) format Digital 1.1 6.8.1 & Table 17 */ -typedef struct { - uint8_t - selCmd; /*!< SDD_REQ SEL_CMD: cascade Level */ - uint8_t - selPar; /*!< SDD_REQ SEL_PAR: Byte Count[4b] | Bit Count[4b] (NVB: Number of Valid Bits)*/ - uint8_t nfcid1 - [RFAL_NFCA_CASCADE_1_UID_LEN]; /*!< NFCID1 data */ - uint8_t bcc; /*!< Checksum calculated as exclusive-OR over the 4 bytes of NFCID1 CLn */ -} rfalNfcaSelReq; - -/*! SEL_RES (SAK) format Digital 1.1 6.8.2 & Table 19 */ -typedef struct { - uint8_t sak; /*!< Select Acknowledge */ -} rfalNfcaSelRes; - -/*! NFC-A listener device (PICC) struct */ -typedef struct { - rfalNfcaListenDeviceType - type; /*!< NFC-A Listen device type */ - rfalNfcaSensRes - sensRes; /*!< SENS_RES (ATQA) */ - rfalNfcaSelRes - selRes; /*!< SEL_RES (SAK) */ - uint8_t - nfcId1Len; /*!< NFCID1 Length */ - uint8_t nfcId1 - [RFAL_NFCA_CASCADE_3_UID_LEN]; /*!< NFCID1 (UID) */ -#ifdef RFAL_FEATURE_T1T - rfalT1TRidRes - ridRes; /*!< RID_RES */ -#endif /* RFAL_FEATURE_T1T */ - bool isSleep; /*!< Device sleeping flag */ -} rfalNfcaListenDevice; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-A Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-A Poller/RW (ISO14443A PCD) including all default timings and bit rate - * to 106 kbps - - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Check Presence - * - * This method checks if a NFC-A Listen device (PICC) is present on the field - * by sending an ALL_REQ (WUPA) or SENS_REQ (REQA) - * - * \param[in] cmd : Indicate if to send an ALL_REQ or a SENS_REQ - * \param[out] sensRes : If received, the SENS_RES - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PAR : Parity error detected, one or more device in the field - * \return ERR_CRC : CRC error detected, one or more device in the field - * \return ERR_FRAMING : Framing error detected, one or more device in the field - * \return ERR_PROTO : Protocol error detected, one or more device in the field - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_NONE : No error, one or more device in the field - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerCheckPresence(rfal14443AShortFrameCmd cmd, rfalNfcaSensRes* sensRes); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Select - * - * This method selects a NFC-A Listener device (PICC) - * - * \param[in] nfcid1 : Listener device NFCID1 to be selected - * \param[in] nfcidLen : Length of the NFCID1 to be selected - * \param[out] selRes : pointer to place the SEL_RES - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, SEL_RES received - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSelect(const uint8_t* nfcid1, uint8_t nfcidLen, rfalNfcaSelRes* selRes); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Sleep - * - * This method sends a SLP_REQ (HLTA) - * No response is expected afterwards Digital 1.1 6.9.2.1 - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSleep(void); - -/*! - ***************************************************************************** - * \brief NFC-A Technology Detection - * - * This method performs NFC-A Technology Detection as defined in the spec - * given in the compliance mode - * - * \param[in] compMode : compliance mode to be performed - * \param[out] sensRes : location to store the SENS_RES, if received - * - * When compMode is set to ISO compliance a SLP_REQ (HLTA) is not sent - * after detection. When set to EMV a ALL_REQ (WUPA) is sent instead of - * a SENS_REQ (REQA) - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error, one or more device in the field - ***************************************************************************** - */ -ReturnCode - rfalNfcaPollerTechnologyDetection(rfalComplianceMode compMode, rfalNfcaSensRes* sensRes); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Collision Resolution - * - * Collision resolution for one NFC-A Listener device/card (PICC) as - * defined in Activity 2.1 9.3.4 - * - * This method executes anti collision loop and select the device with higher NFCID1 - * - * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immediately. If only one device is found - * with no collisions, it will properly resolved. - * - * \param[in] devLimit : device limit value (CON_DEVICES_LIMIT) - * \param[out] collPending : pointer to collision pending flag (INT_COLL_PEND) - * \param[out] selRes : location to store the last Select Response from listener device (PICC) - * \param[out] nfcId1 : location to store the NFCID1 (UID), ensure RFAL_NFCA_CASCADE_3_UID_LEN - * \param[out] nfcId1Len : pointer to length of NFCID1 (UID) - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_PROTO : Card length invalid - * \return ERR_IGNORE : conDevLimit is 0 and there is a collision - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Full Collision Resolution - * - * Performs a full Collision resolution as defined in Activity 2.1 9.3.4 - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcaDevList : NFC-A listener device info - * \param[out] devCnt : Devices found counter - * - * When compMode is set to ISO compliance it assumes that the device is - * not sleeping and therefore no ALL_REQ (WUPA) is sent at the beginning. - * When compMode is set to NFC compliance an additional ALL_REQ (WUPA) is sent - * at the beginning. - * - * - * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immediately. If only one device is found - * with no collisions, it will properly resolved. - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Full Collision Resolution with Sleep - * - * Performs a full Collision resolution similar to rfalNfcaPollerFullCollisionResolution - * but an additional SLP_REQ (HLTA) -> SENS_RES (REQA) is sent regardless if there - * was a collision. - * This proprietary behaviour ensures proper activation of certain devices that suffer - * from influence of Type B commands as foreseen in ISO14443-3 5.2.3 or were somehow - * not detected by the first round of collision resolution - * - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcaDevList : NFC-A listener device info - * \param[out] devCnt : Devices found counter - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSleepFullCollisionResolution( - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Start Full Collision Resolution - * - * This method starts the full Collision resolution as defined - * in Activity 1.0 or 1.1 9.3.4 - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcaDevList : NFC-A listener device info - * \param[out] devCnt : Devices found counter - * - * When compMode is set to ISO compliance it assumes that the device is - * not sleeping and therefore no ALL_REQ (WUPA) is sent at the beginning. - * When compMode is set to NFC compliance an additional ALL_REQ (WUPA) is sent at - * the beginning. - * - * - * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immediately. If only one device is found - * with no collisions, it will properly resolved. - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerStartFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-A Get Full Collision Resolution Status - * - * Returns the Collision Resolution status - * - * \return ERR_BUSY : Operation is ongoing - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerGetFullCollisionResolutionStatus(void); - -/*! - ***************************************************************************** - * \brief NFC-A Listener is SLP_REQ - * - * Checks if the given buffer contains valid NFC-A SLP_REQ (HALT) - * - * \param[in] buf: buffer containing data - * \param[in] bufLen: length of the data in buffer to be checked - * - * \return true if data in buf contains a SLP_REQ ; false otherwise - ***************************************************************************** - */ -bool rfalNfcaListenerIsSleepReq(const uint8_t* buf, uint16_t bufLen); - -#endif /* RFAL_NFCA_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcb.h b/lib/ST25RFAL002/include/rfal_nfcb.h deleted file mode 100644 index 67bcb32713..0000000000 --- a/lib/ST25RFAL002/include/rfal_nfcb.h +++ /dev/null @@ -1,425 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcb.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-B (ISO14443B) helpers - * - * It provides a NFC-B Poller (ISO14443B PCD) interface and - * also provides some NFC-B Listener (ISO14443B PICC) helpers - * - * The definitions and helpers methods provided by this module are only - * up to ISO14443-3 layer (excluding ATTRIB) - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-B - * \brief RFAL NFC-B Module - * @{ - * - */ - -#ifndef RFAL_NFCB_H -#define RFAL_NFCB_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCB_FWTSENSB 7680U /*!< NFC-B FWT(SENSB) Digital 2.0 B.3 */ -#define RFAL_NFCB_DFWT 49152U /*!< NFC-B dFWT Delta 2.0 7.9.1.3 & B.3 */ -#define RFAL_NFCB_DTPOLL_10 rfalConvMsTo1fc(20) /*!< NFC-B Delta Tb Poll Digital 1.0 A.2 */ -#define RFAL_NFCB_DTPOLL_20 rfalConvMsTo1fc(17) /*!< NFC-B Delta Tb Poll Digital 2.1 B.3 */ - -#define RFAL_NFCB_AFI 0x00U /*!< NFC-B default Application Family Digital 1.1 7.6.1.1 */ -#define RFAL_NFCB_PARAM 0x00U /*!< NFC-B default SENSB_REQ PARAM */ -#define RFAL_NFCB_CRC_LEN 2U /*!< NFC-B CRC length and CRC_B(AID) Digital 1.1 Table 28 */ -#define RFAL_NFCB_NFCID0_LEN 4U /*!< Length of NFC-B NFCID0 */ -#define RFAL_NFCB_CMD_LEN 1U /*!< Length of NFC-B Command */ - -#define RFAL_NFCB_SENSB_RES_LEN 12U /*!< Standard length of SENSB_RES without SFGI byte */ -#define RFAL_NFCB_SENSB_RES_EXT_LEN \ - 13U /*!< Extended length of SENSB_RES with SFGI byte */ - -#define RFAL_NFCB_SENSB_REQ_ADV_FEATURE \ - 0x20U /*!< Bit mask for Advance Feature in SENSB_REQ */ -#define RFAL_NFCB_SENSB_RES_FSCI_MASK \ - 0x0FU /*!< Bit mask for FSCI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FSCI_SHIFT \ - 4U /*!< Shift for FSCI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_RFU_MASK \ - 0x08U /*!< Bit mask for Protocol Type RFU in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK \ - 0x03U /*!< Bit mask for Protocol Type TR2 in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_TR2_SHIFT \ - 1U /*!< Shift for Protocol Type TR2 in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK \ - 0x01U /*!< Bit mask Protocol Type ISO14443 Compliant in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FWI_MASK \ - 0x0FU /*!< Bit mask for FWI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FWI_SHIFT \ - 4U /*!< Bit mask for FWI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_ADC_MASK \ - 0x0CU /*!< Bit mask for ADC value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_ADC_ADV_FEATURE_MASK \ - 0x08U /*!< Bit mask for ADC.Advanced Proto Features in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_ADC_PROPRIETARY_MASK \ - 0x04U /*!< Bit mask for ADC.Proprietary Application in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FO_DID_MASK \ - 0x01U /*!< Bit mask for DID in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FO_NAD_MASK \ - 0x02U /*!< Bit mask for DID in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FO_MASK \ - 0x03U /*!< Bit mask for FO value in SENSB_RES (NAD and DID) */ -#define RFAL_NFCB_SENSB_RES_SFGI_MASK \ - 0x0FU /*!< Bit mask for SFGI in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_SFGI_SHIFT \ - 4U /*!< Shift for SFGI in SENSB_RES */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Get device's FSCI given its SENSB_RES Digital 1.1 7.6.2 */ -#define rfalNfcbGetFSCI(sensbRes) \ - ((((rfalNfcbSensbRes*)(sensbRes))->protInfo.FsciProType >> RFAL_NFCB_SENSB_RES_FSCI_SHIFT) & \ - RFAL_NFCB_SENSB_RES_FSCI_MASK) - -/*! Checks if the given NFC-B device indicates ISO-DEP support */ -#define rfalNfcbIsIsoDepSupported(dev) \ - ((((rfalNfcbListenDevice*)(dev))->sensbRes.protInfo.FsciProType & \ - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK) != 0U) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! SENSB_REQ and ALLB_REQ param Digital 1.1 7.6.1 */ -typedef enum { - RFAL_NFCB_SENS_CMD_ALLB_REQ = 0x08, /*!< ALLB_REQ (WUPB) */ - RFAL_NFCB_SENS_CMD_SENSB_REQ = 0x00 /*!< SENSB_REQ (REQB) */ -} rfalNfcbSensCmd; - -/*! Number of Slots (NI) codes used for NFC-B anti collision Digital 1.1 Table 26 */ -typedef enum { - RFAL_NFCB_SLOT_NUM_1 = 0, /*!< N=0 : 1 slot */ - RFAL_NFCB_SLOT_NUM_2 = 1, /*!< N=1 : 2 slots */ - RFAL_NFCB_SLOT_NUM_4 = 2, /*!< N=2 : 4 slots */ - RFAL_NFCB_SLOT_NUM_8 = 3, /*!< N=3 : 8 slots */ - RFAL_NFCB_SLOT_NUM_16 = 4 /*!< N=4 : 16 slots */ -} rfalNfcbSlots; - -/*! SENSB_RES (ATQB) Application Data Format Digital 1.1 Table 28 */ -typedef struct { - uint8_t AFI; /*!< Application Family Identifier */ - uint8_t CRC_B[RFAL_NFCB_CRC_LEN]; /*!< CRC_B of AID */ - uint8_t numApps; /*!< Number of Applications */ -} rfalNfcbSensbResAppData; - -/*! SENSB_RES Protocol Info format Digital 1.1 Table 29 */ -typedef struct { - uint8_t - BRC; /*!< Bit Rate Capability */ - uint8_t - FsciProType; /*!< Frame Size Card Integer [4b] | Protocol Type[4 bits] */ - uint8_t - FwiAdcFo; /*!< Frame Waiting Integer [4b] | Application Data Coding [2b] | Frame Options [2b] */ - uint8_t - SFGI; /*!< Optional: Start-Up Frame Guard Time Integer[4b] | RFU [4b] */ -} rfalNfcbSensbResProtocolInfo; - -/*! SENSB_RES format Digital 1.1 7.6.2 */ -typedef struct { - uint8_t cmd; /*!< SENSB_RES: 50h */ - uint8_t nfcid0[RFAL_NFCB_NFCID0_LEN]; /*!< NFC Identifier (PUPI)*/ - rfalNfcbSensbResAppData appData; /*!< Application Data */ - rfalNfcbSensbResProtocolInfo protInfo; /*!< Protocol Information */ -} rfalNfcbSensbRes; - -/*! NFC-B listener device (PICC) struct */ -typedef struct { - uint8_t sensbResLen; /*!< SENSB_RES length */ - rfalNfcbSensbRes sensbRes; /*!< SENSB_RES */ - bool isSleep; /*!< Device sleeping flag */ -} rfalNfcbListenDevice; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-B Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-B Poller/RW (ISO14443B PCD) including all default timings - * - * It sets NFC-B parameters (AFI, PARAM) to default values - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief Set NFC-B Poller parameters - * - * This methods configures RFAL RF layer to perform as a - * NFCA Poller/RW (ISO14443A PCD) including all default timings - * - * Additionally configures NFC-B specific parameters to be used on the - * following communications - * - * \param[in] AFI : Application Family Identifier to be used - * \param[in] PARAM : PARAM to be used, it announces whether Advanced - * Features or Extended SENSB_RES is supported - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerInitializeWithParams(uint8_t AFI, uint8_t PARAM); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Check Presence - * - * This method checks if a NFC-B Listen device (PICC) is present on the field - * by sending an ALLB_REQ (WUPB) or SENSB_REQ (REQB) - * - * \param[in] cmd : Indicate if to send an ALL_REQ or a SENS_REQ - * \param[in] slots : The number of slots to be announced - * \param[out] sensbRes : If received, the SENSB_RES - * \param[out] sensbResLen : If received, the SENSB_RES length - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PAR : Parity error detected, one or more device in the field - * \return ERR_CRC : CRC error detected, one or more device in the field - * \return ERR_FRAMING : Framing error detected, one or more device in the field - * \return ERR_PROTO : Protocol error detected, invalid SENSB_RES received - * \return ERR_NONE : No error, SENSB_RES received - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerCheckPresence( - rfalNfcbSensCmd cmd, - rfalNfcbSlots slots, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Sleep - * - * This function is used to send the SLPB_REQ (HLTB) command to put the PICC with - * the given NFCID0 to state HALT so that they do not reply to further SENSB_REQ - * commands (only to ALLB_REQ) - * - * \param[in] nfcid0 : NFCID of the device to be put to Sleep - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerSleep(const uint8_t* nfcid0); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Slot Marker - * - * This method selects a NFC-B Slot marker frame - * - * \param[in] slotCode : Slot Code [1-15] - * \param[out] sensbRes : If received, the SENSB_RES - * \param[out] sensbResLen : If received, the SENSB_RES length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, SEL_RES received - ***************************************************************************** - */ -ReturnCode - rfalNfcbPollerSlotMarker(uint8_t slotCode, rfalNfcbSensbRes* sensbRes, uint8_t* sensbResLen); - -/*! - ***************************************************************************** - * \brief NFC-B Technology Detection - * - * This method performs NFC-B Technology Detection as defined in the spec - * given in the compliance mode - * - * \param[in] compMode : compliance mode to be performed - * \param[out] sensbRes : location to store the SENSB_RES, if received - * \param[out] sensbResLen : length of the SENSB_RES, if received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error, one or more device in the field - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerTechnologyDetection( - rfalComplianceMode compMode, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Collision Resolution - * - * NFC-B Collision resolution Listener device/card (PICC) as - * defined in Activity 1.1 9.3.5 - * - * This function is used to perform collision resolution for detection in case - * of multiple NFC Forum Devices with Technology B detected. - * Target with valid SENSB_RES will be stored in devInfo and nfcbDevCount incremented. - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcbDevList - * \param[out] nfcbDevList : NFC-B listener device info - * \param[out] devCnt : devices found counter - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Collision Resolution Slotted - * - * NFC-B Collision resolution Listener device/card (PICC). The sequence can - * be configured to be according to NFC Forum Activity 1.1 9.3.5, ISO10373 - * or EMVCo - * - * This function is used to perform collision resolution for detection in case - * of multiple NFC Forum Devices with Technology B are detected. - * Target with valid SENSB_RES will be stored in devInfo and nfcbDevCount incremented. - * - * This method provides the means to perform a collision resolution loop with specific - * initial and end number of slots. This allows to user to start the loop already with - * greater number of slots, and or limit the end number of slots. At the end a flag - * indicating whether there were collisions pending is returned. - * - * If RFAL_COMPLIANCE_MODE_ISO is used \a initSlots must be set to RFAL_NFCB_SLOT_NUM_1 - * - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcbDevList - * \param[in] initSlots : number of slots to open initially - * \param[in] endSlots : number of slots when to stop collision resolution - * \param[out] nfcbDevList : NFC-B listener device info - * \param[out] devCnt : devices found counter - * \param[out] colPending : flag indicating whether collision are still pending - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerSlottedCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbSlots initSlots, - rfalNfcbSlots endSlots, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt, - bool* colPending); - -/*! - ***************************************************************************** - * \brief NFC-B TR2 code to FDT - * - * Converts the TR2 code as defined in Digital 1.1 Table 33 Minimum - * TR2 Coding to Frame Delay Time (FDT) in 1/Fc - * - * \param[in] tr2Code : TR2 code as defined in Digital 1.1 Table 33 - * - * \return FDT in 1/Fc - ***************************************************************************** - */ -uint32_t rfalNfcbTR2ToFDT(uint8_t tr2Code); - -#endif /* RFAL_NFCB_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcf.h b/lib/ST25RFAL002/include/rfal_nfcf.h deleted file mode 100644 index 30c34765db..0000000000 --- a/lib/ST25RFAL002/include/rfal_nfcf.h +++ /dev/null @@ -1,403 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcf.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-F Poller (FeliCa PCD) device - * - * The definitions and helpers methods provided by this module are - * aligned with NFC-F (FeliCa - JIS X6319-4) - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-F - * \brief RFAL NFC-F Module - * @{ - * - */ - -#ifndef RFAL_NFCF_H -#define RFAL_NFCF_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCF_NFCID2_LEN 8U /*!< NFCID2 (FeliCa IDm) length */ -#define RFAL_NFCF_SENSF_RES_LEN_MIN 16U /*!< SENSF_RES minimum length */ -#define RFAL_NFCF_SENSF_RES_LEN_MAX 18U /*!< SENSF_RES maximum length */ -#define RFAL_NFCF_SENSF_RES_PAD0_LEN 2U /*!< SENSF_RES PAD0 length */ -#define RFAL_NFCF_SENSF_RES_PAD1_LEN 2U /*!< SENSF_RES PAD1 length */ -#define RFAL_NFCF_SENSF_RES_RD_LEN 2U /*!< SENSF_RES Request Data length */ -#define RFAL_NFCF_SENSF_RES_BYTE1 1U /*!< SENSF_RES first byte value */ -#define RFAL_NFCF_SENSF_SC_LEN 2U /*!< Felica SENSF_REQ System Code length */ -#define RFAL_NFCF_SENSF_PARAMS_SC1_POS 0U /*!< System Code byte1 position in the SENSF_REQ */ -#define RFAL_NFCF_SENSF_PARAMS_SC2_POS 1U /*!< System Code byte2 position in the SENSF_REQ */ -#define RFAL_NFCF_SENSF_PARAMS_RC_POS 2U /*!< Request Code position in the SENSF_REQ */ -#define RFAL_NFCF_SENSF_PARAMS_TSN_POS 3U /*!< Time Slot Number position in the SENSF_REQ */ -#define RFAL_NFCF_POLL_MAXCARDS 16U /*!< Max number slots/cards 16 */ - -#define RFAL_NFCF_CMD_POS 0U /*!< Command/Response code length */ -#define RFAL_NFCF_CMD_LEN 1U /*!< Command/Response code length */ -#define RFAL_NFCF_LENGTH_LEN 1U /*!< LEN field length */ -#define RFAL_NFCF_HEADER_LEN (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CMD_LEN) /*!< Header length*/ - -#define RFAL_NFCF_SENSF_NFCID2_BYTE1_POS \ - 0U /*!< NFCID2 byte1 position */ -#define RFAL_NFCF_SENSF_NFCID2_BYTE2_POS \ - 1U /*!< NFCID2 byte2 position */ - -#define RFAL_NFCF_SENSF_NFCID2_PROT_TYPE_LEN \ - 2U /*!< NFCID2 length for byte 1 and byte 2 indicating NFC-DEP or T3T support */ -#define RFAL_NFCF_SENSF_NFCID2_BYTE1_NFCDEP \ - 0x01U /*!< NFCID2 byte1 NFC-DEP support Digital 1.0 Table 44 */ -#define RFAL_NFCF_SENSF_NFCID2_BYTE2_NFCDEP \ - 0xFEU /*!< NFCID2 byte2 NFC-DEP support Digital 1.0 Table 44 */ - -#define RFAL_NFCF_SYSTEMCODE \ - 0xFFFFU /*!< SENSF_RES Default System Code Digital 1.0 6.6.1.1 */ - -#define RFAL_NFCF_BLOCK_LEN \ - 16U /*!< NFCF T3T Block size T3T 1.0 4.1 */ -#define RFAL_NFCF_CHECKUPDATE_RES_ST1_POS \ - 9U /*!< Check|Update Res Status Flag 1 position T3T 1.0 Table 8 */ -#define RFAL_NFCF_CHECKUPDATE_RES_ST2_POS \ - 10U /*!< Check|Update Res Status Flag 2 position T3T 1.0 Table 8 */ -#define RFAL_NFCF_CHECKUPDATE_RES_NOB_POS \ - 11U /*!< Check|Update Res Number of Blocks position T3T 1.0 Table 8 */ - -#define RFAL_NFCF_STATUS_FLAG_SUCCESS \ - 0x00U /*!< Check response Number of Blocks position T3T 1.0 Table 11 */ -#define RFAL_NFCF_STATUS_FLAG_ERROR \ - 0xFFU /*!< Check response Number of Blocks position T3T 1.0 Table 11 */ - -#define RFAL_NFCF_BLOCKLISTELEM_LEN \ - 0x80U /*!< Block List Element Length bit (2|3 bytes) T3T 1.0 5.6.1 */ - -#define RFAL_NFCF_SERVICECODE_RDONLY \ - 0x000BU /*!< NDEF Service Code as Read-Only T3T 1.0 7.2.1 */ -#define RFAL_NFCF_SERVICECODE_RDWR \ - 0x0009U /*!< NDEF Service Code as Read and Write T3T 1.0 7.2.1 */ - -/*! NFC-F Felica command set JIS X6319-4 9.1 */ -enum { - RFAL_NFCF_CMD_POLLING = - 0x00, /*!< SENSF_REQ (Felica Poll/REQC command to identify a card ) */ - RFAL_NFCF_CMD_POLLING_RES = - 0x01, /*!< SENSF_RES (Felica Poll/REQC command response ) */ - RFAL_NFCF_CMD_REQUEST_SERVICE = - 0x02, /*!< verify the existence of Area and Service */ - RFAL_NFCF_CMD_REQUEST_RESPONSE = - 0x04, /*!< verify the existence of a card */ - RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION = - 0x06, /*!< read Block Data from a Service that requires no authentication */ - RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION_RES = - 0x07, /*!< read Block Data response from a Service with no authentication */ - RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION = - 0x08, /*!< write Block Data to a Service that requires no authentication */ - RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION_RES = - 0x09, /*!< write Block Data response to a Service with no authentication */ - RFAL_NFCF_CMD_REQUEST_SYSTEM_CODE = - 0x0c, /*!< acquire the System Code registered to a card */ - RFAL_NFCF_CMD_AUTHENTICATION1 = - 0x10, /*!< authenticate a card */ - RFAL_NFCF_CMD_AUTHENTICATION2 = - 0x12, /*!< allow a card to authenticate a Reader/Writer */ - RFAL_NFCF_CMD_READ = - 0x14, /*!< read Block Data from a Service that requires authentication */ - RFAL_NFCF_CMD_WRITE = - 0x16, /*!< write Block Data to a Service that requires authentication */ -}; - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Checks if the given NFC-F device indicates NFC-DEP support */ -#define rfalNfcfIsNfcDepSupported(dev) \ - ((((rfalNfcfListenDevice*)(dev))->sensfRes.NFCID2[RFAL_NFCF_SENSF_NFCID2_BYTE1_POS] == \ - RFAL_NFCF_SENSF_NFCID2_BYTE1_NFCDEP) && \ - (((rfalNfcfListenDevice*)(dev))->sensfRes.NFCID2[RFAL_NFCF_SENSF_NFCID2_BYTE2_POS] == \ - RFAL_NFCF_SENSF_NFCID2_BYTE2_NFCDEP)) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-F SENSF_RES format Digital 1.1 8.6.2 */ -typedef struct { - uint8_t CMD; /*!< Command Code: 01h */ - uint8_t NFCID2[RFAL_NFCF_NFCID2_LEN]; /*!< NFCID2 */ - uint8_t PAD0[RFAL_NFCF_SENSF_RES_PAD0_LEN]; /*!< PAD0 */ - uint8_t PAD1[RFAL_NFCF_SENSF_RES_PAD1_LEN]; /*!< PAD1 */ - uint8_t MRTIcheck; /*!< MRTIcheck */ - uint8_t MRTIupdate; /*!< MRTIupdate */ - uint8_t PAD2; /*!< PAD2 */ - uint8_t RD[RFAL_NFCF_SENSF_RES_RD_LEN]; /*!< Request Data */ -} rfalNfcfSensfRes; - -/*! NFC-F poller device (PCD) struct */ -typedef struct { - uint8_t NFCID2[RFAL_NFCF_NFCID2_LEN]; /*!< NFCID2 */ -} rfalNfcfPollDevice; - -/*! NFC-F listener device (PICC) struct */ -typedef struct { - uint8_t sensfResLen; /*!< SENF_RES length */ - rfalNfcfSensfRes sensfRes; /*!< SENF_RES */ -} rfalNfcfListenDevice; - -typedef uint16_t rfalNfcfServ; /*!< NFC-F Service Code */ - -/*! NFC-F Block List Element (2 or 3 bytes element) T3T 1.0 5.6.1 */ -typedef struct { - uint8_t conf; /*!< Access Mode | Serv Code List Order */ - uint16_t blockNum; /*!< Block Number */ -} rfalNfcfBlockListElem; - -/*! Check Update Service list and Block list parameter */ -typedef struct { - uint8_t numServ; /*!< Number of Services */ - rfalNfcfServ* servList; /*!< Service Code List */ - uint8_t numBlock; /*!< Number of Blocks */ - rfalNfcfBlockListElem* blockList; /*!< Block Number List */ -} rfalNfcfServBlockListParam; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-F Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-F Poller/RW (FeliCa PCD) including all default timings - * - * \param[in] bitRate : NFC-F bitrate to be initialize (212 or 424) - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Incorrect bitrate - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerInitialize(rfalBitRate bitRate); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Check Presence - * - * This function sends a Poll/SENSF command according to NFC Activity spec - * It detects if a NCF-F device is within range - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_NONE : No error and some NFC-F device was detected - * - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerCheckPresence(void); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Poll - * - * This function sends to all PICCs in field the POLL command with the given - * number of slots. - * - * \param[in] slots : the number of slots to be performed - * \param[in] sysCode : as given in FeliCa poll command - * \param[in] reqCode : FeliCa communication parameters - * \param[out] cardList : Parameter of type rfalFeliCaPollRes which will hold the cards found - * \param[out] devCnt : actual number of cards found - * \param[out] collisions : number of collisions encountered - * - * \warning the list cardList has to be as big as the number of slots for the Poll - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_NONE : No error and some NFC-F device was detected - * - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* cardList, - uint8_t* devCnt, - uint8_t* collisions); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Full Collision Resolution - * - * Performs a full Collision resolution as defined in Activity 1.1 9.3.4 - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcfDevList : NFC-F listener devices list - * \param[out] devCnt : Devices found counter - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcfListenDevice* nfcfDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Check/Read - * - * It computes a Check / Read command according to T3T 1.0 and JIS X6319-4 and - * sends it to PICC. If successfully, the rxBuf will contain the the number of - * blocks in the first byte followed by the blocks data. - * - * \param[in] nfcid2 : nfcid2 of the device - * \param[in] servBlock : parameter containing the list of Services and - * Blocks to be addressed by this command - * \param[out] rxBuf : buffer to place check/read data - * \param[in] rxBufLen : size of the rxBuf - * \param[out] rcvdLen : length of data placed in rxBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_REQUEST : The request was executed with error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerCheck( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvdLen); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Update/Write - * - * It computes a Update / Write command according to T3T 1.0 and JIS X6319-4 and - * sends it to PICC. - * - * \param[in] nfcid2 : nfcid2 of the device - * \param[in] servBlock : parameter containing the list of Services and - * Blocks to be addressed by this command - * \param[in] txBuf : buffer where the request will be composed - * \param[in] txBufLen : size of txBuf - * \param[in] blockData : data to written on the given block(s) - * \param[out] rxBuf : buffer to place check/read data - * \param[in] rxBufLen : size of the rxBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_REQUEST : The request was executed with error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerUpdate( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* txBuf, - uint16_t txBufLen, - const uint8_t* blockData, - uint8_t* rxBuf, - uint16_t rxBufLen); - -/*! - ***************************************************************************** - * \brief NFC-F Listener is T3T Request - * - * This method checks if the given data is a valid T3T command (Read or Write) - * and in case a valid request has been received it may output the request's NFCID2 - * - * \param[in] buf : buffer holding Initiator's received command - * \param[in] bufLen : length of received command in bytes - * \param[out] nfcid2 : pointer to where the NFCID2 may be outputted, - * nfcid2 has NFCF_SENSF_NFCID2_LEN as length - * Pass NULL if output parameter not desired - * - * \return true : Valid T3T command (Read or Write) received - * \return false : Invalid protocol request - * - ***************************************************************************** - */ -bool rfalNfcfListenerIsT3TReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid2); - -#endif /* RFAL_NFCF_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcv.h b/lib/ST25RFAL002/include/rfal_nfcv.h deleted file mode 100644 index b5e1c00aeb..0000000000 --- a/lib/ST25RFAL002/include/rfal_nfcv.h +++ /dev/null @@ -1,923 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

© COPYRIGHT 2020 STMicroelectronics

- * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcv.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-V Poller (ISO15693) device - * - * The definitions and helpers methods provided by this module - * are aligned with NFC-V Digital 2.1 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-V - * \brief RFAL NFC-V Module - * @{ - * - */ - -#ifndef RFAL_NFCV_H -#define RFAL_NFCV_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCV_UID_LEN 8U /*!< NFC-V UID length */ -#define RFAL_NFCV_MAX_BLOCK_LEN \ - 32U /*!< Max Block size: can be of up to 256 bits ISO 15693 2000 5 */ -#define RFAL_NFCV_BNO_LEN 1U /*!< NFC-V Block Number length */ -#define RFAL_NFCV_CRC_LEN 2U /*!< NFC-V CRC length */ -#define RFAL_NFCV_MAX_GEN_DATA_LEN \ - (RFAL_NFCV_MAX_BLOCK_LEN + RFAL_NFCV_BNO_LEN + RFAL_NFCV_UID_LEN) /*!
© COPYRIGHT 2020 STMicroelectronics

s)& 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%0A% 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 z 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